1#!/usr/bin/perl -w 2 3use strict; 4use lib 't/lib'; 5 6use Test::More tests => 12; 7use Test::Identity; 8use Test::HexString; 9use Test::Refcount; 10 11use IO::Async::Loop; 12use IO::Async::Test; 13 14use Net::Async::FastCGI::PSGI; 15 16use TestFCGI; 17 18my ( $S, $selfaddr ) = make_server_sock; 19 20my $loop = IO::Async::Loop->new(); 21testing_loop( $loop ); 22 23my $received_env; 24 25my $fcgi = Net::Async::FastCGI::PSGI->new( 26 handle => $S, 27 app => sub { 28 # Simplest PSGI app 29 $received_env = shift; 30 return [ 31 200, 32 [ "Content-Type" => "text/plain" ], 33 [ "Hello, world" ], 34 ]; 35 }, 36); 37 38ok( defined $fcgi, 'defined $fcgi' ); 39 40$loop->add( $fcgi ); 41 42# One ref in the Loop as well as this lexical variable 43is_refcount( $fcgi, 2, '$fcgi has refcount 2 initially' ); 44 45my $C = connect_client_sock( $selfaddr ); 46 47$C->syswrite( 48 # Begin with FCGI_KEEP_CONN 49 fcgi_trans( type => 1, id => 1, data => "\0\1\1\0\0\0\0\0" ) . 50 # Parameters 51 fcgi_trans( type => 4, id => 1, data => 52 fcgi_keyval( REQUEST_METHOD => "GET" ) . 53 fcgi_keyval( SCRIPT_NAME => "" ) . 54 fcgi_keyval( PATH_INFO => "" ) . 55 fcgi_keyval( REQUEST_URI => "/" ) . 56 fcgi_keyval( QUERY_STRING => "" ) . 57 fcgi_keyval( SERVER_NAME => "localhost" ) . 58 fcgi_keyval( SERVER_PORT => "80" ) . 59 fcgi_keyval( SERVER_PROTOCOL => "HTTP/1.1" ) 60 ) . 61 # End of parameters 62 fcgi_trans( type => 4, id => 1, data => "" ) . 63 # End of STDIN 64 fcgi_trans( type => 5, id => 1, data => "" ) 65); 66 67wait_for { defined $received_env }; 68 69# Some keys are awkward, handle them first 70ok( defined(delete $received_env->{'psgi.input'}), "psgi.input exists" ); 71ok( defined(delete $received_env->{'psgi.errors'}), "psgi.errors exists" ); 72 73identical( delete $received_env->{'net.async.fastcgi'}, $fcgi, "net.async.fastcgi is \$fcgi" ); 74can_ok( delete $received_env->{'net.async.fastcgi.req'}, "params" ); 75identical( delete $received_env->{'io.async.loop'}, $loop, "io.async.loop is \$loop" ); 76 77is_deeply( $received_env, 78 { 79 PATH_INFO => "", 80 QUERY_STRING => "", 81 REQUEST_METHOD => "GET", 82 REQUEST_URI => "/", 83 SCRIPT_NAME => "", 84 SERVER_NAME => "localhost", 85 SERVER_PORT => "80", 86 SERVER_PROTOCOL => "HTTP/1.1", 87 88 'psgi.version' => [1,0], 89 'psgi.url_scheme' => "http", 90 'psgi.run_once' => 0, 91 'psgi.multiprocess' => 0, 92 'psgi.multithread' => 0, 93 'psgi.streaming' => 1, 94 'psgi.nonblocking' => 1, 95 }, 96 'received $env in PSGI app' ); 97 98my $CRLF = "\x0d\x0a"; 99my $expect_stdout = join( "", map "$_$CRLF", 100 "Status: 200", 101 "Content-Type: text/plain", 102 '' ) . 103 "Hello, world"; 104 105my $expect; 106 107$expect = 108 # STDOUT 109 fcgi_trans( type => 6, id => 1, data => $expect_stdout ) . 110 # End of STDOUT 111 fcgi_trans( type => 6, id => 1, data => "" ) . 112 # End request 113 fcgi_trans( type => 3, id => 1, data => "\0\0\0\0\0\0\0\0" ); 114 115my $buffer; 116 117$buffer = ""; 118 119wait_for_stream { length $buffer >= length $expect } $C => $buffer; 120 121is_hexstr( $buffer, $expect, 'FastCGI end request record' ); 122 123$fcgi->configure( 124 app => sub { 125 my $env = shift; 126 my $input = delete $env->{'psgi.input'}; 127 128 my $content = ""; 129 while( $input->read( my $buffer, 1024 ) ) { 130 $content .= $buffer; 131 } 132 133 return [ 134 200, 135 [ "Content-Type" => "text/plain" ], 136 [ "Input was: $content" ], 137 ]; 138 } 139); 140 141$C->syswrite( 142 # Begin with FCGI_KEEP_CONN 143 fcgi_trans( type => 1, id => 1, data => "\0\1\1\0\0\0\0\0" ) . 144 # Parameters 145 fcgi_trans( type => 4, id => 1, data => 146 fcgi_keyval( REQUEST_METHOD => "GET" ) . 147 fcgi_keyval( SCRIPT_NAME => "" ) . 148 fcgi_keyval( PATH_INFO => "" ) . 149 fcgi_keyval( REQUEST_URI => "/" ) . 150 fcgi_keyval( QUERY_STRING => "" ) . 151 fcgi_keyval( SERVER_NAME => "localhost" ) . 152 fcgi_keyval( SERVER_PORT => "80" ) . 153 fcgi_keyval( SERVER_PROTOCOL => "HTTP/1.1" ) 154 ) . 155 # End of parameters 156 fcgi_trans( type => 4, id => 1, data => "" ) . 157 # STDIN 158 fcgi_trans( type => 5, id => 1, data => "Some data on STDIN" ) . 159 # End of STDIN 160 fcgi_trans( type => 5, id => 1, data => "" ) 161); 162 163$expect_stdout = join( "", map "$_$CRLF", 164 "Status: 200", 165 "Content-Type: text/plain", 166 '' ) . 167 "Input was: Some data on STDIN"; 168 169$expect = 170 # STDOUT 171 fcgi_trans( type => 6, id => 1, data => $expect_stdout ) . 172 # End of STDOUT 173 fcgi_trans( type => 6, id => 1, data => "" ) . 174 # End request 175 fcgi_trans( type => 3, id => 1, data => "\0\0\0\0\0\0\0\0" ); 176 177$buffer = ""; 178 179wait_for_stream { length $buffer >= length $expect } $C => $buffer; 180 181is_hexstr( $buffer, $expect, 'FastCGI request/response with STDIN reading' ); 182 183$fcgi->configure( 184 app => sub { 185 my $env = shift; 186 187 $env->{'psgi.errors'}->print( "An error line here\n" ); 188 189 return [ 190 200, 191 [ "Content-Type" => "text/plain" ], 192 [ "" ], 193 ]; 194 } 195); 196 197$C->syswrite( 198 # Begin with FCGI_KEEP_CONN 199 fcgi_trans( type => 1, id => 1, data => "\0\1\1\0\0\0\0\0" ) . 200 # Parameters 201 fcgi_trans( type => 4, id => 1, data => 202 fcgi_keyval( REQUEST_METHOD => "GET" ) . 203 fcgi_keyval( SCRIPT_NAME => "" ) . 204 fcgi_keyval( PATH_INFO => "" ) . 205 fcgi_keyval( REQUEST_URI => "/" ) . 206 fcgi_keyval( QUERY_STRING => "" ) . 207 fcgi_keyval( SERVER_NAME => "localhost" ) . 208 fcgi_keyval( SERVER_PORT => "80" ) . 209 fcgi_keyval( SERVER_PROTOCOL => "HTTP/1.1" ) 210 ) . 211 # End of parameters 212 fcgi_trans( type => 4, id => 1, data => "" ) . 213 # End of STDIN 214 fcgi_trans( type => 5, id => 1, data => "" ) 215); 216 217$expect_stdout = join( "", map "$_$CRLF", 218 "Status: 200", 219 "Content-Type: text/plain", 220 '' ) . 221 ""; 222 223$expect = 224 # STDOUT 225 fcgi_trans( type => 6, id => 1, data => $expect_stdout ) . 226 # STDERR 227 fcgi_trans( type => 7, id => 1, data => "An error line here\n" ) . 228 # End of STDOUT 229 fcgi_trans( type => 6, id => 1, data => "" ) . 230 # End of STDERR 231 fcgi_trans( type => 7, id => 1, data => "" ) . 232 # End request 233 fcgi_trans( type => 3, id => 1, data => "\0\0\0\0\0\0\0\0" ); 234 235$buffer = ""; 236 237wait_for_stream { length $buffer >= length $expect } $C => $buffer; 238 239is_hexstr( $buffer, $expect, 'FastCGI request/response with STDERR printing' ); 240 241$fcgi->configure( 242 app => sub { 243 my $env = shift; 244 245 open my $body, "<", \"Here is a IO-like string"; 246 247 return [ 248 200, 249 [ "Content-Type" => "text/plain" ], 250 $body, 251 ]; 252 } 253); 254 255$C->syswrite( 256 # Begin with FCGI_KEEP_CONN 257 fcgi_trans( type => 1, id => 1, data => "\0\1\1\0\0\0\0\0" ) . 258 # Parameters 259 fcgi_trans( type => 4, id => 1, data => 260 fcgi_keyval( REQUEST_METHOD => "GET" ) . 261 fcgi_keyval( SCRIPT_NAME => "" ) . 262 fcgi_keyval( PATH_INFO => "" ) . 263 fcgi_keyval( REQUEST_URI => "/" ) . 264 fcgi_keyval( QUERY_STRING => "" ) . 265 fcgi_keyval( SERVER_NAME => "localhost" ) . 266 fcgi_keyval( SERVER_PORT => "80" ) . 267 fcgi_keyval( SERVER_PROTOCOL => "HTTP/1.1" ) 268 ) . 269 # End of parameters 270 fcgi_trans( type => 4, id => 1, data => "" ) . 271 # End of STDIN 272 fcgi_trans( type => 5, id => 1, data => "" ) 273); 274 275# This STDOUT will come in two pieces 276$expect_stdout = join( "", map "$_$CRLF", 277 "Status: 200", 278 "Content-Type: text/plain", 279 '' ); 280 281$expect = 282 # STDOUT 283 fcgi_trans( type => 6, id => 1, data => $expect_stdout ) . 284 # STDOUT 285 fcgi_trans( type => 6, id => 1, data => "Here is a IO-like string" ) . 286 # End of STDOUT 287 fcgi_trans( type => 6, id => 1, data => "" ) . 288 # End request 289 fcgi_trans( type => 3, id => 1, data => "\0\0\0\0\0\0\0\0" ); 290 291$buffer = ""; 292 293wait_for_stream { length $buffer >= length $expect } $C => $buffer; 294 295is_hexstr( $buffer, $expect, 'FastCGI request/response with IO-like body' ); 296