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