[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