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