File Coverage

blib/lib/HTTP/Engine/Middleware/Static.pm
Criterion Covered Total %
statement 24 24 100.0
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 32 32 100.0


line stmt bran cond sub pod time code
1             package HTTP::Engine::Middleware::Static;
2 1     1   871 use HTTP::Engine::Middleware;
  1         2  
  1         9  
3 1     1   10 use HTTP::Engine::Response;
  1         2  
  1         42  
4              
5 1     1   6 use MIME::Types;
  1         2  
  1         76  
6 1     1   6 use Path::Class;
  1         3  
  1         76  
7 1     1   6 use Cwd;
  1         2  
  1         79  
8 1     1   7 use Any::Moose 'X::Types::Path::Class';
  1         2  
  1         9  
9 1     1   363 use Any::Moose '::Util::TypeConstraints';
  1         2  
  1         4  
10 1     1   301 use File::Spec::Unix;
  1         2  
  1         801  
11              
12             # corece of Regexp
13             subtype 'HTTP::Engine::Middleware::Static::Regexp'
14             => as 'RegexpRef';
15             coerce 'HTTP::Engine::Middleware::Static::Regexp'
16             => from 'Str' => via { qr/$_/ };
17              
18             has 'regexp' => (
19             is => 'ro',
20             isa => 'HTTP::Engine::Middleware::Static::Regexp',
21             coerce => 1,
22             required => 1,
23             );
24              
25             has 'docroot' => (
26             is => 'ro',
27             isa => 'Path::Class::Dir',
28             coerce => 1,
29             required => 1,
30             );
31              
32             has directory_index => (
33             is => 'ro',
34             isa => 'Str|Undef',
35             );
36              
37             has 'mime_types' => (
38             is => 'ro',
39             isa => 'MIME::Types',
40             lazy => 1,
41             default => sub {
42             my $mime_types = MIME::Types->new(only_complete => 1);
43             $mime_types->create_type_index;
44             $mime_types;
45             },
46             );
47              
48             before_handle {
49             my ( $c, $self, $req ) = @_;
50              
51             my $re = $self->regexp;
52             my $uri_path = $req->uri->path;
53             return $req unless $uri_path && $uri_path =~ /^(?:$re)$/;
54              
55             my $docroot = dir($self->docroot)->absolute;
56             my $file = do {
57             if ($uri_path =~ m{/$} && $self->directory_index) {
58             $docroot->file(
59             File::Spec::Unix->splitpath($uri_path),
60             $self->directory_index
61             );
62             } else {
63             $docroot->file(
64             File::Spec::Unix->splitpath($uri_path)
65             )
66             }
67             };
68              
69             # check directory traversal
70             my $realpath = Cwd::realpath($file->absolute->stringify);
71             return HTTP::Engine::Response->new( status => 403, body => 'forbidden') unless $docroot->subsumes($realpath);
72              
73             return HTTP::Engine::Response->new( status => '404', body => 'not found' ) unless -e $file;
74              
75             my $content_type = 'text/plain';
76             if ($file =~ /.*\.(\S{1,})$/xms ) {
77             $content_type = $self->mime_types->mimeTypeOf($1)->type;
78             }
79              
80             my $fh = $file->openr;
81             die "Unable to open $file for reading : $!" unless $fh;
82             binmode $fh;
83              
84             my $res = HTTP::Engine::Response->new( body => $fh, content_type => $content_type );
85             my $stat = $file->stat;
86             $res->header( 'Content-Length' => $stat->size );
87             $res->header( 'Last-Modified' => $stat->mtime );
88             $res;
89             };
90              
91              
92             __MIDDLEWARE__
93              
94             __END__
95              
96             =head1 NAME
97              
98             HTTP::Engine::Middleware::Static - handler for static files
99              
100             =head1 SYNOPSIS
101              
102             my $mw = HTTP::Engine::Middleware->new;
103             $mw->install( 'HTTP::Engine::Middleware::Static' => {
104             regexp => qr{^/(robots.txt|favicon.ico|(?:css|js|img)/.+)$},
105             docroot => '/path/to/htdocs/',
106             });
107             HTTP::Engine->new(
108             interface => {
109             module => 'YourFavoriteInterfaceHere',
110             request_handler => $mw->handler( \&handler ),
111             }
112             )->run();
113              
114             # $ GET http//localhost/css/foo.css
115             # to get the /path/to/htdocs/css/foo.css
116              
117             # $ GET http//localhost/js/jquery.js
118             # to get the /path/to/htdocs/js/jquery.js
119              
120             # $ GET http//localhost/robots.txt
121             # to get the /path/to/htdocs/robots.txt
122              
123             has multi document root
124              
125             my $mw = HTTP::Engine::Middleware->new;
126             $mw->install(
127             'HTTP::Engine::Middleware::Static' => {
128             regexp => qr{^/(robots.txt|favicon.ico|(?:css|js|img)/.+)$},
129             docroot => '/path/to/htdocs/',
130             },
131             'HTTP::Engine::Middleware::Static' => {
132             regexp => qr{^/foo(/.+)$},
133             docroot => '/foo/bar/',
134             },
135             );
136             HTTP::Engine->new(
137             interface => {
138             module => 'YourFavoriteInterfaceHere',
139             request_handler => $mw->handler( \&handler ),
140             }
141             )->run();
142              
143             # $ GET http//localhost/css/foo.css
144             # to get the /path/to/htdocs/css/foo.css
145              
146             # $ GET http//localhost/robots.txt
147             # to get the /path/to/htdocs/robots.txt
148              
149             # $ GET http//localhost/foo/baz.html
150             # to get the /foo/bar/baz.txt
151              
152             through only the specific URL to backend
153              
154             my $mw = HTTP::Engine::Middleware->new;
155             $mw->install( 'HTTP::Engine::Middleware::Static' => {
156             regexp => qr{^/(robots.txt|favicon.ico|(?:css|img)/.+|js/(?!dynamic).+)$},
157             docroot => '/path/to/htdocs/',
158             });
159             HTTP::Engine->new(
160             interface => {
161             module => 'YourFavoriteInterfaceHere',
162             request_handler => $mw->handler( \&handler ),
163             }
164             )->run();
165              
166             # $ GET http//localhost/js/jquery.js
167             # to get the /path/to/htdocs/js/jquery.js
168              
169             # $ GET http//localhost/js/dynamic-json.js
170             # to get the your application response
171              
172             Will you want to set config from yaml?
173              
174             my $mw = HTTP::Engine::Middleware->new;
175             $mw->install( 'HTTP::Engine::Middleware::Static' => {
176             regexp => '^/(robots.txt|favicon.ico|(?:css|img)/.+|js/(?!dynamic).+)$',
177             docroot => '/path/to/htdocs/',
178             });
179             HTTP::Engine->new(
180             interface => {
181             module => 'YourFavoriteInterfaceHere',
182             request_handler => $mw->handler( \&handler ),
183             }
184             )->run();
185              
186             # $ GET http//localhost/js/jquery.js
187             # to get the /path/to/htdocs/js/jquery.js
188              
189             # $ GET http//localhost/js/dynamic-json.js
190             # to get the your application response
191              
192             =head1 DESCRIPTION
193              
194             On development site, you would feed some static contents from Interface::ServerSimple, or other stuff.
195             This module helps that.
196              
197             =head1 AUTHORS
198              
199             Kazuhiro Osawa
200              
201             =cut