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";