removed plack.request keys
[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         return;
511     }
512
513     my %query;
514
515     # replace semi-colons
516     $query_string =~ s/;/&/g;
517
518     my @params = grep { length $_ } split /&/, $query_string;
519
520     for my $item ( @params ) {
521
522         my ($param, $value)
523             = map { $self->unescape_uri($_) }
524               split( /=/, $item, 2 );
525
526         $param = $self->unescape_uri($item) unless defined $param;
527
528         if ( exists $query{$param} ) {
529             if ( ref $query{$param} ) {
530                 push @{ $query{$param} }, $value;
531             }
532             else {
533                 $query{$param} = [ $query{$param}, $value ];
534             }
535         }
536         else {
537             $query{$param} = $value;
538         }
539     }
540
541     $c->request->query_parameters( 
542       $c->request->_use_hash_multivalue ?
543         Hash::MultiValue->from_mixed(\%query) :
544         \%query);
545 }
546
547 =head2 $self->prepare_read($c)
548
549 Prepare to read by initializing the Content-Length from headers.
550
551 =cut
552
553 sub prepare_read {
554     my ( $self, $c ) = @_;
555
556     # Initialize the amount of data we think we need to read
557     $c->request->_read_length;
558 }
559
560 =head2 $self->prepare_request(@arguments)
561
562 Populate the context object from the request object.
563
564 =cut
565
566 sub prepare_request {
567     my ($self, $ctx, %args) = @_;
568     $ctx->log->psgienv($args{env}) if $ctx->log->can('psgienv');
569     $ctx->request->_set_env($args{env});
570     $self->_set_env($args{env}); # Nasty back compat!
571     $ctx->response->_set_response_cb($args{response_cb});
572 }
573
574 =head2 $self->prepare_uploads($c)
575
576 =cut
577
578 sub prepare_uploads {
579     my ( $self, $c ) = @_;
580
581     my $request = $c->request;
582     return unless $request->_body;
583
584     my $uploads = $request->_body->upload;
585     my $parameters = $request->parameters;
586     foreach my $name (keys %$uploads) {
587         my $files = $uploads->{$name};
588         my @uploads;
589         for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
590             my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
591             my $u = Catalyst::Request::Upload->new
592               (
593                size => $upload->{size},
594                type => scalar $headers->content_type,
595                headers => $headers,
596                tempname => $upload->{tempname},
597                filename => $upload->{filename},
598               );
599             push @uploads, $u;
600         }
601         $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
602
603         # support access to the filename as a normal param
604         my @filenames = map { $_->{filename} } @uploads;
605         # append, if there's already params with this name
606         if (exists $parameters->{$name}) {
607             if (ref $parameters->{$name} eq 'ARRAY') {
608                 push @{ $parameters->{$name} }, @filenames;
609             }
610             else {
611                 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
612             }
613         }
614         else {
615             $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
616         }
617     }
618 }
619
620 =head2 $self->write($c, $buffer)
621
622 Writes the buffer to the client.
623
624 =cut
625
626 sub write {
627     my ( $self, $c, $buffer ) = @_;
628
629     $c->response->write($buffer);
630 }
631
632 =head2 $self->read($c, [$maxlength])
633
634 Reads from the input stream by calling C<< $self->read_chunk >>.
635
636 Maintains the read_length and read_position counters as data is read.
637
638 =cut
639
640 sub read {
641     my ( $self, $c, $maxlength ) = @_;
642
643     $c->request->read($maxlength);
644 }
645
646 =head2 $self->read_chunk($c, \$buffer, $length)
647
648 Each engine implements read_chunk as its preferred way of reading a chunk
649 of data. Returns the number of bytes read. A return of 0 indicates that
650 there is no more data to be read.
651
652 =cut
653
654 sub read_chunk {
655     my ($self, $ctx) = (shift, shift);
656     return $ctx->request->read_chunk(@_);
657 }
658
659 =head2 $self->run($app, $server)
660
661 Start the engine. Builds a PSGI application and calls the
662 run method on the server passed in, which then causes the
663 engine to loop, handling requests..
664
665 =cut
666
667 sub run {
668     my ($self, $app, $psgi, @args) = @_;
669     # @args left here rather than just a $options, $server for back compat with the
670     # old style scripts which send a few args, then a hashref
671
672     # They should never actually be used in the normal case as the Plack engine is
673     # passed in got all the 'standard' args via the loader in the script already.
674
675     # FIXME - we should stash the options in an attribute so that custom args
676     # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
677     my $server = pop @args if (scalar @args && blessed $args[-1]);
678     my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
679     # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
680     if (scalar @args && !ref($args[0])) {
681         if (my $listen = shift @args) {
682             $options->{listen} ||= [$listen];
683         }
684     }
685     if (! $server ) {
686         $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
687         # We're not being called from a script, so auto detect what backend to
688         # run on.  This should never happen, as mod_perl never calls ->run,
689         # instead the $app->handle method is called per request.
690         $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
691     }
692     $app->run_options($options);
693     $server->run($psgi, $options);
694 }
695
696 =head2 build_psgi_app ($app, @args)
697
698 Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
699
700 =cut
701
702 sub build_psgi_app {
703     my ($self, $app, @args) = @_;
704
705     return sub {
706         my ($env) = @_;
707
708         return sub {
709             my ($respond) = @_;
710             confess("Did not get a response callback for writer, cannot continue") unless $respond;
711             $app->handle_request(env => $env, response_cb => $respond);
712         };
713     };
714 }
715
716 =head2 $self->unescape_uri($uri)
717
718 Unescapes a given URI using the most efficient method available.  Engines such
719 as Apache may implement this using Apache's C-based modules, for example.
720
721 =cut
722
723 sub unescape_uri {
724     my ( $self, $str ) = @_;
725
726     $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
727
728     return $str;
729 }
730
731 =head2 $self->finalize_output
732
733 <obsolete>, see finalize_body
734
735 =head2 $self->env
736
737 Hash containing environment variables including many special variables inserted
738 by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
739
740 Before accessing environment variables consider whether the same information is
741 not directly available via Catalyst objects $c->request, $c->engine ...
742
743 BEWARE: If you really need to access some environment variable from your Catalyst
744 application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
745 as in some environments the %ENV hash does not contain what you would expect.
746
747 =head1 AUTHORS
748
749 Catalyst Contributors, see Catalyst.pm
750
751 =head1 COPYRIGHT
752
753 This library is free software. You can redistribute it and/or modify it under
754 the same terms as Perl itself.
755
756 =cut
757
758 __PACKAGE__->meta->make_immutable;
759
760 1;