r12983@zaphod: kd | 2008-04-28 18:10:27 +1000
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine / HTTP.pm
CommitLineData
ca61af20 1package Catalyst::Engine::HTTP;
45374ac6 2
7fa2c9c1 3use Moose;
4extends 'Catalyst::Engine::CGI';
0fc2d522 5
4bb8bd62 6use Data::Dump qw(dump);
fbcc39ad 7use Errno 'EWOULDBLOCK';
055ff026 8use HTTP::Date ();
06744540 9use HTTP::Headers;
fbcc39ad 10use HTTP::Status;
fbcc39ad 11use Socket;
6c7a1d2f 12use IO::Socket::INET ();
b5ecfcf0 13use IO::Select ();
45374ac6 14
71fd2e0f 15# For PAR
16require Catalyst::Engine::HTTP::Restarter;
17require Catalyst::Engine::HTTP::Restarter::Watcher;
18
333123ef 19use constant CHUNKSIZE => 64 * 1024;
20use constant DEBUG => $ENV{CATALYST_HTTP_DEBUG} || 0;
4bb8bd62 21
45374ac6 22=head1 NAME
23
ca61af20 24Catalyst::Engine::HTTP - Catalyst HTTP Engine
45374ac6 25
26=head1 SYNOPSIS
27
ca61af20 28A script using the Catalyst::Engine::HTTP module might look like:
45374ac6 29
30 #!/usr/bin/perl -w
31
ca61af20 32 BEGIN { $ENV{CATALYST_ENGINE} = 'HTTP' }
45374ac6 33
34 use strict;
35 use lib '/path/to/MyApp/lib';
36 use MyApp;
37
38 MyApp->run;
39
40=head1 DESCRIPTION
41
42This is the Catalyst engine specialized for development and testing.
43
fbcc39ad 44=head1 METHODS
45
b5ecfcf0 46=head2 $self->finalize_headers($c)
fbcc39ad 47
48=cut
49
50sub finalize_headers {
51 my ( $self, $c ) = @_;
52 my $protocol = $c->request->protocol;
53 my $status = $c->response->status;
54 my $message = status_message($status);
7fa2c9c1 55 my $res_headers = $c->response->headers;
56
06744540 57 my @headers;
58 push @headers, "$protocol $status $message";
7fa2c9c1 59
60 $res_headers->header( Date => HTTP::Date::time2str(time) );
61 $res_headers->header( Status => $status );
62
7f3c5736 63 # Should we keep the connection open?
64 my $connection = $c->request->header('Connection');
ac5c933b 65 if ( $self->{options}->{keepalive}
66 && $connection
7f3c5736 67 && $connection =~ /^keep-alive$/i
68 ) {
7fa2c9c1 69 $res_headers->header( Connection => 'keep-alive' );
7f3c5736 70 $self->{_keepalive} = 1;
71 }
72 else {
7fa2c9c1 73 $res_headers->header( Connection => 'close' );
7f3c5736 74 }
7fa2c9c1 75
76 push @headers, $res_headers->as_string("\x0D\x0A");
77
06744540 78 # Buffer the headers so they are sent with the first write() call
79 # This reduces the number of TCP packets we are sending
80 $self->{_header_buf} = join("\x0D\x0A", @headers, '');
fbcc39ad 81}
82
b5ecfcf0 83=head2 $self->finalize_read($c)
fbcc39ad 84
85=cut
86
4090e3bb 87around finalize_read => sub {
fbcc39ad 88 # Never ever remove this, it would result in random length output
89 # streams if STDIN eq STDOUT (like in the HTTP engine)
4f5ebacd 90 *STDIN->blocking(1);
4090e3bb 91 shift->(@_);
92};
fbcc39ad 93
b5ecfcf0 94=head2 $self->prepare_read($c)
fbcc39ad 95
96=cut
97
4090e3bb 98around prepare_read => sub {
fbcc39ad 99 # Set the input handle to non-blocking
4f5ebacd 100 *STDIN->blocking(0);
4090e3bb 101 shift->(@_);
102};
fbcc39ad 103
b5ecfcf0 104=head2 $self->read_chunk($c, $buffer, $length)
fbcc39ad 105
106=cut
107
108sub read_chunk {
109 my $self = shift;
110 my $c = shift;
ac5c933b 111
4bb8bd62 112 # If we have any remaining data in the input buffer, send it back first
113 if ( $_[0] = delete $self->{inputbuf} ) {
114 my $read = length( $_[0] );
115 DEBUG && warn "read_chunk: Read $read bytes from previous input buffer\n";
116 return $read;
117 }
fbcc39ad 118
119 # support for non-blocking IO
4f5ebacd 120 my $rin = '';
121 vec( $rin, *STDIN->fileno, 1 ) = 1;
fbcc39ad 122
123 READ:
124 {
125 select( $rin, undef, undef, undef );
4f5ebacd 126 my $rc = *STDIN->sysread(@_);
fbcc39ad 127 if ( defined $rc ) {
4bb8bd62 128 DEBUG && warn "read_chunk: Read $rc bytes from socket\n";
fbcc39ad 129 return $rc;
130 }
131 else {
132 next READ if $! == EWOULDBLOCK;
133 return;
134 }
135 }
136}
137
00c99324 138=head2 $self->write($c, $buffer)
139
e512dd24 140Writes the buffer to the client.
00c99324 141
142=cut
143
4090e3bb 144around write => sub {
145 my $orig = shift;
4bb8bd62 146 my ( $self, $c, $buffer ) = @_;
7fa2c9c1 147
85d9fce6 148 # Avoid 'print() on closed filehandle Remote' warnings when using IE
149 return unless *STDOUT->opened();
150
85d9fce6 151 # Prepend the headers if they have not yet been sent
152 if ( my $headers = delete $self->{_header_buf} ) {
e512dd24 153 $buffer = $headers . $buffer;
4bb8bd62 154 }
7fa2c9c1 155
4090e3bb 156 my $ret = $self->$orig($c, $buffer);
7fa2c9c1 157
e512dd24 158 if ( !defined $ret ) {
4bb8bd62 159 $self->{_write_error} = $!;
e2b0ddd3 160 DEBUG && warn "write: Failed to write response ($!)\n";
4bb8bd62 161 }
9f3ebd8a 162 else {
163 DEBUG && warn "write: Wrote response ($ret bytes)\n";
164 }
0fc2d522 165
4bb8bd62 166 return $ret;
4090e3bb 167};
00c99324 168
b5ecfcf0 169=head2 run
fbcc39ad 170
171=cut
172
173# A very very simple HTTP server that initializes a CGI environment
174sub run {
37553dc8 175 my ( $self, $class, $port, $host, $options ) = @_;
fbcc39ad 176
4eeca0f2 177 $options ||= {};
ac5c933b 178
3bcb3aae 179 $self->{options} = $options;
1cf1c56a 180
e1576f62 181 if ($options->{background}) {
182 my $child = fork;
183 die "Can't fork: $!" unless defined($child);
44c6d25a 184 return $child if $child;
e1576f62 185 }
186
57a87bb3 187 my $restart = 0;
6a5aa41c 188 local $SIG{CHLD} = 'IGNORE';
fbcc39ad 189
1cf1c56a 190 my $allowed = $options->{allowed} || { '127.0.0.1' => '255.255.255.255' };
6c7a1d2f 191 my $addr = $host ? inet_aton($host) : INADDR_ANY;
192 if ( $addr eq INADDR_ANY ) {
fbcc39ad 193 require Sys::Hostname;
6c7a1d2f 194 $host = lc Sys::Hostname::hostname();
fbcc39ad 195 }
196 else {
6c7a1d2f 197 $host = gethostbyaddr( $addr, AF_INET ) || inet_ntoa($addr);
fbcc39ad 198 }
6c7a1d2f 199
200 # Handle requests
201
202 # Setup socket
203 my $daemon = IO::Socket::INET->new(
204 Listen => SOMAXCONN,
205 LocalAddr => inet_ntoa($addr),
206 LocalPort => $port,
207 Proto => 'tcp',
208 ReuseAddr => 1,
209 Type => SOCK_STREAM,
210 )
211 or die "Couldn't create daemon: $!";
212
213 my $url = "http://$host";
214 $url .= ":$port" unless $port == 80;
215
fbcc39ad 216 print "You can connect to your server at $url\n";
fbcc39ad 217
e1576f62 218 if ($options->{background}) {
219 open STDIN, "+</dev/null" or die $!;
220 open STDOUT, ">&STDIN" or die $!;
221 open STDERR, ">&STDIN" or die $!;
222 if ( $^O !~ /MSWin32/ ) {
223 require POSIX;
224 POSIX::setsid()
225 or die "Can't start a new session: $!";
226 }
227 }
228
229 if (my $pidfile = $options->{pidfile}) {
230 if (! open PIDFILE, "> $pidfile") {
231 warn("Cannot open: $pidfile: $!");
232 }
233 print PIDFILE "$$\n";
234 close PIDFILE;
235 }
236
4bb8bd62 237 my $pid = undef;
7fa2c9c1 238
4bb8bd62 239 # Ignore broken pipes as an HTTP server should
240 local $SIG{PIPE} = 'IGNORE';
7fa2c9c1 241
b095458a 242 # Restart on HUP
7fa2c9c1 243 local $SIG{HUP} = sub {
b095458a 244 $restart = 1;
245 warn "Restarting server on SIGHUP...\n";
246 };
7fa2c9c1 247
4bb8bd62 248 LISTEN:
249 while ( !$restart ) {
7fa2c9c1 250 while ( accept( Remote, $daemon ) ) {
4bb8bd62 251 DEBUG && warn "New connection\n";
fbcc39ad 252
4bb8bd62 253 select Remote;
fbcc39ad 254
4bb8bd62 255 Remote->blocking(1);
7fa2c9c1 256
7f3c5736 257 # Read until we see all headers
4bb8bd62 258 $self->{inputbuf} = '';
7fa2c9c1 259
7f3c5736 260 if ( !$self->_read_headers ) {
261 # Error reading, give up
059c085b 262 close Remote;
7f3c5736 263 next LISTEN;
4bb8bd62 264 }
fbcc39ad 265
4bb8bd62 266 my ( $method, $uri, $protocol ) = $self->_parse_request_line;
7fa2c9c1 267
4bb8bd62 268 DEBUG && warn "Parsed request: $method $uri $protocol\n";
7fa2c9c1 269 next unless $method;
57a87bb3 270
4bb8bd62 271 unless ( uc($method) eq 'RESTART' ) {
57a87bb3 272
4bb8bd62 273 # Fork
7fa2c9c1 274 if ( $options->{fork} ) {
1b45d7e5 275 if ( $pid = fork ) {
276 DEBUG && warn "Forked child $pid\n";
277 next;
278 }
279 }
6c7a1d2f 280
4bb8bd62 281 $self->_handler( $class, $port, $method, $uri, $protocol );
ac5c933b 282
4bb8bd62 283 if ( my $error = delete $self->{_write_error} ) {
4bb8bd62 284 close Remote;
ac5c933b 285
1b45d7e5 286 if ( !defined $pid ) {
287 next LISTEN;
288 }
4bb8bd62 289 }
fbcc39ad 290
1b45d7e5 291 if ( defined $pid ) {
292 # Child process, close connection and exit
293 DEBUG && warn "Child process exiting\n";
294 $daemon->close;
295 exit;
296 }
1cf1c56a 297 }
4bb8bd62 298 else {
299 my $sockdata = $self->_socket_data( \*Remote );
300 my $ipaddr = _inet_addr( $sockdata->{peeraddr} );
301 my $ready = 0;
302 foreach my $ip ( keys %$allowed ) {
303 my $mask = $allowed->{$ip};
304 $ready = ( $ipaddr & _inet_addr($mask) ) == _inet_addr($ip);
305 last if $ready;
306 }
307 if ($ready) {
308 $restart = 1;
309 last;
310 }
1cf1c56a 311 }
4bb8bd62 312 }
313 continue {
314 close Remote;
315 }
fbcc39ad 316 }
ac5c933b 317
6c7a1d2f 318 $daemon->close;
ac5c933b 319
4bb8bd62 320 DEBUG && warn "Shutting down\n";
37553dc8 321
57a87bb3 322 if ($restart) {
60c38e3e 323 $SIG{CHLD} = 'DEFAULT';
6844bc1c 324 wait;
e37e3977 325
326 ### if the standalone server was invoked with perl -I .. we will loose
327 ### those include dirs upon re-exec. So add them to PERL5LIB, so they
328 ### are available again for the exec'ed process --kane
329 use Config;
ac5c933b 330 $ENV{PERL5LIB} .= join $Config{path_sep}, @INC;
331
ea52914e 332 exec $^X, $0, @{ $options->{argv} };
60c38e3e 333 }
57a87bb3 334
335 exit;
fbcc39ad 336}
337
6c7a1d2f 338sub _handler {
339 my ( $self, $class, $port, $method, $uri, $protocol ) = @_;
340
6c7a1d2f 341 local *STDIN = \*Remote;
342 local *STDOUT = \*Remote;
343
344 # We better be careful and just use 1.0
345 $protocol = '1.0';
346
347 my $sockdata = $self->_socket_data( \*Remote );
348 my %copy_of_env = %ENV;
349
350 my $sel = IO::Select->new;
351 $sel->add( \*STDIN );
ac5c933b 352
3bcb3aae 353 REQUEST:
683762ca 354 while (1) {
355 my ( $path, $query_string ) = split /\?/, $uri, 2;
ac5c933b 356
7f3c5736 357 # Initialize CGI environment
358 local %ENV = (
359 PATH_INFO => $path || '',
360 QUERY_STRING => $query_string || '',
361 REMOTE_ADDR => $sockdata->{peeraddr},
362 REMOTE_HOST => $sockdata->{peername},
363 REQUEST_METHOD => $method || '',
364 SERVER_NAME => $sockdata->{localname},
365 SERVER_PORT => $port,
366 SERVER_PROTOCOL => "HTTP/$protocol",
367 %copy_of_env,
368 );
cf26c39c 369
7f3c5736 370 # Parse headers
371 if ( $protocol >= 1 ) {
372 $self->_parse_headers;
373 }
6c7a1d2f 374
7f3c5736 375 # Pass flow control to Catalyst
376 $class->handle_request;
ac5c933b 377
7f3c5736 378 DEBUG && warn "Request done\n";
ac5c933b 379
7f3c5736 380 # Allow keepalive requests, this is a hack but we'll support it until
381 # the next major release.
382 if ( delete $self->{_keepalive} ) {
ac5c933b 383
7f3c5736 384 DEBUG && warn "Reusing previous connection for keep-alive request\n";
ac5c933b 385
386 if ( $sel->can_read(1) ) {
7f3c5736 387 if ( !$self->_read_headers ) {
388 # Error reading, give up
389 last REQUEST;
390 }
391
392 ( $method, $uri, $protocol ) = $self->_parse_request_line;
ac5c933b 393
7f3c5736 394 DEBUG && warn "Parsed request: $method $uri $protocol\n";
ac5c933b 395
7f3c5736 396 # Force HTTP/1.0
397 $protocol = '1.0';
ac5c933b 398
7f3c5736 399 next REQUEST;
400 }
ac5c933b 401
7f3c5736 402 DEBUG && warn "No keep-alive request within 1 second\n";
403 }
ac5c933b 404
7f3c5736 405 last REQUEST;
406 }
ac5c933b 407
7f3c5736 408 DEBUG && warn "Closing connection\n";
06744540 409
410 close Remote;
3bcb3aae 411}
412
7f3c5736 413sub _read_headers {
414 my $self = shift;
7fa2c9c1 415
7f3c5736 416 while (1) {
417 my $read = sysread Remote, my $buf, CHUNKSIZE;
7fa2c9c1 418
059c085b 419 if ( !defined $read ) {
420 next if $! == EWOULDBLOCK;
421 DEBUG && warn "Error reading headers: $!\n";
422 return;
7fa2c9c1 423 } elsif ( $read == 0 ) {
059c085b 424 DEBUG && warn "EOF\n";
7f3c5736 425 return;
426 }
7fa2c9c1 427
7f3c5736 428 DEBUG && warn "Read $read bytes\n";
429 $self->{inputbuf} .= $buf;
430 last if $self->{inputbuf} =~ /(\x0D\x0A?\x0D\x0A?|\x0A\x0D?\x0A\x0D?)/s;
431 }
7fa2c9c1 432
7f3c5736 433 return 1;
434}
435
4bb8bd62 436sub _parse_request_line {
437 my $self = shift;
6c7a1d2f 438
7fa2c9c1 439 # Parse request line
4bb8bd62 440 if ( $self->{inputbuf} !~ s/^(\w+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012// ) {
441 return ();
442 }
7fa2c9c1 443
4bb8bd62 444 my $method = $1;
445 my $uri = $2;
446 my $proto = $3 || 'HTTP/0.9';
7fa2c9c1 447
4bb8bd62 448 return ( $method, $uri, $proto );
6c7a1d2f 449}
450
4bb8bd62 451sub _parse_headers {
452 my $self = shift;
7fa2c9c1 453
4bb8bd62 454 # Copy the buffer for header parsing, and remove the header block
455 # from the content buffer.
456 my $buf = $self->{inputbuf};
457 $self->{inputbuf} =~ s/.*?(\x0D\x0A?\x0D\x0A?|\x0A\x0D?\x0A\x0D?)//s;
7fa2c9c1 458
4bb8bd62 459 # Parse headers
460 my $headers = HTTP::Headers->new;
461 my ($key, $val);
462 HEADER:
463 while ( $buf =~ s/^([^\012]*)\012// ) {
464 $_ = $1;
465 s/\015$//;
466 if ( /^([\w\-~]+)\s*:\s*(.*)/ ) {
467 $headers->push_header( $key, $val ) if $key;
468 ($key, $val) = ($1, $2);
469 }
470 elsif ( /^\s+(.*)/ ) {
471 $val .= " $1";
472 }
473 else {
474 last HEADER;
475 }
476 }
477 $headers->push_header( $key, $val ) if $key;
ac5c933b 478
4bb8bd62 479 DEBUG && warn "Parsed headers: " . dump($headers) . "\n";
6c7a1d2f 480
4bb8bd62 481 # Convert headers into ENV vars
482 $headers->scan( sub {
483 my ( $key, $val ) = @_;
ac5c933b 484
4bb8bd62 485 $key = uc $key;
486 $key = 'COOKIE' if $key eq 'COOKIES';
487 $key =~ tr/-/_/;
488 $key = 'HTTP_' . $key
489 unless $key =~ m/\A(?:CONTENT_(?:LENGTH|TYPE)|COOKIE)\z/;
ac5c933b 490
4bb8bd62 491 if ( exists $ENV{$key} ) {
492 $ENV{$key} .= ", $val";
493 }
494 else {
495 $ENV{$key} = $val;
496 }
497 } );
6c7a1d2f 498}
499
500sub _socket_data {
501 my ( $self, $handle ) = @_;
502
8b9d0298 503 my $remote_sockaddr = getpeername($handle);
ac5c933b 504 my ( undef, $iaddr ) = $remote_sockaddr
505 ? sockaddr_in($remote_sockaddr)
3150d774 506 : (undef, undef);
ac5c933b 507
8b9d0298 508 my $local_sockaddr = getsockname($handle);
6c7a1d2f 509 my ( undef, $localiaddr ) = sockaddr_in($local_sockaddr);
510
8b9d0298 511 # This mess is necessary to keep IE from crashing the server
6c7a1d2f 512 my $data = {
ac5c933b 513 peername => $iaddr
8b9d0298 514 ? ( gethostbyaddr( $iaddr, AF_INET ) || 'localhost' )
515 : 'localhost',
ac5c933b 516 peeraddr => $iaddr
8b9d0298 517 ? ( inet_ntoa($iaddr) || '127.0.0.1' )
518 : '127.0.0.1',
519 localname => gethostbyaddr( $localiaddr, AF_INET ) || 'localhost',
520 localaddr => inet_ntoa($localiaddr) || '127.0.0.1',
6c7a1d2f 521 };
522
523 return $data;
524}
525
1cf1c56a 526sub _inet_addr { unpack "N*", inet_aton( $_[0] ) }
527
4090e3bb 528no Moose;
529
45374ac6 530=head1 SEE ALSO
531
2f381252 532L<Catalyst>, L<Catalyst::Engine>
fbcc39ad 533
534=head1 AUTHORS
535
2f381252 536Catalyst Contributors, see Catalyst.pm
4bb8bd62 537
fbcc39ad 538=head1 THANKS
45374ac6 539
fbcc39ad 540Many parts are ripped out of C<HTTP::Server::Simple> by Jesse Vincent.
45374ac6 541
542=head1 COPYRIGHT
543
544This program is free software, you can redistribute it and/or modify it under
545the same terms as Perl itself.
546
547=cut
548
45374ac6 5491;