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