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