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