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