diff -arubN POE-Component-FastCGI-0.20-orig/lib/POE/Component/FastCGI/Response.pm POE-Component-FastCGI-0.20/lib/POE/Component/FastCGI/Response.pm --- POE-Component-FastCGI-0.20-orig/lib/POE/Component/FastCGI/Response.pm 2019-11-04 17:46:19.000000000 -0500 +++ POE-Component-FastCGI-0.20/lib/POE/Component/FastCGI/Response.pm 2024-02-22 15:34:06.601913649 -0500 @@ -5,6 +5,7 @@ use bytes; use POE::Kernel; +use Carp; sub new { my($class, $client, $id, $code, @response) = @_; @@ -81,6 +82,11 @@ push @headers, $status_line; push @headers, $self->headers_as_string("\x0D\x0A"); + unless( $self->{client} ) { + # we need better error handling here + carp "Sending mulitple responses"; + return; + } my $filter = $self->{client}->get_input_filter(); my $keepconn = $filter->{conn}->[$filter->{requestid}]->{keepconn}; diff -arubN POE-Component-FastCGI-0.20-orig/lib/POE/Component/FastCGI.pm POE-Component-FastCGI-0.20/lib/POE/Component/FastCGI.pm --- POE-Component-FastCGI-0.20-orig/lib/POE/Component/FastCGI.pm 2019-11-04 17:46:19.000000000 -0500 +++ POE-Component-FastCGI-0.20/lib/POE/Component/FastCGI.pm 2024-02-22 15:56:25.589503264 -0500 @@ -14,6 +14,8 @@ use POE::Component::FastCGI::Request; use POE::Component::FastCGI::Response; +sub DEBUG () { 0 } + sub new { my($class, %args) = @_; @@ -28,7 +30,8 @@ _start => \&_start, accept => \&_accept, input => \&_input, - error => \&_error, + client_error => \&_client_error, + server_error => \&_server_error, client_shutdown => \&_client_shutdown, # For graceful external shutdown @@ -57,9 +60,8 @@ Port => $heap->{Port}, (defined $heap->{Unix} ? (Domain => AF_UNIX) : ()), (defined $heap->{Address} ? (Address => $heap->{Address}) : ()), - Acceptor => sub { - $poe_kernel->post($session => accept => @_[ARG0, ARG1, ARG2]); - } + Error => sub { $poe_kernel->post( $session => server_error => @_[ARG0..$#_] ) }, + Acceptor => sub { $poe_kernel->post( $session => accept => @_[ARG0..$#_] ) } ); } @@ -68,12 +70,13 @@ # XXX: check fastcgi is allowed to connect. + DEBUG and warn "FastCGI: accept\n"; my $wheel = POE::Wheel::ReadWrite->new( Handle => $socket, Driver => POE::Driver::SysRW->new(), Filter => POE::Filter::FastCGI->new(), InputEvent => 'input', - ErrorEvent => 'error' + ErrorEvent => 'client_error' ); $heap->{wheels}->{$wheel->ID} = $wheel; } @@ -101,9 +104,12 @@ my $path = $request->uri->path; + DEBUG and warn "FastCGI $fcgi->[0]: path=$path\n"; + my $run; for my $handler(@{$heap->{Handlers}}) { + DEBUG and warn "FastCGI $fcgi->[0]: Match $handler->[0]"; if(ref $handler->[0] eq 'Regexp') { $run = $handler, last if $path =~ /$handler->[0]/; }else{ @@ -114,16 +120,25 @@ } if(not defined $run) { - $request->error(404, "No handler found for $path"); + DEBUG and warn "FastCGI $fcgi->[0]: No handler found\n"; + $request->error(404, "No handler found for path '$path'"); }else{ - if(ref($run->[1]) eq 'CODE' or $run->[1]->isa('POE::Session::AnonEvent') ) { + if(ref($run->[1]) eq 'ARRAY' ) { + DEBUG and warn "FastCGI $fcgi->[0]: Running POE handler $run->[1][0]/$run->[1][1]\n"; + $kernel->post($run->[1][0], $run->[1][1], $request, $run->[0]); + } + elsif(ref($run->[1]) eq 'CODE' or $run->[1]->isa('POE::Session::AnonEvent') ) { + DEBUG and warn "FastCGI $fcgi->[0]: Running code handler\n"; $run->[1]->($request, $run->[0]); - } else { + } + else { + DEBUG and warn "FastCGI $fcgi->[0]: Running POE handler $heap->{Session}/$run->[1]\n"; $kernel->post($heap->{Session}, $run->[1],$request, $run->[0]); } if($request->{_res}) { + DEBUG and warn "FastCGI $fcgi->[0]: Streaming\n"; # Streaming support if($request->{_res}->streaming) { push @{$heap->{toclose}->{$wheel_id}}, $request->{_res}; @@ -136,7 +151,17 @@ } } -sub _error { +sub _server_error { + my($heap, $op, $errnum, $errstr, $wheel_id) = @_[HEAP, ARG0..$#_]; + my $addr = (defined $heap->{Address} ? $heap->{Address} + : (defined $heap->{Unix} ? "" : "0.0.0.0" )); + $addr .= ":$heap->{Port}" if $heap->{Port}; + warn "Error on $op '$addr' $errstr [$errnum]"; + + undef; +} + +sub _client_error { my($heap, $wheel_id) = @_[HEAP, ARG3]; if(exists $heap->{toclose}->{$wheel_id}) { for(@{$heap->{toclose}->{$wheel_id}}) { @@ -170,6 +195,8 @@ # these are here to help PoCo::FastCGI::Response # to deal with it's wheel from the right session sub _w_send { +# use Carp; +# warn Carp::longmess( "w_send" ); my($resp) = $_[ARG0]; $resp->_send(); } @@ -228,14 +255,19 @@ Session => 'MAIN', ); - sub default { - my($request) = @_; - + POE::Session->create( + inline_states => { + _start => sub { $poe_kernel->alias_set( 'MAIN' ) }, + poe_event_name => sub { + my($request, $path) = @_[ ARG0..$#_ ]; my $response = $request->make_response; $response->header("Content-type" => "text/html"); $response->content("A page"); $response->send; } + } ); + + $poe_kernel->run; =head1 DESCRIPTION @@ -248,43 +280,78 @@ Creates a new POE session for the FastCGI server and listens on the specified port. -Parameters - Auth (optional) - A code reference to run when called as a FastCGI authorizer. - Handlers (required) - A array reference with a mapping of paths to code references or POE event names. - Port (required unless Unix is set) - Port number to listen on. - Address (requied if Unix is set) - Address to listen on. - Unix (optional) - Listen on UNIX socket given in Address. - Session (required if you want to get POE callbacks) - Into which session we should post the POE event back. - -The call returns a POE session ID. This should be stored, and when application is to be terminated, a 'shutdown' event can be posted to this session. This will terminate the server socket and free resources. - -The handlers parameter should be a list of lists defining either regexps of -paths to match or absolute paths to code references. - -The code references will be passed one parameter, a -L object. To send a response -the C method should be called which returns a -L object. These objects -are subclasses of L and L -respectively. +Parameters: + +=over 4 + +=item Auth (optional) -Example: +A code reference to run when called as a FastCGI authorizer. + +=item Handlers (required) + +A array reference with a mapping of paths to code references or POE event names. + +=item Port (required unless Unix is set) + +Port number to listen on. + +=item Address (requied if Unix is set) + +Address to listen on. + +=item Unix (optional) + +Listen on UNIX socket given in Address. + +=item Session (required if you want to get POE callbacks) + +Into which session we should post the POE event back. + +=back 4 + +The call returns a POE session ID. This should be stored, and when +application is to be terminated, a 'shutdown' event can be posted to this +session. This will terminate the server socket and free resources. + +The Handlers parameter should be a list of lists. The first value of the +each list is either a regexp of paths to match or an absolute path prefix. +The 2nd value may be a code reference, a string, or list. The coderef is +called as described below. A string is an event name that will be posted in +the L session. The first value of the be used as the POE session +and the 2nd as event name. + +The handlers are passed two parameters, a +L object (a subclass of L) +and the string or regex that matched the request path. + +To send a response the Cmake_response> +method should be called which returns a L +object, a subclass of L. + +Examples: + + Session => 'thingy', Handlers => [ + # string to coderef [ '/page' => \&page ], + # regex to inline coderef [ qr!^/(\w+)\.html$! => sub { - my $request = shift; + my( $request, $re ) = @_; my $response = $request->make_response; output_template($request, $response, $1); } ], + # Event thingy/amp will be posted + [ qr(^/amp/(\w+)$) => 'amp' ], + # Event other/thing will be posted + [ '/other/thing' => [ 'other', 'thing' ], ] +Note that the handlers are checked in order. If multiple handlers could +match a give request path, then the first one in the list is used and all +others ignored. + =back =head1 USING FASTCGI @@ -292,7 +359,7 @@ Many webservers have support for FastCGI. PoCo::FastCGI has been tested on Mac OSX and Linux using lighttpd. -Currently you must run the PoCo::FastCGI script separately to the +Currently you must run the PoCo::FastCGI script separately from the webserver and then instruct the webserver to connect to it. Lighttpd configuration example (assuming listening on port 1026): diff -arubN POE-Component-FastCGI-0.20-orig/lib/POE/Filter/FastCGI.pm POE-Component-FastCGI-0.20/lib/POE/Filter/FastCGI.pm --- POE-Component-FastCGI-0.20-orig/lib/POE/Filter/FastCGI.pm 2019-11-04 17:46:19.000000000 -0500 +++ POE-Component-FastCGI-0.20/lib/POE/Filter/FastCGI.pm 2024-02-22 15:57:24.405342439 -0500 @@ -44,6 +44,8 @@ constant->import($_ => $c++) for @ROLE[1 .. $#ROLE]; } +sub DEBUG () { 0 } + sub new { my($class) = @_; my $self = bless { @@ -82,6 +84,7 @@ @$self{qw/version type requestid contentlen padlen/} = unpack "CCnnC", $header; + DEBUG and warn "FastCGI: version=$self->{version} type=$self->{type} requestid=$self->{requestid} contentlen=$self->{contentlen} padlen=$self->{padlen}\n"; warn "Wrong version, or direct request from a browser" if $self->{version} != FCGI_VERSION_1; @@ -145,6 +148,7 @@ defined($vlen = _read_nv_len(\$content, \$offset))) { my($name, $value) = (substr($content, $offset, $nlen), substr($content, $offset + $nlen, $vlen)); + DEBUG and warn "FastCGI $self->{requestid}: $name=$value\n"; $conn->{cgi}->{$name} = $value; $offset += $nlen + $vlen; } diff -arubN POE-Component-FastCGI-0.20-orig/t/fastcgi.t POE-Component-FastCGI-0.20/t/fastcgi.t --- POE-Component-FastCGI-0.20-orig/t/fastcgi.t 2019-11-04 17:46:19.000000000 -0500 +++ POE-Component-FastCGI-0.20/t/fastcgi.t 2024-02-22 16:00:11.190051384 -0500 @@ -1,7 +1,18 @@ +#!/usr/bin/env perl +use strict; +use warnings; + use Test::Simple tests => $ENV{FULL_TEST} ? 4 : 2; use IO::Socket; + +sub diag { print STDERR "# @_\n"; } + +sub DEBUG () { 0 } + + # Will probably fail on windows.. -my $address = "/tmp/pocofcgi.test"; +my $address = "/tmp/pocofcgi.test-$$"; +END { unlink $address } use POE::Component::FastCGI; ok(1); @@ -15,18 +26,23 @@ Handlers => [ [ '/' => sub { my $request = shift; + DEBUG and diag( "Coderef handler" ); $request->make_response->error(404, "Bananas"); } ] ] )); -goto END unless $ENV{FULL_TEST}; +unless( $ENV{FULL_TEST} ) { + diag( "Set FULL_TEST=1 to run this test" ); + goto END; +} my $pid = fork(); die $! unless defined $pid; POE::Kernel->run unless $pid; +diag( "sleep 2" ); sleep 2; ok(my $sock = IO::Socket::UNIX->new($address)); @@ -34,16 +50,16 @@ die $! unless $sock; # A FastCGI request.. -print $sock '”SERVER_SOFTWARElighttpd/1.3.13 SERVER_NAME0.0.0.0GATEWAY_INTERFACECGI/1.1 SERVER_PORT8080 SERVER_ADDR127.0.0.1 REMOTE_PORT57158 REMOTE_ADDR127.0.0.1 SCRIPT_NAME/ PATH_INFOSCRIPT_FILENAME// -DOCUMENT_ROOT/ REQUEST_URI/ QUERY_STRINGREQUEST_METHODGETREDIRECT_STATUS200SERVER_PROTOCOLHTTP/1.0 -HTTP_USER_AGENTWget/1.8.2 HTTP_HOSTlocalhost:8080 HTTP_ACCEPT*/* -HTTP_CONNECTIONKeep-Alive'; +print $sock "\1\1\0\1\0\b\0\0\0\1\0\0\0\0\0\0\1\4\0\1\1\x94\0\0\17\17SERVER_SOFTWARElighttpd/1.3.13\13\aSERVER_NAME0.0.0.0\21\aGATEWAY_INTERFACECGI/1.1\13\4SERVER_PORT8080\13\tSERVER_ADDR127.0.0.1\13\5REMOTE_PORT57158\13\tREMOTE_ADDR127.0.0.1\13\1SCRIPT_NAME/\t\0PATH_INFO\17\2SCRIPT_FILENAME//\r\1DOCUMENT_ROOT/\13\1REQUEST_URI/\f\0QUERY_STRING\16\3REQUEST_METHODGET\17\3REDIRECT_STATUS200\17\bSERVER_PROTOCOLHTTP/1.0\17\nHTTP_USER_AGENTWget/1.8.2\t\16HTTP_HOSTlocalhost:8080\13\3HTTP_ACCEPT*/*\17\17HTTP_CONNECTIONKeep-Alive\1\4\0\1\0\0\0\0\1\5\0\1\0\0\0\0"; + +my $i; +read($sock, $i, 1024); +ok($i =~ /Bananas/) or die $i; -read($sock, $i, 60); -ok($i =~ /Bananas/); +#use Data::Dump qw( ddx ); +#ddx $i; # Stop forked copy running POE kill(15, $pid); END: -unlink "/tmp/pocofcgi.test"; diff -arubN POE-Component-FastCGI-0.20-orig/t/poe.t POE-Component-FastCGI-0.20/t/poe.t --- POE-Component-FastCGI-0.20-orig/t/poe.t 1969-12-31 19:00:00.000000000 -0500 +++ POE-Component-FastCGI-0.20/t/poe.t 2024-02-22 16:00:21.460848758 -0500 @@ -0,0 +1,105 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::Simple tests => $ENV{FULL_TEST} ? 6 : 2; +use IO::Socket; + +sub diag { print STDERR "# @_\n"; } + + +sub DEBUG () { 0 } + +# Will probably fail on windows.. +my $address = "/tmp/pocofcgi.test-$$"; +END { unlink $address } + +use POE::Component::FastCGI; +use POE; +ok(1); + +# silence the warning +POE::Kernel->run(); + +my $SID = POE::Session->create( + inline_states => { + _start => sub { + DEBUG and diag( "_start" ); + $poe_kernel->alias_set( 'testing' ); + }, + _stop => sub { diag( "_stop" ) }, + default => sub { + my( $request, $path ) = @_[ ARG0 .. $#_ ]; + DEBUG and diag( "default handler" ); + my $resp = $request->make_response; + $resp->error(404, "Default"); + }, + bananas => sub { + my( $request, $path ) = @_[ ARG0 .. $#_ ]; + DEBUG and diag( "bananas handler" ); + my $resp = $request->make_response; + $resp->code( 200 ); + $resp->message( "OK" ); + $resp->header( 'Content-Type', 'text/plain' ); + $resp->content("Bananas"); + $resp->send; + } + } )->ID; + +DEBUG and diag( "Session $SID" ); + +ok(POE::Component::FastCGI->new( + Address => $address, + Unix => 1, + Session => $SID, + Handlers => [ + [ '/' => 'default' ], + [ '/bananas' => [ 'testing', 'bananas'] ] + ] +)); + +unless( $ENV{FULL_TEST} ) { + diag( "Set FULL_TEST=1 to run this test" ); + goto END; +} + + +my $pid = fork(); +die $! unless defined $pid; + +POE::Kernel->run unless $pid; + +# parent + +diag( "sleep 2" ); +sleep 2; + +ok(my $sock = IO::Socket::UNIX->new($address)); + +die $! unless $sock; + +my $i; +# A FastCGI request... +my $bananas = "\1\1\0\1\0\b\0\0\0\1\0\0\0\0\0\0\1\4\0\1\1\x9B\0\0\17\17SERVER_SOFTWARElighttpd/1.3.13\13\aSERVER_NAME0.0.0.0\21\aGATEWAY_INTERFACECGI/1.1\13\4SERVER_PORT8080\13\tSERVER_ADDR127.0.0.1\13\5REMOTE_PORT57158\13\tREMOTE_ADDR127.0.0.1\13\1SCRIPT_NAME/\t\0PATH_INFO\17\2SCRIPT_FILENAME//\r\1DOCUMENT_ROOT/\13\x08REQUEST_URI/bananas\f\0QUERY_STRING\16\3REQUEST_METHODGET\17\3REDIRECT_STATUS200\17\bSERVER_PROTOCOLHTTP/1.0\17\nHTTP_USER_AGENTWget/1.8.2\t\16HTTP_HOSTlocalhost:8080\13\3HTTP_ACCEPT*/*\17\17HTTP_CONNECTIONKeep-Alive\1\4\0\1\0\0\0\0\1\5\0\1\0\0\0\0"; + +print $sock $bananas; +read($sock, $i, 1024); +ok($i =~ /Bananas/) or die $i; + +undef( $sock ); +diag( "sleep 2" ); +sleep 2; +ok( $sock = IO::Socket::UNIX->new($address)); + +my $req = "\1\1\0\1\0\b\0\0\0\1\0\0\0\0\0\0\1\4\0\1\1\x94\0\0\17\17SERVER_SOFTWARElighttpd/1.3.13\13\aSERVER_NAME0.0.0.0\21\aGATEWAY_INTERFACECGI/1.1\13\4SERVER_PORT8080\13\tSERVER_ADDR127.0.0.1\13\5REMOTE_PORT57158\13\tREMOTE_ADDR127.0.0.1\13\1SCRIPT_NAME/\t\0PATH_INFO\17\2SCRIPT_FILENAME//\r\1DOCUMENT_ROOT/\13\1REQUEST_URI/\f\0QUERY_STRING\16\3REQUEST_METHODGET\17\3REDIRECT_STATUS200\17\bSERVER_PROTOCOLHTTP/1.0\17\nHTTP_USER_AGENTWget/1.8.2\t\16HTTP_HOSTlocalhost:8080\13\3HTTP_ACCEPT*/*\17\17HTTP_CONNECTIONKeep-Alive\1\4\0\1\0\0\0\0\1\5\0\1\0\0\0\0"; + +print $sock $req; +read($sock, $i, 1024); +ok($i =~ /Default/) or die $i; + + +# Stop forked copy running POE +kill(15, $pid); + +END: