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