You are viewing a plain text version of this content. The canonical link for it is here.
Posted to axkit-dev@xml.apache.org by ma...@sergeant.org on 2006/08/23 18:55:21 UTC

[SVN] [111] More AIO fixes and updates

Revision: 111
Author:   matt
Date:     2006-08-23 16:55:00 +0000 (Wed, 23 Aug 2006)

Log Message:
-----------
More AIO fixes and updates
Make keep-alive work
Added shutdown to console

Modified Paths:
--------------
    trunk/lib/AxKit2/Client.pm
    trunk/lib/AxKit2/Connection.pm
    trunk/lib/AxKit2/Console.pm
    trunk/lib/AxKit2/Plugin.pm
    trunk/plugins/aio/uri_to_file
    trunk/plugins/uri_to_file

Added Paths:
-----------
    trunk/plugins/aio/serve_file

Modified: trunk/lib/AxKit2/Client.pm
===================================================================
--- trunk/lib/AxKit2/Client.pm	2006-08-22 22:22:18 UTC (rev 110)
+++ trunk/lib/AxKit2/Client.pm	2006-08-23 16:55:00 UTC (rev 111)
@@ -102,10 +102,8 @@
     my @hooks;
   MAINLOOP:
     for my $plugin ($conf->plugins) {
-        my $plug = plugin_instance($plugin) || next;
-        for my $h ($plug->hooks($hook)) {
-            push @hooks, [$plugin, $plug, $h];
-        }
+        my $plug = $PLUGINS{$plugin} || next;
+        push @hooks, map { [$plugin, $plug, $_] } $plug->hooks($hook);
     }
     
     $self->_run_hooks($hook, [@_], \@hooks);
@@ -153,6 +151,7 @@
             return $meth->($self, $r[0], $r[1], @$args);
         }
     }
+    return @r;
 }
 
 sub log {
@@ -212,6 +211,23 @@
     }
 }
 
