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