Clear the request env after finishing a request.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
1 package Catalyst::Engine;
2
3 use Moose;
4 with 'MooseX::Emulate::Class::Accessor::Fast';
5
6 use CGI::Simple::Cookie;
7 use Data::Dump qw/dump/;
8 use Errno 'EWOULDBLOCK';
9 use HTML::Entities;
10 use HTTP::Body;
11 use HTTP::Headers;
12 use URI::QueryParam;
13 use Moose::Util::TypeConstraints;
14
15 use namespace::clean -except => 'meta';
16
17 has env => (is => 'ro', writer => '_set_env', clearer => '_clear_env');
18
19 # input position and length
20 has read_length => (is => 'rw');
21 has read_position => (is => 'rw');
22
23 has _prepared_write => (is => 'rw');
24
25 has _response_cb => (
26     is     => 'ro',
27     isa    => 'CodeRef',
28     writer => '_set_response_cb',
29 );
30
31 has _writer => (
32     is     => 'ro',
33     isa    => duck_type([qw(write close)]),
34     writer => '_set_writer',
35 );
36
37 # Amount of data to read from input on each pass
38 our $CHUNKSIZE = 64 * 1024;
39
40 =head1 NAME
41
42 Catalyst::Engine - The Catalyst Engine
43
44 =head1 SYNOPSIS
45
46 See L<Catalyst>.
47
48 =head1 DESCRIPTION
49
50 =head1 METHODS
51
52
53 =head2 $self->finalize_body($c)
54
55 Finalize body.  Prints the response output.
56
57 =cut
58
59 sub finalize_body {
60     my ( $self, $c ) = @_;
61     my $body = $c->response->body;
62     no warnings 'uninitialized';
63     if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
64         my $got;
65         do {
66             $got = read $body, my ($buffer), $CHUNKSIZE;
67             $got = 0 unless $self->write( $c, $buffer );
68         } while $got > 0;
69
70         close $body;
71     }
72     else {
73         $self->write( $c, $body );
74     }
75
76     $self->_writer->close;
77
78     $self->_clear_env;
79
80     return;
81 }
82
83 =head2 $self->finalize_cookies($c)
84
85 Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
86 response headers.
87
88 =cut
89
90 sub finalize_cookies {
91     my ( $self, $c ) = @_;
92
93     my @cookies;
94     my $response = $c->response;
95
96     foreach my $name (keys %{ $response->cookies }) {
97
98         my $val = $response->cookies->{$name};
99
100         my $cookie = (
101             blessed($val)
102             ? $val
103             : CGI::Simple::Cookie->new(
104                 -name    => $name,
105                 -value   => $val->{value},
106                 -expires => $val->{expires},
107                 -domain  => $val->{domain},
108                 -path    => $val->{path},
109                 -secure  => $val->{secure} || 0,
110                 -httponly => $val->{httponly} || 0,
111             )
112         );
113
114         push @cookies, $cookie->as_string;
115     }
116
117     for my $cookie (@cookies) {
118         $response->headers->push_header( 'Set-Cookie' => $cookie );
119     }
120 }
121
122 =head2 $self->finalize_error($c)
123
124 Output an appropriate error message. Called if there's an error in $c
125 after the dispatch has finished. Will output debug messages if Catalyst
126 is in debug mode, or a `please come back later` message otherwise.
127
128 =cut
129
130 sub _dump_error_page_element {
131     my ($self, $i, $element) = @_;
132     my ($name, $val)  = @{ $element };
133
134     # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
135     # scrolling. Suggestions for more pleasant ways to do this welcome.
136     local $val->{'__MOP__'} = "Stringified: "
137         . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
138
139     my $text = encode_entities( dump( $val ));
140     sprintf <<"EOF", $name, $text;
141 <h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
142 <div id="dump_$i">
143     <pre wrap="">%s</pre>
144 </div>
145 EOF
146 }
147
148 sub finalize_error {
149     my ( $self, $c ) = @_;
150
151     $c->res->content_type('text/html; charset=utf-8');
152     my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
153
154     my ( $title, $error, $infos );
155     if ( $c->debug ) {
156
157         # For pretty dumps
158         $error = join '', map {
159                 '<p><code class="error">'
160               . encode_entities($_)
161               . '</code></p>'
162         } @{ $c->error };
163         $error ||= 'No output';
164         $error = qq{<pre wrap="">$error</pre>};
165         $title = $name = "$name on Catalyst $Catalyst::VERSION";
166         $name  = "<h1>$name</h1>";
167
168         # Don't show context in the dump
169         $c->req->_clear_context;
170         $c->res->_clear_context;
171
172         # Don't show body parser in the dump
173         $c->req->_clear_body;
174
175         my @infos;
176         my $i = 0;
177         for my $dump ( $c->dump_these ) {
178             push @infos, $self->_dump_error_page_element($i, $dump);
179             $i++;
180         }
181         $infos = join "\n", @infos;
182     }
183     else {
184         $title = $name;
185         $error = '';
186         $infos = <<"";
187 <pre>
188 (en) Please come back later
189 (fr) SVP veuillez revenir plus tard
190 (de) Bitte versuchen sie es spaeter nocheinmal
191 (at) Konnten's bitt'schoen spaeter nochmal reinschauen
192 (no) Vennligst prov igjen senere
193 (dk) Venligst prov igen senere
194 (pl) Prosze sprobowac pozniej
195 (pt) Por favor volte mais tarde
196 (ru) Попробуйте еще раз позже
197 (ua) Спробуйте ще раз пізніше
198 </pre>
199
200         $name = '';
201     }
202     $c->res->body( <<"" );
203 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
204     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
205 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
206 <head>
207     <meta http-equiv="Content-Language" content="en" />
208     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
209     <title>$title</title>
210     <script type="text/javascript">
211         <!--
212         function toggleDump (dumpElement) {
213             var e = document.getElementById( dumpElement );
214             if (e.style.display == "none") {
215                 e.style.display = "";
216             }
217             else {
218                 e.style.display = "none";
219             }
220         }
221         -->
222     </script>
223     <style type="text/css">
224         body {
225             font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
226                          Tahoma, Arial, helvetica, sans-serif;
227             color: #333;
228             background-color: #eee;
229             margin: 0px;
230             padding: 0px;
231         }
232         :link, :link:hover, :visited, :visited:hover {
233             color: #000;
234         }
235         div.box {
236             position: relative;
237             background-color: #ccc;
238             border: 1px solid #aaa;
239             padding: 4px;
240             margin: 10px;
241         }
242         div.error {
243             background-color: #cce;
244             border: 1px solid #755;
245             padding: 8px;
246             margin: 4px;
247             margin-bottom: 10px;
248         }
249         div.infos {
250             background-color: #eee;
251             border: 1px solid #575;
252             padding: 8px;
253             margin: 4px;
254             margin-bottom: 10px;
255         }
256         div.name {
257             background-color: #cce;
258             border: 1px solid #557;
259             padding: 8px;
260             margin: 4px;
261         }
262         code.error {
263             display: block;
264             margin: 1em 0;
265             overflow: auto;
266         }
267         div.name h1, div.error p {
268             margin: 0;
269         }
270         h2 {
271             margin-top: 0;
272             margin-bottom: 10px;
273             font-size: medium;
274             font-weight: bold;
275             text-decoration: underline;
276         }
277         h1 {
278             font-size: medium;
279             font-weight: normal;
280         }
281         /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
282         /* Browser specific (not valid) styles to make preformatted text wrap */
283         pre {
284             white-space: pre-wrap;       /* css-3 */
285             white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
286             white-space: -pre-wrap;      /* Opera 4-6 */
287             white-space: -o-pre-wrap;    /* Opera 7 */
288             word-wrap: break-word;       /* Internet Explorer 5.5+ */
289         }
290     </style>
291 </head>
292 <body>
293     <div class="box">
294         <div class="error">$error</div>
295         <div class="infos">$infos</div>
296         <div class="name">$name</div>
297     </div>
298 </body>
299 </html>
300
301
302     # Trick IE
303     $c->res->{body} .= ( ' ' x 512 );
304
305     # Return 500
306     $c->res->status(500);
307 }
308
309 =head2 $self->finalize_headers($c)
310
311 Abstract method, allows engines to write headers to response
312
313 =cut
314
315 sub finalize_headers {
316     my ($self, $ctx) = @_;
317
318     my @headers;
319     $ctx->response->headers->scan(sub { push @headers, @_ });
320
321     $self->_set_writer($self->_response_cb->([ $ctx->response->status, \@headers ]));
322
323     return;
324 }
325
326 =head2 $self->finalize_read($c)
327
328 =cut
329
330 sub finalize_read { }
331
332 =head2 $self->finalize_uploads($c)
333
334 Clean up after uploads, deleting temp files.
335
336 =cut
337
338 sub finalize_uploads {
339     my ( $self, $c ) = @_;
340
341     my $request = $c->request;
342     foreach my $key (keys %{ $request->uploads }) {
343         my $upload = $request->uploads->{$key};
344         unlink grep { -e $_ } map { $_->tempname }
345           (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
346     }
347
348 }
349
350 =head2 $self->prepare_body($c)
351
352 sets up the L<Catalyst::Request> object body using L<HTTP::Body>
353
354 =cut
355
356 sub prepare_body {
357     my ( $self, $c ) = @_;
358
359     my $appclass = ref($c) || $c;
360     if ( my $length = $self->read_length ) {
361         my $request = $c->request;
362         unless ( $request->_body ) {
363             my $type = $request->header('Content-Type');
364             $request->_body(HTTP::Body->new( $type, $length ));
365             $request->_body->tmpdir( $appclass->config->{uploadtmp} )
366               if exists $appclass->config->{uploadtmp};
367         }
368
369         # Check for definedness as you could read '0'
370         while ( defined ( my $buffer = $self->read($c) ) ) {
371             $c->prepare_body_chunk($buffer);
372         }
373
374         # paranoia against wrong Content-Length header
375         my $remaining = $length - $self->read_position;
376         if ( $remaining > 0 ) {
377             $self->finalize_read($c);
378             Catalyst::Exception->throw(
379                 "Wrong Content-Length value: $length" );
380         }
381     }
382     else {
383         # Defined but will cause all body code to be skipped
384         $c->request->_body(0);
385     }
386 }
387
388 =head2 $self->prepare_body_chunk($c)
389
390 Add a chunk to the request body.
391
392 =cut
393
394 sub prepare_body_chunk {
395     my ( $self, $c, $chunk ) = @_;
396
397     $c->request->_body->add($chunk);
398 }
399
400 =head2 $self->prepare_body_parameters($c)
401
402 Sets up parameters from body.
403
404 =cut
405
406 sub prepare_body_parameters {
407     my ( $self, $c ) = @_;
408
409     return unless $c->request->_body;
410
411     $c->request->body_parameters( $c->request->_body->param );
412 }
413
414 =head2 $self->prepare_connection($c)
415
416 Abstract method implemented in engines.
417
418 =cut
419
420 sub prepare_connection {
421     my ($self, $ctx) = @_;
422
423     my $env = $self->env;
424     my $request = $ctx->request;
425
426     $request->address( $env->{REMOTE_ADDR} );
427     $request->hostname( $env->{REMOTE_HOST} )
428         if exists $env->{REMOTE_HOST};
429     $request->protocol( $env->{SERVER_PROTOCOL} );
430     $request->remote_user( $env->{REMOTE_USER} );
431     $request->method( $env->{REQUEST_METHOD} );
432     $request->secure( $env->{'psgi.url_scheme'} eq 'https' );
433
434     return;
435 }
436
437 =head2 $self->prepare_cookies($c)
438
439 Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
440
441 =cut
442
443 sub prepare_cookies {
444     my ( $self, $c ) = @_;
445
446     if ( my $header = $c->request->header('Cookie') ) {
447         $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
448     }
449 }
450
451 =head2 $self->prepare_headers($c)
452
453 =cut
454
455 sub prepare_headers {
456     my ($self, $ctx) = @_;
457
458     my $env = $self->env;
459     my $headers = $ctx->request->headers;
460
461     for my $header (keys %{ $env }) {
462         next unless $header =~ /^(HTTP|CONTENT|COOKIE)/i;
463         (my $field = $header) =~ s/^HTTPS?_//;
464         $field =~ tr/_/-/;
465         $headers->header($field => $env->{$header});
466     }
467 }
468
469 =head2 $self->prepare_parameters($c)
470
471 sets up parameters from query and post parameters.
472
473 =cut
474
475 sub prepare_parameters {
476     my ( $self, $c ) = @_;
477
478     my $request = $c->request;
479     my $parameters = $request->parameters;
480     my $body_parameters = $request->body_parameters;
481     my $query_parameters = $request->query_parameters;
482     # We copy, no references
483     foreach my $name (keys %$query_parameters) {
484         my $param = $query_parameters->{$name};
485         $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
486     }
487
488     # Merge query and body parameters
489     foreach my $name (keys %$body_parameters) {
490         my $param = $body_parameters->{$name};
491         my @values = ref $param eq 'ARRAY' ? @$param : ($param);
492         if ( my $existing = $parameters->{$name} ) {
493           unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
494         }
495         $parameters->{$name} = @values > 1 ? \@values : $values[0];
496     }
497 }
498
499 =head2 $self->prepare_path($c)
500
501 abstract method, implemented by engines.
502
503 =cut
504
505 sub prepare_path {
506     my ($self, $ctx) = @_;
507
508     my $env = $self->env;
509
510     my $scheme    = $ctx->request->secure ? 'https' : 'http';
511     my $host      = $env->{HTTP_HOST} || $env->{SERVER_NAME};
512     my $port      = $env->{SERVER_PORT} || 80;
513     my $base_path = $env->{SCRIPT_NAME} || "/";
514
515     # set the request URI
516     my $req_uri = $env->{REQUEST_URI};
517     $req_uri =~ s/\?.*$//;
518     my $path = $self->unescape_uri($req_uri);
519     $path =~ s{^/+}{};
520
521     # Using URI directly is way too slow, so we construct the URLs manually
522     my $uri_class = "URI::$scheme";
523
524     # HTTP_HOST will include the port even if it's 80/443
525     $host =~ s/:(?:80|443)$//;
526
527     if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
528         $host .= ":$port";
529     }
530
531     # Escape the path
532     $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
533     $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
534
535     my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
536     my $uri   = $scheme . '://' . $host . '/' . $path . $query;
537
538     $ctx->request->uri( bless \$uri, $uri_class );
539
540     # set the base URI
541     # base must end in a slash
542     $base_path .= '/' unless $base_path =~ m{/$};
543
544     my $base_uri = $scheme . '://' . $host . $base_path;
545
546     $ctx->request->base( bless \$base_uri, $uri_class );
547
548     return;
549 }
550
551 =head2 $self->prepare_request($c)
552
553 =head2 $self->prepare_query_parameters($c)
554
555 process the query string and extract query parameters.
556
557 =cut
558
559 sub prepare_query_parameters {
560     my ($self, $c) = @_;
561
562     my $query_string = exists $self->env->{QUERY_STRING}
563         ? $self->env->{QUERY_STRING}
564         : '';
565
566     # Check for keywords (no = signs)
567     # (yes, index() is faster than a regex :))
568     if ( index( $query_string, '=' ) < 0 ) {
569         $c->request->query_keywords( $self->unescape_uri($query_string) );
570         return;
571     }
572
573     my %query;
574
575     # replace semi-colons
576     $query_string =~ s/;/&/g;
577
578     my @params = grep { length $_ } split /&/, $query_string;
579
580     for my $item ( @params ) {
581
582         my ($param, $value)
583             = map { $self->unescape_uri($_) }
584               split( /=/, $item, 2 );
585
586         $param = $self->unescape_uri($item) unless defined $param;
587
588         if ( exists $query{$param} ) {
589             if ( ref $query{$param} ) {
590                 push @{ $query{$param} }, $value;
591             }
592             else {
593                 $query{$param} = [ $query{$param}, $value ];
594             }
595         }
596         else {
597             $query{$param} = $value;
598         }
599     }
600
601     $c->request->query_parameters( \%query );
602 }
603
604 =head2 $self->prepare_read($c)
605
606 prepare to read from the engine.
607
608 =cut
609
610 sub prepare_read {
611     my ( $self, $c ) = @_;
612
613     # Initialize the read position
614     $self->read_position(0);
615
616     # Initialize the amount of data we think we need to read
617     $self->read_length( $c->request->header('Content-Length') || 0 );
618 }
619
620 =head2 $self->prepare_request(@arguments)
621
622 Populate the context object from the request object.
623
624 =cut
625
626 sub prepare_request {
627     my ($self, $ctx, %args) = @_;
628     $self->_set_env($args{env});
629 }
630
631 =head2 $self->prepare_uploads($c)
632
633 =cut
634
635 sub prepare_uploads {
636     my ( $self, $c ) = @_;
637
638     my $request = $c->request;
639     return unless $request->_body;
640
641     my $uploads = $request->_body->upload;
642     my $parameters = $request->parameters;
643     foreach my $name (keys %$uploads) {
644         my $files = $uploads->{$name};
645         my @uploads;
646         for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
647             my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
648             my $u = Catalyst::Request::Upload->new
649               (
650                size => $upload->{size},
651                type => $headers->content_type,
652                headers => $headers,
653                tempname => $upload->{tempname},
654                filename => $upload->{filename},
655               );
656             push @uploads, $u;
657         }
658         $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
659
660         # support access to the filename as a normal param
661         my @filenames = map { $_->{filename} } @uploads;
662         # append, if there's already params with this name
663         if (exists $parameters->{$name}) {
664             if (ref $parameters->{$name} eq 'ARRAY') {
665                 push @{ $parameters->{$name} }, @filenames;
666             }
667             else {
668                 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
669             }
670         }
671         else {
672             $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
673         }
674     }
675 }
676
677 =head2 $self->prepare_write($c)
678
679 Abstract method. Implemented by the engines.
680
681 =cut
682
683 sub prepare_write { }
684
685 =head2 $self->read($c, [$maxlength])
686
687 Reads from the input stream by calling C<< $self->read_chunk >>.
688
689 Maintains the read_length and read_position counters as data is read.
690
691 =cut
692
693 sub read {
694     my ( $self, $c, $maxlength ) = @_;
695
696     my $remaining = $self->read_length - $self->read_position;
697     $maxlength ||= $CHUNKSIZE;
698
699     # Are we done reading?
700     if ( $remaining <= 0 ) {
701         $self->finalize_read($c);
702         return;
703     }
704
705     my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
706     my $rc = $self->read_chunk( $c, my $buffer, $readlen );
707     if ( defined $rc ) {
708         if (0 == $rc) { # Nothing more to read even though Content-Length
709                         # said there should be. FIXME - Warn in the log here?
710             $self->finalize_read;
711             return;
712         }
713         $self->read_position( $self->read_position + $rc );
714         return $buffer;
715     }
716     else {
717         Catalyst::Exception->throw(
718             message => "Unknown error reading input: $!" );
719     }
720 }
721
722 =head2 $self->read_chunk($c, $buffer, $length)
723
724 Each engine implements read_chunk as its preferred way of reading a chunk
725 of data. Returns the number of bytes read. A return of 0 indicates that
726 there is no more data to be read.
727
728 =cut
729
730 sub read_chunk { }
731
732 =head2 $self->read_length
733
734 The length of input data to be read.  This is obtained from the Content-Length
735 header.
736
737 =head2 $self->read_position
738
739 The amount of input data that has already been read.
740
741 =head2 $self->run($c)
742
743 Start the engine. Implemented by the various engine classes.
744
745 =cut
746
747 sub run {
748     my ($self, $app) = @_;
749
750     return sub {
751         my ($env) = @_;
752
753         return sub {
754             my ($respond) = @_;
755             $self->_set_response_cb($respond);
756             $app->handle_request(env => $env);
757         };
758     };
759 }
760
761 =head2 $self->write($c, $buffer)
762
763 Writes the buffer to the client.
764
765 =cut
766
767 sub write {
768     my ( $self, $c, $buffer ) = @_;
769
770     unless ( $self->_prepared_write ) {
771         $self->prepare_write($c);
772         $self->_prepared_write(1);
773     }
774
775     return 0 if !defined $buffer;
776
777     my $len = length($buffer);
778     $self->_writer->write($buffer);
779
780     return $len;
781 }
782
783 =head2 $self->unescape_uri($uri)
784
785 Unescapes a given URI using the most efficient method available.  Engines such
786 as Apache may implement this using Apache's C-based modules, for example.
787
788 =cut
789
790 sub unescape_uri {
791     my ( $self, $str ) = @_;
792
793     $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
794
795     return $str;
796 }
797
798 =head2 $self->finalize_output
799
800 <obsolete>, see finalize_body
801
802 =head2 $self->env
803
804 Hash containing enviroment variables including many special variables inserted
805 by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
806
807 Before accesing enviroment variables consider whether the same information is
808 not directly available via Catalyst objects $c->request, $c->engine ...
809
810 BEWARE: If you really need to access some enviroment variable from your Catalyst
811 application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
812 as in some enviroments the %ENV hash does not contain what you would expect.
813
814 =head1 AUTHORS
815
816 Catalyst Contributors, see Catalyst.pm
817
818 =head1 COPYRIGHT
819
820 This library is free software. You can redistribute it and/or modify it under
821 the same terms as Perl itself.
822
823 =cut
824
825 1;