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