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