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