+sub hook_write_body_data {
+    my $self = shift;
+    my ($ret) = $self->run_hooks('write_body_data');
+    if ($ret == CONTINUATION) {
+        die "Continuations not supported on write_body_data";
+    }
+    elsif ($ret == DECLINED) {
+        return;
+    }
+    elsif ($ret == OK || $ret == DONE) {
+        return 1;
+    }
+    else {
+        $self->default_error_out($ret);
+    }
+}
+
 sub hook_post_read_request {
     my $self = shift;
     $self->run_hooks('post_read_request', @_);
@@ -329,6 +345,7 @@
     }
     elsif ($ret == OK) {
         $out->output($self) if $out;
+        $self->write(sub { $self->http_response_sent() });
     }
     else {
         $self->default_error_out($ret);
@@ -389,16 +406,19 @@
 # stolen shamelessly from httpd-2.2.2/modules/http/http_protocol.c
 sub default_error_out {
     my ($self, $code, $extras) = @_;
-    
+
     $self->headers_out->code($code);
-    $self->headers_out->header('Content-Type', 'text/html');
-    $self->send_http_headers;
     
     if ($code == NOT_MODIFIED) {
+        $self->send_http_headers;
         # The 304 response MUST NOT contain a message-body
         return;
     }
     
+    $self->headers_out->header('Content-Type', 'text/html');
+    $self->headers_out->header('Connection', 'close');
+    $self->send_http_headers;
+    
     $self->write("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" .
                  "<HTML><HEAD>\n" .
                  "<TITLE>$code ".$self->headers_out->http_code_english."</TITLE>\n" .

Modified: trunk/lib/AxKit2/Connection.pm
===================================================================
--- trunk/lib/AxKit2/Connection.pm	2006-08-22 22:22:18 UTC (rev 110)
+++ trunk/lib/AxKit2/Connection.pm	2006-08-23 16:55:00 UTC (rev 111)
@@ -36,8 +36,10 @@
     sock_closed
     pause_count
     continuation
+    keep_alive_count
     );
 
+use constant KEEP_ALIVE_MAX => 100;
 use constant CLEANUP_TIME => 5; # every N seconds
 use constant MAX_HTTP_HEADER_LENGTH => 102400; # 100k
 
@@ -56,6 +58,7 @@
     $self->{closed} = 0;
     $self->{ditch_leading_rn} = 0; # TODO - work out how to set that...
     $self->{server_config} = $servconf;
+    $self->{keep_alive_count} = 0;
     $self->{notes} = {};
     
     $self->log(LOGINFO, "Connection from " . $self->peer_addr_string);
@@ -112,7 +115,7 @@
 sub max_connect_time    { 180 }
 sub event_err { my AxKit2::Connection $self = shift; $self->close("Error") }
 sub event_hup { my AxKit2::Connection $self = shift; $self->close("Disconnect (HUP)") }
-sub close     { my AxKit2::Connection $self = shift; $self->{sock_closed}++; $self->SUPER::close(@_) }
+sub close     { my AxKit2::Connection $self = shift; $self->{sock_closed}++; $self->{notes} = undef; $self->SUPER::close(@_) }
 
 sub event_read {
     my AxKit2::Connection $self = shift;
@@ -171,6 +174,22 @@
     $self->hook_post_read_request($self->{headers_in});
 }
 
+sub event_write {
+    my AxKit2::Connection $self = shift;
+    $self->{alive_time} = time;
+    
+    if ($self->hook_write_body_data) {
+        return;
+    }
+    
+    # if hook_write_body_data didn't want to send anything, we just pump
+    # whatever's in the queue to go out.
+    if ($self->write(undef)) {
+        # Everything sent. No need to watch for write notifications any more.
+        $self->watch_write(0);
+    }
+}
+
 sub headers_out {
     my AxKit2::Connection $self = shift;
     @_ and $self->{headers_out} = shift;
@@ -201,6 +220,19 @@
     $self->{headers_out} = AxKit2::HTTPHeaders->new_response;
     $self->{headers_out}->header(Date   => http_date());
     $self->{headers_out}->header(Server => "AxKit-2/v$AxKit2::VERSION");
+    if ($hd->header('Connection') =~ /\bkeep-alive\b/i) {
+        # client asked for keep alive. Do we?
+        $self->{keep_alive_count}++;
+        if ($self->{keep_alive_count} > KEEP_ALIVE_MAX) {
+            $self->{headers_out}->header(Connection => 'close');
+        }
+        else {
+            $self->{headers_out}->header(Connection => 'Keep-Alive');
+            $self->{headers_out}->header('Keep-Alive' => 
+                "timeout=" . $self->max_idle_time . 
+                ", max=" .  (KEEP_ALIVE_MAX - $self->{keep_alive_count}));
+        }
+    }
     
     # This starts off the chain reaction of the main state machine
     $self->hook_uri_translation($hd, $hd->request_uri);
@@ -212,6 +244,8 @@
 sub http_response_sent {
     my AxKit2::Connection $self = $_[0];
     
+    $self->log(LOGDEBUG, "Response sent");
+    
     return 0 if $self->{sock_closed};
     
     # close if we're supposed to
@@ -240,7 +274,7 @@
     # now since we're doing persistence, uncork so the last packet goes.
     # we will recork when we're processing a new request.
     # TODO: Disabled because this seemed mostly relevant to Perlbal...
-    #$self->tcp_cork(0);
+    $self->tcp_cork(0);
 
     # reset state
     $self->{alive_time}            = $self->{create_time} = time;
@@ -248,6 +282,7 @@
     $self->{headers_in}            = undef;
     $self->{headers_out}           = undef;
     $self->{http_headers_sent}     = 0;
+    $self->{notes}                 = {};
     
     # NOTE: because we only speak 1.0 to clients they can't have
     # pipeline in a read that we haven't read yet.

Modified: trunk/lib/AxKit2/Console.pm
===================================================================
--- trunk/lib/AxKit2/Console.pm	2006-08-22 22:22:18 UTC (rev 110)
+++ trunk/lib/AxKit2/Console.pm	2006-08-23 16:55:00 UTC (rev 111)
@@ -314,6 +314,10 @@
     $self->write($output);
 }
 
+sub cmd_shutdown {
+    Danga::Socket->SetPostLoopCallback(sub { 0 });
+}
+
 # Cleanup routine to get rid of timed out sockets
 sub _do_cleanup {
     my $now = time;

Modified: trunk/lib/AxKit2/Plugin.pm
===================================================================
--- trunk/lib/AxKit2/Plugin.pm	2006-08-22 22:22:18 UTC (rev 110)
+++ trunk/lib/AxKit2/Plugin.pm	2006-08-23 16:55:00 UTC (rev 111)
@@ -25,7 +25,7 @@
 # DON'T FORGET - edit "AVAILABLE HOOKS" below.
 our @hooks = qw(
     logging connect pre_request post_read_request body_data uri_translation
-    mime_map access_control authentication authorization  fixup
+    mime_map access_control authentication authorization fixup write_body_data
     xmlresponse response response_sent disconnect error
 );
 our %hooks = map { $_ => 1 } @hooks;

Added: trunk/plugins/aio/serve_file
===================================================================
--- trunk/plugins/aio/serve_file	2006-08-22 22:22:18 UTC (rev 110)
+++ trunk/plugins/aio/serve_file	2006-08-23 16:55:00 UTC (rev 111)
@@ -0,0 +1,181 @@
+#!/usr/bin/perl -w
+
+# Copyright 2001-2006 The Apache Software Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+=head1 NAME
+
+serve_file - Plugin for serving raw files
+
+=head1 SYNOPSIS
+
+  Plugin serve_file
+
+=head1 DESCRIPTION
+
+This plugin turns AxKit2 into a normal every-day httpd. Yay!
+
+Most httpds need to serve plain files. Things like favicon.ico and robots.txt
+that any sane web server would be lost without. So just load this plugin after
+all the others, and if your other plugins DECLINE to deliver the content, this
+kind little plugin will happily deliver your file without making any changes
+to it whatsoever. Ain't that nice?
+
+=head1 CONFIG
+
+None.
+
+=cut
+
+use AxKit2::Utils qw(http_date);
+
+sub register {
+    my $self = shift;
+    $self->register_hook('response' => 'hook_response1');
+    $self->register_hook('response' => 'hook_response2');
+}
+
+sub hook_response1 {
+    my ($self, $hd) = @_;
+    
+    my $ct = $hd->mime_type;
+    
+    # set default return value
+    $self->client->notes('serve_file_retcode', DECLINED);
+    
+    my $client = $self->client;
+    
+    if ($hd->request_method eq 'GET' || $hd->request_method eq 'HEAD') {
+        # and once we have it, start serving
+        $self->client->watch_read(0);
+        
+        my $file = $hd->filename;
+        $self->log(LOGINFO, "Serving file: $file");
+        
+        IO::AIO::aio_stat($file, sub {
+            #print "STAT returned\n";
+            if (!-e _) {
+                $client->notes('serve_file_retcode', NOT_FOUND);
+                return $client->finish_continuation;
+            }
+            
+            # we only serve files here...
+            if (!-f _) {
+                $client->notes('serve_file_retcode', BAD_REQUEST);
+                return $client->finish_continuation;
+            }
+            
+            my $mtime = http_date((stat(_))[9]);
+            my $ifmod = $client->headers_in->header('If-Modified-Since') || "";
+            
+            my $ifmod_len = 0;
+            if ($ifmod =~ s/; length=(\d+)//) {
+                $ifmod_len = $1;
+            }
+            
+            my $modified = $ifmod ? ($ifmod ne $mtime) : 1;
+            
+            my $size = -s _;
+            
+            $modified++ if $ifmod_len && $ifmod_len != $size;
+            
+            if (!$modified) {
+                $client->notes('serve_file_retcode', NOT_MODIFIED);
+                return $client->finish_continuation;
+            }
+            
+            $client->headers_out->header("Last-Modified", $mtime);
+            $client->headers_out->header("Content-Length", $size);
+            $client->headers_out->header("Content-Type", $ct);
+            
+            $client->send_http_headers;
+            
+            $client->notes('serve_file_retcode', OK);
+            
+            if ($hd->request_method eq 'HEAD') {
+                return $client->finish_continuation;
+            }
+            
+            IO::AIO::aio_open($file, 0, 0, sub {
+                #print "OPEN returned\n";
+                my $fh = shift;
+                
+                if ($client->{closed}) {
+                    return CORE::close($fh);
+                }
+                
+                if (!$fh) {
+                    $client->notes('serve_file_retcode', SERVER_ERROR);
+                    return $client->close('aio_open_failure');
+                }
+                
+                $client->notes('serve_file_bytes_remaining', $size);
+                
+                $client->watch_write(1);
+                
+                my $send_sub = sub {
+                    my $remaining = $client->notes('serve_file_bytes_remaining');
+                    #print "sending $remaining bytes...\n";
+                    if ($remaining <= 0) {
+                        CORE::close($fh);
+                        $client->watch_write(0);
+                        return $client->finish_continuation;
+                    }
+                    IO::AIO::aio_sendfile($client->sock, $fh, 
+                                         ($size - $remaining), $remaining,
+                                         sub {
+                                            my $sent = shift;
+                                            return unless $sent >= 0;
+                                            my $r = $client->notes('serve_file_bytes_remaining');
+                                            $r -= $sent;
+                                            $client->notes('serve_file_bytes_remaining', $r);
+                                            $client->notes('serve_file_ready_for_more', 1);
+                                         });
+                    $client->notes('serve_file_ready_for_more', 0);
+                };
+                
+                $client->notes('serve_file_ready_for_more', 1);
+                $client->notes('serve_file_send_sub', $send_sub);
+            });
+            
+            return; # we're not done until aio_open is done...
+        });
+        
+        return CONTINUATION;
+    }
+            
+    return DECLINED;
+}
+
+sub hook_response2 {
+    my $self = shift;
+    return $self->client->notes('serve_file_retcode');
+}
+
+sub hook_write_body_data {
+    my $self = shift;
+    return OK unless $self->client->notes('serve_file_ready_for_more');
+    my $sub = $self->client->notes('serve_file_send_sub');
+    $sub->();
+    if ($self->client->notes('serve_file_bytes_remaining')) {
+        return OK;
+    }
+    else {
+        $self->client->watch_write(0);
+        # close the circular reference...
+        $self->client->notes('serve_file_send_sub', undef);
+        return DONE;
+    }
+}

Modified: trunk/plugins/aio/uri_to_file
===================================================================
--- trunk/plugins/aio/uri_to_file	2006-08-22 22:22:18 UTC (rev 110)
+++ trunk/plugins/aio/uri_to_file	2006-08-23 16:55:00 UTC (rev 111)
@@ -77,6 +77,10 @@
     
     $uri = uri_decode($uri);
     
+    if ($uri =~ /\.\./) {
+        return BAD_REQUEST;
+    }
+    
     my $root = $self->config->path;
     
     $uri =~ s/^\Q$root// || die "$uri did not match config path $root";
@@ -100,7 +104,6 @@
         $self->log(LOGINFO, "aio_stat($path) returned ($_[0])");
         if ($_[0]) {
             # error (usually file didn't exist).
-            print "error\n";
             while ($path =~ /\// && !-f $path) {
                 $path =~ s/(\/[^\/]*)$//;
                 $path_info = $1 . $path_info;
@@ -116,7 +119,6 @@
             }
         }
         elsif (-d _) {
-            print "dir\n";
             # URI didn't end in a slash - need to redirect
             if ($original_uri !~ /\/$/) {
                 $self->log(LOGINFO, "redirecting to $original_uri/$removed");
@@ -130,15 +132,13 @@
             }
         }
         elsif (-f _) {
-            print "file\n";
-            return;
+            return $client->finish_continuation;
         }
         else {
             # neither a dir nor a file
             die "Unknown entity type: $path";
         }
         
-        print "Setting filename to $path\n";
         $hd->filename($path);
         
         $client->finish_continuation;
@@ -150,7 +150,6 @@
 # This allows us to return REDIRECT above
 sub hook_uri_translation2 {
     my $self = shift;
-    
-    print "Continuation returns!\n";
+    $self->log(LOGDEBUG, "uri_to_file continuation finished");
     return $self->client->notes('uri_to_file_retcode');
 }

Modified: trunk/plugins/uri_to_file
===================================================================
--- trunk/plugins/uri_to_file	2006-08-22 22:22:18 UTC (rev 110)
+++ trunk/plugins/uri_to_file	2006-08-23 16:55:00 UTC (rev 111)
@@ -63,7 +63,6 @@
     
     $self->log(LOGINFO, "translate: $uri");
     
-    
     $uri =~ s/(\?.*)//;
     my $removed = $1 || '';
     
@@ -71,6 +70,10 @@
     
     $uri = uri_decode($uri);
     
+    if ($uri =~ /\.\./) {
+        return BAD_REQUEST;
+    }
+    
     my $root = $self->config->path;
     
     $uri =~ s/^\Q$root// || die "$uri did not match config path $root";