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