Improved performance and stability of built-in HTTP server. Ripped out keep-alive...
[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     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 a newline
244             $self->{inputbuf} = '';
245         
246             while (1) {
247                 my $read = sysread Remote, my $buf, CHUNKSIZE;
248             
249                 if ( !$read ) {
250                     DEBUG && warn "EOF or error: $!\n";
251                     next LISTEN;
252                 }
253             
254                 DEBUG && warn "Read $read bytes\n";
255                 $self->{inputbuf} .= $buf;
256                 last if $self->{inputbuf} =~ /(\x0D\x0A?|\x0A\x0D?)/s;
257             }
258
259             my ( $method, $uri, $protocol ) = $self->_parse_request_line;
260         
261             DEBUG && warn "Parsed request: $method $uri $protocol\n";
262         
263             next unless $method;
264
265             unless ( uc($method) eq 'RESTART' ) {
266
267                 # Fork
268                 if ( $options->{fork} ) { next if $pid = fork }
269
270                 $self->_handler( $class, $port, $method, $uri, $protocol );
271             
272                 if ( my $error = delete $self->{_write_error} ) {
273                     DEBUG && warn "Write error: $error\n";
274                     close Remote;
275                     next LISTEN;
276                 }
277
278                 $daemon->close if defined $pid;
279             }
280             else {
281                 my $sockdata = $self->_socket_data( \*Remote );
282                 my $ipaddr   = _inet_addr( $sockdata->{peeraddr} );
283                 my $ready    = 0;
284                 foreach my $ip ( keys %$allowed ) {
285                     my $mask = $allowed->{$ip};
286                     $ready = ( $ipaddr & _inet_addr($mask) ) == _inet_addr($ip);
287                     last if $ready;
288                 }
289                 if ($ready) {
290                     $restart = 1;
291                     last;
292                 }
293             }
294
295             exit if defined $pid;
296         }
297         continue {
298             close Remote;
299         }
300     }
301     
302     $daemon->close;
303     
304     DEBUG && warn "Shutting down\n";
305
306     if ($restart) {
307         $SIG{CHLD} = 'DEFAULT';
308         wait;
309
310         ### if the standalone server was invoked with perl -I .. we will loose
311         ### those include dirs upon re-exec. So add them to PERL5LIB, so they
312         ### are available again for the exec'ed process --kane
313         use Config;
314         $ENV{PERL5LIB} .= join $Config{path_sep}, @INC; 
315         
316         exec $^X . ' "' . $0 . '" ' . join( ' ', @{ $options->{argv} } );
317     }
318
319     exit;
320 }
321
322 sub _handler {
323     my ( $self, $class, $port, $method, $uri, $protocol ) = @_;
324
325     local *STDIN  = \*Remote;
326     local *STDOUT = \*Remote;
327
328     # We better be careful and just use 1.0
329     $protocol = '1.0';
330
331     my $sockdata    = $self->_socket_data( \*Remote );
332     my %copy_of_env = %ENV;
333
334     my $sel = IO::Select->new;
335     $sel->add( \*STDIN );
336
337     my ( $path, $query_string ) = split /\?/, $uri, 2;
338
339     # Initialize CGI environment
340     local %ENV = (
341         PATH_INFO       => $path         || '',
342         QUERY_STRING    => $query_string || '',
343         REMOTE_ADDR     => $sockdata->{peeraddr},
344         REMOTE_HOST     => $sockdata->{peername},
345         REQUEST_METHOD  => $method || '',
346         SERVER_NAME     => $sockdata->{localname},
347         SERVER_PORT     => $port,
348         SERVER_PROTOCOL => "HTTP/$protocol",
349         %copy_of_env,
350     );
351
352     # Parse headers
353     if ( $protocol >= 1 ) {
354         $self->_parse_headers;
355     }
356
357     # Pass flow control to Catalyst
358     $class->handle_request;
359     
360     DEBUG && warn "Request done\n";
361     
362     # XXX: We used to have a hack for keep-alive here but keep-alive
363     # has no place in a single-tasking server like this.  Use HTTP::POE
364     # if you want keep-alive.
365
366     close Remote;
367 }
368
369 sub _parse_request_line {
370     my $self = shift;
371
372     # Parse request line    
373     if ( $self->{inputbuf} !~ s/^(\w+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012// ) {
374         return ();
375     }
376     
377     my $method = $1;
378     my $uri    = $2;
379     my $proto  = $3 || 'HTTP/0.9';
380     
381     return ( $method, $uri, $proto );
382 }
383
384 sub _parse_headers {
385     my $self = shift;
386     
387     # Copy the buffer for header parsing, and remove the header block
388     # from the content buffer.
389     my $buf = $self->{inputbuf};
390     $self->{inputbuf} =~ s/.*?(\x0D\x0A?\x0D\x0A?|\x0A\x0D?\x0A\x0D?)//s;
391     
392     # Parse headers
393     my $headers = HTTP::Headers->new;
394     my ($key, $val);
395     HEADER:
396     while ( $buf =~ s/^([^\012]*)\012// ) {
397         $_ = $1;
398         s/\015$//;
399         if ( /^([\w\-~]+)\s*:\s*(.*)/ ) {
400             $headers->push_header( $key, $val ) if $key;
401             ($key, $val) = ($1, $2);
402         }
403         elsif ( /^\s+(.*)/ ) {
404             $val .= " $1";
405         }
406         else {
407             last HEADER;
408         }
409     }
410     $headers->push_header( $key, $val ) if $key;
411     
412     DEBUG && warn "Parsed headers: " . dump($headers) . "\n";
413
414     # Convert headers into ENV vars
415     $headers->scan( sub {
416         my ( $key, $val ) = @_;
417         
418         $key = uc $key;
419         $key = 'COOKIE' if $key eq 'COOKIES';
420         $key =~ tr/-/_/;
421         $key = 'HTTP_' . $key
422             unless $key =~ m/\A(?:CONTENT_(?:LENGTH|TYPE)|COOKIE)\z/;
423             
424         if ( exists $ENV{$key} ) {
425             $ENV{$key} .= ", $val";
426         }
427         else {
428             $ENV{$key} = $val;
429         }
430     } );
431 }
432
433 sub _socket_data {
434     my ( $self, $handle ) = @_;
435
436     my $remote_sockaddr       = getpeername($handle);
437     my ( undef, $iaddr )      = $remote_sockaddr 
438         ? sockaddr_in($remote_sockaddr) 
439         : (undef, undef);
440         
441     my $local_sockaddr        = getsockname($handle);
442     my ( undef, $localiaddr ) = sockaddr_in($local_sockaddr);
443
444     # This mess is necessary to keep IE from crashing the server
445     my $data = {
446         peername  => $iaddr 
447             ? ( gethostbyaddr( $iaddr, AF_INET ) || 'localhost' )
448             : 'localhost',
449         peeraddr  => $iaddr 
450             ? ( inet_ntoa($iaddr) || '127.0.0.1' )
451             : '127.0.0.1',
452         localname => gethostbyaddr( $localiaddr, AF_INET ) || 'localhost',
453         localaddr => inet_ntoa($localiaddr) || '127.0.0.1',
454     };
455
456     return $data;
457 }
458
459 sub _inet_addr { unpack "N*", inet_aton( $_[0] ) }
460
461 =head1 SEE ALSO
462
463 L<Catalyst>, L<Catalyst::Engine>.
464
465 =head1 AUTHORS
466
467 Sebastian Riedel, <sri@cpan.org>
468
469 Dan Kubb, <dan.kubb-cpan@onautopilot.com>
470
471 Sascha Kiefer, <esskar@cpan.org>
472
473 Andy Grundman, <andy@hybridized.org>
474
475 =head1 THANKS
476
477 Many parts are ripped out of C<HTTP::Server::Simple> by Jesse Vincent.
478
479 =head1 COPYRIGHT
480
481 This program is free software, you can redistribute it and/or modify it under
482 the same terms as Perl itself.
483
484 =cut
485
486 1;