[Catalyst-commits] r7066 - in trunk/Catalyst-Engine-HTTP-Sprocket:
. lib/Catalyst/Engine/HTTP lib/Catalyst/Engine/HTTP/Sprocket
andyg at dev.catalyst.perl.org
andyg at dev.catalyst.perl.org
Fri Oct 26 05:13:10 GMT 2007
Author: andyg
Date: 2007-10-26 05:13:09 +0100 (Fri, 26 Oct 2007)
New Revision: 7066
Modified:
trunk/Catalyst-Engine-HTTP-Sprocket/Makefile.PL
trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket.pm
trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket/Server.pm
Log:
Serve static files using AIO if available
Modified: trunk/Catalyst-Engine-HTTP-Sprocket/Makefile.PL
===================================================================
--- trunk/Catalyst-Engine-HTTP-Sprocket/Makefile.PL 2007-10-26 01:32:55 UTC (rev 7065)
+++ trunk/Catalyst-Engine-HTTP-Sprocket/Makefile.PL 2007-10-26 04:13:09 UTC (rev 7066)
@@ -4,8 +4,11 @@
all_from 'lib/Catalyst/Engine/HTTP/Sprocket.pm';
requires 'Catalyst::Runtime';
+requires 'MIME::Types';
requires 'Sprocket';
+recommends 'IO::AIO';
+
tests 't/0*.t';
auto_install;
Modified: trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket/Server.pm
===================================================================
--- trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket/Server.pm 2007-10-26 01:32:55 UTC (rev 7065)
+++ trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket/Server.pm 2007-10-26 04:13:09 UTC (rev 7066)
@@ -6,12 +6,21 @@
use base qw(Sprocket::Plugin);
use Data::Dump qw(dump);
+use Fcntl;
+use File::Spec::Functions;
+use HTTP::Date;
+use HTTP::Response;
+use HTTP::Status qw(status_message);
+use MIME::Types;
use POE;
use POE::Wheel::Run;
+use Sprocket::AIO;
+use URI::Escape qw(uri_unescape);
use Catalyst::Engine::HTTP::Sprocket::Worker;
-sub DEBUG () { $ENV{CATALYST_POE_DEBUG} || 0 }
+sub DEBUG () { $ENV{CATALYST_POE_DEBUG} || 0 }
+sub HAS_AIO () { Sprocket::AIO::HAS_AIO() }
my $DONE_MAGIC = 'PmlTpdeB3BGukGz8eb99Wg';
my $DONE_REGEX = qr{$DONE_MAGIC$};
@@ -22,6 +31,16 @@
# Number of children to fork
$config->{children} ||= 1;
+ if ( HAS_AIO ) {
+ # Try to serve everything under /static using AIO
+ $config->{aio_static} ||= 1;
+ $config->{aio_static_path} ||= catdir( $FindBin::Bin, '..', 'root', 'static' );
+ }
+ else {
+ warn "You should install IO::AIO for better file serving performance.\n";
+ $config->{aio_static} = 0;
+ }
+
# Magic string children send to notify they are done
# with a request
$config->{child_done} = $DONE_MAGIC;
@@ -29,8 +48,12 @@
my $self = $class->SUPER::new(
name => 'Catalyst Server',
config => $config,
+ mime => MIME::Types->new( only_complete => 1 ),
);
+ # preload the type index hash so it's not built on the first request
+ $self->{mime}->create_type_index;
+
POE::Session->create(
object_states => [
$self => [ qw(
@@ -179,6 +202,16 @@
DEBUG && warn "[$con] local_receive: " . dump($input) . "\n";
+ if ( $self->{config}->{aio_static} && $input =~ m{^GET /static/([^ \?]+)} ) {
+ my $file = $self->{config}->{aio_static_path} . '/' . uri_unescape($1);
+ DEBUG && warn "[$con] directly serving static file $file\n";
+
+ $con->x->{_req} = HTTP::Request->parse( $input );
+
+ aio_stat( $file, $con->callback( 'stat_file', $file ) );
+ return;
+ }
+
# Find a free child to send the request to
for my $wheel_id ( keys %{ $self->{children} } ) {
next if $self->{child_busy}->{ $wheel_id };
@@ -322,4 +355,139 @@
$self->{child_busy}->{ $wheel_id } = 0;
}
+### AIO handler for static files
+
+# This code is mostly borrowed from Sprocket::Plugin::HTTP
+
+sub stat_file {
+ my ( $self, $server, $con, $file ) = @_;
+
+ unless ( -f _ ) {
+ DEBUG && warn "404: $file\n";
+ $con->call( simple_response => 404 );
+ return;
+ }
+
+ my $x = $con->x;
+
+ $x->{_stat} = [ stat(_) ];
+ $x->{_r} = HTTP::Response->new( 200 );
+
+ $x->{_r}->last_modified( time2str( $x->{_stat}->[ 9 ] ) );
+
+ # bail if HEAD request
+ if ( $x->{_req}->method eq 'HEAD' ) {
+ $x->{_r}->content_type( 'text/html' );
+ $x->{_r}->content_length( $x->{_stat}->[ 7 ] );
+ $con->call( 'static_finish' );
+ return;
+ }
+
+ # 304 check
+ if ( my $since = $x->{_req}->header('If-Modified-Since') ) {
+ $since = str2time($since);
+ my $mtime = $x->{_stat}->[ 9 ];
+ if ( $mtime && $since && $since >= $mtime ) {
+ $con->call( simple_response => 304 );
+ return;
+ }
+ }
+
+ aio_open( $file, O_RDONLY, 0, $con->callback( 'opened_file', $file ) );
+}
+
+sub opened_file {
+ my ( $self, $server, $con, $file, $fh ) = @_;
+
+ unless ( $fh ) {
+ $con->call( simple_response => 403 );
+ return;
+ }
+
+ my $out = '';
+ aio_read( $fh, 0, $con->x->{_stat}->[ 7 ], $out, 0, $con->callback( 'send_file', $file, $fh, \$out ) );
+}
+
+sub send_file {
+ my ( $self, $server, $con, $file, $fh, $out, $size_out ) = @_;
+
+ my $r = $con->x->{_r};
+ my $size = $con->x->{_stat}->[ 7 ];
+
+ if ( $size == $size_out ) {
+ $r->content_type( $self->{mime}->mimeTypeOf( $file ) );
+ $con->call( static_finish => $out, $size );
+ }
+ else {
+ $server->_log( v=> 4, msg => "500 [$size != $size_out] [short read:$!] $file" );
+ $r->code( 500 );
+ $r->content_type( 'text/plain' );
+ $con->call( static_finish => 'ERROR: short read' );
+ }
+
+ close $fh if $fh;
+
+ return 1;
+}
+
+sub simple_response {
+ my ( $self, $server, $con, $code ) = @_;
+
+ my $r = $con->x->{_r} ||= HTTP::Response->new();
+ $r->code( $code );
+
+ my $content = status_message($code);
+ $r->content_type( 'text/html' );
+
+ $con->call( static_finish => $content );
+}
+
+sub static_finish {
+ my ( $self, $server, $con, $out, $size ) = @_;
+
+ my $time = time();
+
+ my $x = $con->x;
+ my $r = $x->{_r} ||= HTTP::Response->new( 500 );
+
+ $r->header( Date => time2str($time) );
+ $r->protocol( 'HTTP/1.0' );
+
+ unless ( defined $x->{_close} ) {
+ my $connection = $x->{_req}->header('Connection');
+ if ( $connection && $connection =~ m/^keep-alive$/i ) {
+ $x->{_close} = 0;
+ $r->header( Connection => 'keep-alive' );
+ }
+ else {
+ $x->{_close} = 1;
+ }
+ }
+
+ if ( defined $out ) {
+ if ( ref $out && ref $out eq 'SCALAR' ) {
+ $r->content( $$out );
+ }
+ else {
+ $size ||= length $out;
+ $r->content( $out );
+ }
+
+ $r->header( 'Content-Length' => $size );
+ }
+
+ # XXX: always close for now
+ $x->{_close} = 1;
+
+ if ( $x->{_close} ) {
+ $r->header( Connection => 'close' );
+ $con->send( $r->as_string( "\x0D\x0A" ) );
+ $con->close();
+ }
+ else {
+ # XXX: timeout
+ $con->send( $r->as_string( "\x0D\x0A" ) );
+ }
+}
+
1;
\ No newline at end of file
Modified: trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket.pm
===================================================================
--- trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket.pm 2007-10-26 01:32:55 UTC (rev 7065)
+++ trunk/Catalyst-Engine-HTTP-Sprocket/lib/Catalyst/Engine/HTTP/Sprocket.pm 2007-10-26 04:13:09 UTC (rev 7066)
@@ -37,7 +37,7 @@
addr => $addr,
port => $port,
host => $host,
- options => $options,
+ %{ $options },
};
Sprocket::Server->spawn(
More information about the Catalyst-commits
mailing list