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