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