1 package Catalyst::Engine;
4 with 'MooseX::Emulate::Class::Accessor::Fast';
6 use CGI::Simple::Cookie;
7 use Data::Dump qw/dump/;
8 use Errno 'EWOULDBLOCK';
14 use Catalyst::EngineLoader;
16 use Plack::Request::Upload;
20 use namespace::clean -except => 'meta';
22 # Amount of data to read from input on each pass
23 our $CHUNKSIZE = 64 * 1024;
25 # XXX - Only here for Engine::PSGI compat
26 sub prepare_connection {
27 my ($self, $ctx) = @_;
28 $ctx->request->prepare_connection;
33 Catalyst::Engine - The Catalyst Engine
44 =head2 $self->finalize_body($c)
46 Finalize body. Prints the response output as blocking stream if it looks like
47 a filehandle, otherwise write it out all in one go. If there is no body in
48 the response, we assume you are handling it 'manually', such as for nonblocking
49 style or asynchronous streaming responses. You do this by calling L</write>
50 several times (which sends HTTP headers if needed) or you close over
51 C<< $response->write_fh >>.
53 See L<Catalyst::Response/write> and L<Catalyst::Response/write_fh> for more.
58 my ( $self, $c ) = @_;
59 my $res = $c->response; # We use this all over
61 ## If we've asked for the write 'filehandle' that means the application is
62 ## doing something custom and is expected to close the response
63 return if $res->_has_write_fh;
65 my $body = $res->body; # save some typing
66 if($res->_has_response_cb) {
67 ## we have not called the response callback yet, so we are safe to send
68 ## the whole body to PSGI
71 $res->headers->scan(sub { push @headers, @_ });
73 # We need to figure out what kind of body we have and normalize it to something
76 # Handle objects first
78 if($body->can('getline')) {
79 # Body is an IO handle that meets the PSGI spec. Nothing to normalize
80 } elsif($body->can('read')) {
82 # In the past, Catalyst only looked for ->read not ->getline. It is very possible
83 # that one might have an object that respected read but did not have getline.
84 # As a result, we need to handle this case for backcompat.
86 # We will just do the old loop for now. In a future version of Catalyst this support
87 # will be removed and one will have to rewrite their custom object or use
88 # Plack::Middleware::AdaptFilehandleRead. In anycase support for this is officially
89 # deprecated and described as such as of 5.90060
93 $got = read $body, my ($buffer), $CHUNKSIZE;
94 $got = 0 unless $self->write($c, $buffer );
100 # Looks like for backcompat reasons we need to be able to deal
101 # with stringyfiable objects.
105 if( (ref($body) eq 'GLOB') or (ref($body) eq 'ARRAY')) {
106 # Again, PSGI can just accept this, no transform needed. We don't officially
107 # document the body as arrayref at this time (and there's not specific test
108 # cases. we support it because it simplifies some plack compatibility logic
109 # and we might make it official at some point.
111 $c->log->error("${\ref($body)} is not a valid value for Response->body");
115 # Body is defined and not an object or reference. We assume a simple value
116 # and wrap it in an array for PSGI
124 $res->_response_cb->([ $res->status, \@headers, $body]);
125 $res->_clear_response_cb;
128 ## Now, if there's no response callback anymore, that means someone has
129 ## called ->write in order to stream 'some stuff along the way'. I think
130 ## for backcompat we still need to handle a ->body. I guess I could see
131 ## someone calling ->write to presend some stuff, and then doing the rest
132 ## via ->body, like in a template.
134 ## We'll just use the old, existing code for this (or most of it)
136 if(my $body = $res->body) {
138 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
140 ## In this case we have no choice and will fall back on the old
141 ## manual streaming stuff. Not optimal. This is deprecated as of 5.900560+
145 $got = read $body, my ($buffer), $CHUNKSIZE;
146 $got = 0 unless $self->write($c, $buffer );
153 # Case where body was set afgter calling ->write. We'd prefer not to
154 # support this, but I can see some use cases with the way most of the
157 $self->write($c, $body );
161 $res->_writer->close;
168 =head2 $self->finalize_cookies($c)
170 Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
175 sub finalize_cookies {
176 my ( $self, $c ) = @_;
179 my $response = $c->response;
181 foreach my $name (keys %{ $response->cookies }) {
183 my $val = $response->cookies->{$name};
188 : CGI::Simple::Cookie->new(
190 -value => $val->{value},
191 -expires => $val->{expires},
192 -domain => $val->{domain},
193 -path => $val->{path},
194 -secure => $val->{secure} || 0,
195 -httponly => $val->{httponly} || 0,
198 if (!defined $cookie) {
199 $c->trace(1, "undef passed in '$name' cookie value - not setting cookie");
203 push @cookies, $cookie->as_string;
206 for my $cookie (@cookies) {
207 $response->headers->push_header( 'Set-Cookie' => $cookie );
211 =head2 $self->finalize_error($c)
213 Output an appropriate error message. Called if there's an error in $c
214 after the dispatch has finished. Will output debug messages if Catalyst
215 is in debug mode, or a `please come back later` message otherwise.
219 sub _dump_error_page_element {
220 my ($self, $i, $element) = @_;
221 my ($name, $val) = @{ $element };
223 # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
224 # scrolling. Suggestions for more pleasant ways to do this welcome.
225 local $val->{'__MOP__'} = "Stringified: "
226 . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
228 my $text = encode_entities( dump( $val ));
229 sprintf <<"EOF", $name, $text;
230 <h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
232 <pre wrap="">%s</pre>
238 my ( $self, $c ) = @_;
240 $c->res->content_type('text/html; charset=utf-8');
241 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
243 # Prevent Catalyst::Plugin::Unicode::Encoding from running.
244 # This is a little nasty, but it's the best way to be clean whether or
245 # not the user has an encoding plugin.
247 if ($c->can('encoding')) {
251 my ( $title, $error, $infos );
252 ## For now we keep debug mode for turning on the default
253 ## debugging error screen - jnap.
257 $error = join '', map {
258 '<p><code class="error">'
259 . encode_entities($_)
262 $error ||= 'No output';
263 $error = qq{<pre wrap="">$error</pre>};
264 $title = $name = "$name on Catalyst $Catalyst::VERSION";
265 $name = "<h1>$name</h1>";
267 # Don't show context in the dump
268 $c->res->_clear_context;
270 # Don't show body parser in the dump
271 $c->req->_clear_body;
275 for my $dump ( $c->dump_these ) {
276 push @infos, $self->_dump_error_page_element($i, $dump);
279 $infos = join "\n", @infos;
286 (en) Please come back later
287 (fr) SVP veuillez revenir plus tard
288 (de) Bitte versuchen sie es spaeter nocheinmal
289 (at) Konnten's bitt'schoen spaeter nochmal reinschauen
290 (no) Vennligst prov igjen senere
291 (dk) Venligst prov igen senere
292 (pl) Prosze sprobowac pozniej
293 (pt) Por favor volte mais tarde
294 (ru) Попробуйте еще раз позже
295 (ua) Спробуйте ще раз пізніше
300 $c->res->body( <<"" );
301 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
302 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
303 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
305 <meta http-equiv="Content-Language" content="en" />
306 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
307 <title>$title</title>
308 <script type="text/javascript">
310 function toggleDump (dumpElement) {
311 var e = document.getElementById( dumpElement );
312 if (e.style.display == "none") {
313 e.style.display = "";
316 e.style.display = "none";
321 <style type="text/css">
323 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
324 Tahoma, Arial, helvetica, sans-serif;
326 background-color: #eee;
330 :link, :link:hover, :visited, :visited:hover {
335 background-color: #ccc;
336 border: 1px solid #aaa;
341 background-color: #cce;
342 border: 1px solid #755;
348 background-color: #eee;
349 border: 1px solid #575;
355 background-color: #cce;
356 border: 1px solid #557;
365 div.name h1, div.error p {
373 text-decoration: underline;
379 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
380 /* Browser specific (not valid) styles to make preformatted text wrap */
382 white-space: pre-wrap; /* css-3 */
383 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
384 white-space: -pre-wrap; /* Opera 4-6 */
385 white-space: -o-pre-wrap; /* Opera 7 */
386 word-wrap: break-word; /* Internet Explorer 5.5+ */
392 <div class="error">$error</div>
393 <div class="infos">$infos</div>
394 <div class="name">$name</div>
399 # Trick IE. Old versions of IE would display their own error page instead
400 # of ours if we'd give it less than 512 bytes.
401 $c->res->{body} .= ( ' ' x 512 );
403 $c->res->{body} = Encode::encode("UTF-8", $c->res->{body});
406 $c->res->status(500);
409 =head2 $self->finalize_headers($c)
411 Allows engines to write headers to response
415 sub finalize_headers {
416 my ($self, $ctx) = @_;
418 $ctx->finalize_headers unless $ctx->response->finalized_headers;
422 =head2 $self->finalize_uploads($c)
424 Clean up after uploads, deleting temp files.
428 sub finalize_uploads {
429 my ( $self, $c ) = @_;
431 # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
432 # on the HTTP::Body object.
433 my $request = $c->request;
434 foreach my $key (keys %{ $request->uploads }) {
435 my $upload = $request->uploads->{$key};
436 unlink grep { -e $_ } map { $_->tempname }
437 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
442 =head2 $self->prepare_body($c)
444 sets up the L<Catalyst::Request> object body using L<HTTP::Body>
449 my ( $self, $c ) = @_;
451 $c->request->prepare_body;
454 =head2 $self->prepare_body_chunk($c)
456 Add a chunk to the request body.
460 # XXX - Can this be deleted?
461 sub prepare_body_chunk {
462 my ( $self, $c, $chunk ) = @_;
464 $c->request->prepare_body_chunk($chunk);
467 =head2 $self->prepare_body_parameters($c)
469 Sets up parameters from body.
473 sub prepare_body_parameters {
474 my ( $self, $c ) = @_;
476 $c->request->prepare_body_parameters;
479 =head2 $self->prepare_parameters($c)
481 Sets up parameters from query and post parameters.
482 If parameters have already been set up will clear
483 existing parameters and set up again.
487 sub prepare_parameters {
488 my ( $self, $c ) = @_;
490 $c->request->_clear_parameters;
491 return $c->request->parameters;
494 =head2 $self->prepare_path($c)
496 abstract method, implemented by engines.
501 my ($self, $ctx) = @_;
503 my $env = $ctx->request->env;
505 my $scheme = $ctx->request->secure ? 'https' : 'http';
506 my $host = $env->{HTTP_HOST} || $env->{SERVER_NAME};
507 my $port = $env->{SERVER_PORT} || 80;
508 my $base_path = $env->{SCRIPT_NAME} || "/";
510 # set the request URI
512 if (!$ctx->config->{use_request_uri_for_path}) {
513 my $path_info = $env->{PATH_INFO};
514 if ( exists $env->{REDIRECT_URL} ) {
515 $base_path = $env->{REDIRECT_URL};
516 $base_path =~ s/\Q$path_info\E$//;
518 $path = $base_path . $path_info;
520 $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
521 $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
524 my $req_uri = $env->{REQUEST_URI};
525 $req_uri =~ s/\?.*$//;
530 # Using URI directly is way too slow, so we construct the URLs manually
531 my $uri_class = "URI::$scheme";
533 # HTTP_HOST will include the port even if it's 80/443
534 $host =~ s/:(?:80|443)$//;
536 if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
540 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
541 my $uri = $scheme . '://' . $host . '/' . $path . $query;
543 $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
546 # base must end in a slash
547 $base_path .= '/' unless $base_path =~ m{/$};
549 my $base_uri = $scheme . '://' . $host . $base_path;
551 $ctx->request->base( bless \$base_uri, $uri_class );
556 =head2 $self->prepare_request($c)
558 =head2 $self->prepare_query_parameters($c)
560 process the query string and extract query parameters.
564 sub prepare_query_parameters {
566 my $env = $c->request->env;
568 if(my $query_obj = $env->{'plack.request.query'}) {
569 $c->request->query_parameters(
570 $c->request->_use_hash_multivalue ?
572 $query_obj->as_hashref_mixed);
576 my $query_string = exists $env->{QUERY_STRING}
577 ? $env->{QUERY_STRING}
580 # Check for keywords (no = signs)
581 # (yes, index() is faster than a regex :))
582 if ( index( $query_string, '=' ) < 0 ) {
583 $c->request->query_keywords($self->unescape_uri($query_string));
589 # replace semi-colons
590 $query_string =~ s/;/&/g;
592 my @params = grep { length $_ } split /&/, $query_string;
594 for my $item ( @params ) {
597 = map { $self->unescape_uri($_) }
598 split( /=/, $item, 2 );
600 $param = $self->unescape_uri($item) unless defined $param;
602 if ( exists $query{$param} ) {
603 if ( ref $query{$param} ) {
604 push @{ $query{$param} }, $value;
607 $query{$param} = [ $query{$param}, $value ];
611 $query{$param} = $value;
615 $c->request->query_parameters(
616 $c->request->_use_hash_multivalue ?
617 Hash::MultiValue->from_mixed(\%query) :
621 =head2 $self->prepare_read($c)
623 Prepare to read by initializing the Content-Length from headers.
628 my ( $self, $c ) = @_;
630 # Initialize the amount of data we think we need to read
631 $c->request->_read_length;
634 =head2 $self->prepare_request(@arguments)
636 Populate the context object from the request object.
640 sub prepare_request {
641 my ($self, $ctx, %args) = @_;
642 $ctx->log->psgienv($args{env}) if $ctx->log->can('psgienv');
643 $ctx->request->_set_env($args{env});
644 $ctx->response->_set_response_cb($args{response_cb});
647 =head2 $self->prepare_uploads($c)
651 sub prepare_uploads {
652 my ( $self, $c ) = @_;
654 my $request = $c->request;
655 return unless $request->_body;
657 my $uploads = $request->_body->upload;
658 my $parameters = $request->parameters;
659 foreach my $name (keys %$uploads) {
660 my $files = $uploads->{$name};
662 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
663 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
664 my $u = Catalyst::Request::Upload->new
666 size => $upload->{size},
667 type => scalar $headers->content_type,
669 tempname => $upload->{tempname},
670 filename => $upload->{filename},
674 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
676 # support access to the filename as a normal param
677 my @filenames = map { $_->{filename} } @uploads;
678 # append, if there's already params with this name
679 if (exists $parameters->{$name}) {
680 if (ref $parameters->{$name} eq 'ARRAY') {
681 push @{ $parameters->{$name} }, @filenames;
684 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
688 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
693 =head2 $self->write($c, $buffer)
695 Writes the buffer to the client.
700 my ( $self, $c, $buffer ) = @_;
702 $c->response->write($buffer);
705 =head2 $self->read($c, [$maxlength])
707 Reads from the input stream by calling C<< $self->read_chunk >>.
709 Maintains the read_length and read_position counters as data is read.
714 my ( $self, $c, $maxlength ) = @_;
716 $c->request->read($maxlength);
719 =head2 $self->read_chunk($c, \$buffer, $length)
721 Each engine implements read_chunk as its preferred way of reading a chunk
722 of data. Returns the number of bytes read. A return of 0 indicates that
723 there is no more data to be read.
728 my ($self, $ctx) = (shift, shift);
729 return $ctx->request->read_chunk(@_);
732 =head2 $self->run($app, $server)
734 Start the engine. Builds a PSGI application and calls the
735 run method on the server passed in, which then causes the
736 engine to loop, handling requests..
741 my ($self, $app, $psgi, @args) = @_;
742 # @args left here rather than just a $options, $server for back compat with the
743 # old style scripts which send a few args, then a hashref
745 # They should never actually be used in the normal case as the Plack engine is
746 # passed in got all the 'standard' args via the loader in the script already.
748 # FIXME - we should stash the options in an attribute so that custom args
749 # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
750 my $server = pop @args if (scalar @args && blessed $args[-1]);
751 my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
752 # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
753 if (scalar @args && !ref($args[0])) {
754 if (my $listen = shift @args) {
755 $options->{listen} ||= [$listen];
759 $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
760 # We're not being called from a script, so auto detect what backend to
761 # run on. This should never happen, as mod_perl never calls ->run,
762 # instead the $app->handle method is called per request.
763 $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
765 $app->run_options($options);
766 $server->run($psgi, $options);
769 =head2 build_psgi_app ($app, @args)
771 Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
776 my ($self, $app, @args) = @_;
783 confess("Did not get a response callback for writer, cannot continue") unless $respond;
784 $app->handle_request(env => $env, response_cb => $respond);
789 =head2 $self->unescape_uri($uri)
791 Unescapes a given URI using the most efficient method available. Engines such
792 as Apache may implement this using Apache's C-based modules, for example.
797 my ( $self, $str ) = @_;
799 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
804 =head2 $self->finalize_output
806 <obsolete>, see finalize_body
810 Hash containing environment variables including many special variables inserted
811 by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
813 Before accessing environment variables consider whether the same information is
814 not directly available via Catalyst objects $c->request, $c->engine ...
816 BEWARE: If you really need to access some environment variable from your Catalyst
817 application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
818 as in some environments the %ENV hash does not contain what you would expect.
822 Catalyst Contributors, see Catalyst.pm
826 This library is free software. You can redistribute it and/or modify it under
827 the same terms as Perl itself.
831 __PACKAGE__->meta->make_immutable;