Unpossible, but people keep seeing it
[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
fbcc39ad 183 # Don't show body parser in the dump
0f56bbcf 184 $c->req->_clear_body;
fbcc39ad 185
c6ef5e69 186 my @infos;
187 my $i = 0;
c6ef5e69 188 for my $dump ( $c->dump_these ) {
c96cdcef 189 push @infos, $self->_dump_error_page_element($i, $dump);
c6ef5e69 190 $i++;
191 }
192 $infos = join "\n", @infos;
969647fd 193 }
194 else {
195 $title = $name;
196 $error = '';
197 $infos = <<"";
198<pre>
199(en) Please come back later
0c2b4ac0 200(fr) SVP veuillez revenir plus tard
969647fd 201(de) Bitte versuchen sie es spaeter nocheinmal
d82cc9ae 202(at) Konnten's bitt'schoen spaeter nochmal reinschauen
969647fd 203(no) Vennligst prov igjen senere
d82cc9ae 204(dk) Venligst prov igen senere
205(pl) Prosze sprobowac pozniej
2f381252 206(pt) Por favor volte mais tarde
b31c0f2e 207(ru) Попробуйте еще раз позже
208(ua) Спробуйте ще раз пізніше
969647fd 209</pre>
210
211 $name = '';
212 }
e060fe05 213 $c->res->body( <<"" );
7299a7b4 214<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
215 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
216<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
969647fd 217<head>
7299a7b4 218 <meta http-equiv="Content-Language" content="en" />
219 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 220 <title>$title</title>
7299a7b4 221 <script type="text/javascript">
c6ef5e69 222 <!--
223 function toggleDump (dumpElement) {
7299a7b4 224 var e = document.getElementById( dumpElement );
225 if (e.style.display == "none") {
226 e.style.display = "";
c6ef5e69 227 }
228 else {
7299a7b4 229 e.style.display = "none";
c6ef5e69 230 }
231 }
232 -->
233 </script>
969647fd 234 <style type="text/css">
235 body {
236 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
237 Tahoma, Arial, helvetica, sans-serif;
34d28dfd 238 color: #333;
969647fd 239 background-color: #eee;
240 margin: 0px;
241 padding: 0px;
242 }
c6ef5e69 243 :link, :link:hover, :visited, :visited:hover {
34d28dfd 244 color: #000;
c6ef5e69 245 }
969647fd 246 div.box {
9619f23c 247 position: relative;
969647fd 248 background-color: #ccc;
249 border: 1px solid #aaa;
250 padding: 4px;
251 margin: 10px;
969647fd 252 }
253 div.error {
34d28dfd 254 background-color: #cce;
969647fd 255 border: 1px solid #755;
256 padding: 8px;
257 margin: 4px;
258 margin-bottom: 10px;
969647fd 259 }
260 div.infos {
34d28dfd 261 background-color: #eee;
969647fd 262 border: 1px solid #575;
263 padding: 8px;
264 margin: 4px;
265 margin-bottom: 10px;
969647fd 266 }
267 div.name {
34d28dfd 268 background-color: #cce;
969647fd 269 border: 1px solid #557;
270 padding: 8px;
271 margin: 4px;
969647fd 272 }
7f8e0078 273 code.error {
274 display: block;
275 margin: 1em 0;
276 overflow: auto;
7f8e0078 277 }
9619f23c 278 div.name h1, div.error p {
279 margin: 0;
280 }
281 h2 {
282 margin-top: 0;
283 margin-bottom: 10px;
284 font-size: medium;
285 font-weight: bold;
286 text-decoration: underline;
287 }
288 h1 {
289 font-size: medium;
290 font-weight: normal;
291 }
2666dd3b 292 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
293 /* Browser specific (not valid) styles to make preformatted text wrap */
b0ad47c1 294 pre {
2666dd3b 295 white-space: pre-wrap; /* css-3 */
296 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
297 white-space: -pre-wrap; /* Opera 4-6 */
298 white-space: -o-pre-wrap; /* Opera 7 */
299 word-wrap: break-word; /* Internet Explorer 5.5+ */
300 }
969647fd 301 </style>
302</head>
303<body>
304 <div class="box">
305 <div class="error">$error</div>
306 <div class="infos">$infos</div>
307 <div class="name">$name</div>
308 </div>
309</body>
310</html>
311
4b66aa19 312 # Trick IE. Old versions of IE would display their own error page instead
313 # of ours if we'd give it less than 512 bytes.
d82cc9ae 314 $c->res->{body} .= ( ' ' x 512 );
315
361ba9b2 316 $c->res->{body} = Encode::encode("UTF-8", $c->res->{body});
317
d82cc9ae 318 # Return 500
33117422 319 $c->res->status(500);
969647fd 320}
321
b5ecfcf0 322=head2 $self->finalize_headers($c)
fc7ec1d9 323
9c4288ea 324Allows engines to write headers to response
4ab87e27 325
fc7ec1d9 326=cut
327
44d28c7d 328sub finalize_headers {
329 my ($self, $ctx) = @_;
330
9c4288ea 331 $ctx->response->finalize_headers;
44d28c7d 332 return;
333}
fc7ec1d9 334
b5ecfcf0 335=head2 $self->finalize_uploads($c)
fc7ec1d9 336
4ab87e27 337Clean up after uploads, deleting temp files.
338
fc7ec1d9 339=cut
340
fbcc39ad 341sub finalize_uploads {
342 my ( $self, $c ) = @_;
99fe1710 343
671123ba 344 # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
345 # on the HTTP::Body object.
7fa2c9c1 346 my $request = $c->request;
91772de9 347 foreach my $key (keys %{ $request->uploads }) {
348 my $upload = $request->uploads->{$key};
7fa2c9c1 349 unlink grep { -e $_ } map { $_->tempname }
350 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 351 }
7fa2c9c1 352
fc7ec1d9 353}
354
b5ecfcf0 355=head2 $self->prepare_body($c)
fc7ec1d9 356
4ab87e27 357sets up the L<Catalyst::Request> object body using L<HTTP::Body>
358
fc7ec1d9 359=cut
360
fbcc39ad 361sub prepare_body {
362 my ( $self, $c ) = @_;
99fe1710 363
398f13db 364 $c->request->prepare_body;
fc7ec1d9 365}
366
b5ecfcf0 367=head2 $self->prepare_body_chunk($c)
4bd82c41 368
4ab87e27 369Add a chunk to the request body.
370
4bd82c41 371=cut
372
398f13db 373# XXX - Can this be deleted?
4bd82c41 374sub prepare_body_chunk {
375 my ( $self, $c, $chunk ) = @_;
4f5ebacd 376
398f13db 377 $c->request->prepare_body_chunk($chunk);
4bd82c41 378}
379
b5ecfcf0 380=head2 $self->prepare_body_parameters($c)
06e1b616 381
b0ad47c1 382Sets up parameters from body.
4ab87e27 383
06e1b616 384=cut
385
fbcc39ad 386sub prepare_body_parameters {
387 my ( $self, $c ) = @_;
b0ad47c1 388
398f13db 389 $c->request->prepare_body_parameters;
44d28c7d 390}
fc7ec1d9 391
b5ecfcf0 392=head2 $self->prepare_parameters($c)
fc7ec1d9 393
4ab87e27 394sets up parameters from query and post parameters.
395
fc7ec1d9 396=cut
397
fbcc39ad 398sub prepare_parameters {
399 my ( $self, $c ) = @_;
fc7ec1d9 400
1cbdfa9b 401 $c->request->parameters;
fbcc39ad 402}
403
b5ecfcf0 404=head2 $self->prepare_path($c)
fc7ec1d9 405
4ab87e27 406abstract method, implemented by engines.
407
fc7ec1d9 408=cut
409
44d28c7d 410sub prepare_path {
411 my ($self, $ctx) = @_;
412
faa02805 413 my $env = $ctx->request->env;
44d28c7d 414
415 my $scheme = $ctx->request->secure ? 'https' : 'http';
416 my $host = $env->{HTTP_HOST} || $env->{SERVER_NAME};
417 my $port = $env->{SERVER_PORT} || 80;
418 my $base_path = $env->{SCRIPT_NAME} || "/";
419
420 # set the request URI
661de072 421 my $path;
422 if (!$ctx->config->{use_request_uri_for_path}) {
4904ee27 423 my $path_info = $env->{PATH_INFO};
424 if ( exists $env->{REDIRECT_URL} ) {
425 $base_path = $env->{REDIRECT_URL};
426 $base_path =~ s/\Q$path_info\E$//;
427 }
428 $path = $base_path . $path_info;
661de072 429 $path =~ s{^/+}{};
430 $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
431 $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
432 }
433 else {
434 my $req_uri = $env->{REQUEST_URI};
435 $req_uri =~ s/\?.*$//;
436 $path = $req_uri;
437 $path =~ s{^/+}{};
438 }
44d28c7d 439
440 # Using URI directly is way too slow, so we construct the URLs manually
441 my $uri_class = "URI::$scheme";
442
443 # HTTP_HOST will include the port even if it's 80/443
444 $host =~ s/:(?:80|443)$//;
445
446 if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
447 $host .= ":$port";
448 }
449
44d28c7d 450 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
451 my $uri = $scheme . '://' . $host . '/' . $path . $query;
452
4ee03d72 453 $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
44d28c7d 454
455 # set the base URI
456 # base must end in a slash
457 $base_path .= '/' unless $base_path =~ m{/$};
458
459 my $base_uri = $scheme . '://' . $host . $base_path;
460
461 $ctx->request->base( bless \$base_uri, $uri_class );
462
463 return;
464}
fc7ec1d9 465
b5ecfcf0 466=head2 $self->prepare_request($c)
fc7ec1d9 467
b5ecfcf0 468=head2 $self->prepare_query_parameters($c)
fc7ec1d9 469
4ab87e27 470process the query string and extract query parameters.
471
fc7ec1d9 472=cut
473
e0616220 474sub prepare_query_parameters {
44d28c7d 475 my ($self, $c) = @_;
476
faa02805 477 my $env = $c->request->env;
478 my $query_string = exists $env->{QUERY_STRING}
479 ? $env->{QUERY_STRING}
44d28c7d 480 : '';
b0ad47c1 481
3b4d1251 482 # Check for keywords (no = signs)
483 # (yes, index() is faster than a regex :))
933ba403 484 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 485 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 486 return;
487 }
488
489 my %query;
e0616220 490
491 # replace semi-colons
492 $query_string =~ s/;/&/g;
b0ad47c1 493
2f381252 494 my @params = grep { length $_ } split /&/, $query_string;
e0616220 495
933ba403 496 for my $item ( @params ) {
b0ad47c1 497
498 my ($param, $value)
933ba403 499 = map { $self->unescape_uri($_) }
e5542b70 500 split( /=/, $item, 2 );
b0ad47c1 501
933ba403 502 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 503
933ba403 504 if ( exists $query{$param} ) {
505 if ( ref $query{$param} ) {
506 push @{ $query{$param} }, $value;
507 }
508 else {
509 $query{$param} = [ $query{$param}, $value ];
510 }
511 }
512 else {
513 $query{$param} = $value;
514 }
e0616220 515 }
933ba403 516 $c->request->query_parameters( \%query );
e0616220 517}
fbcc39ad 518
b5ecfcf0 519=head2 $self->prepare_read($c)
fbcc39ad 520
47b9d68e 521Prepare to read by initializing the Content-Length from headers.
4ab87e27 522
fbcc39ad 523=cut
fc7ec1d9 524
fbcc39ad 525sub prepare_read {
526 my ( $self, $c ) = @_;
4f5ebacd 527
878b821c 528 # Initialize the amount of data we think we need to read
faa02805 529 $c->request->_read_length;
fbcc39ad 530}
fc7ec1d9 531
b5ecfcf0 532=head2 $self->prepare_request(@arguments)
fc7ec1d9 533
c4a17516 534Populate the context object from the request object.
4ab87e27 535
fc7ec1d9 536=cut
537
44d28c7d 538sub prepare_request {
539 my ($self, $ctx, %args) = @_;
faa02805 540 $ctx->request->_set_env($args{env});
541 $self->_set_env($args{env}); # Nasty back compat!
542 $ctx->response->_set_response_cb($args{response_cb});
44d28c7d 543}
fc7ec1d9 544
b5ecfcf0 545=head2 $self->prepare_uploads($c)
c9afa5fc 546
fbcc39ad 547=cut
548
549sub prepare_uploads {
550 my ( $self, $c ) = @_;
7fa2c9c1 551
552 my $request = $c->request;
0f56bbcf 553 return unless $request->_body;
7fa2c9c1 554
0f56bbcf 555 my $uploads = $request->_body->upload;
7fa2c9c1 556 my $parameters = $request->parameters;
91772de9 557 foreach my $name (keys %$uploads) {
558 my $files = $uploads->{$name};
fbcc39ad 559 my @uploads;
7fa2c9c1 560 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
561 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
562 my $u = Catalyst::Request::Upload->new
563 (
564 size => $upload->{size},
a160c98d 565 type => scalar $headers->content_type,
7fa2c9c1 566 headers => $headers,
567 tempname => $upload->{tempname},
568 filename => $upload->{filename},
569 );
fbcc39ad 570 push @uploads, $u;
571 }
7fa2c9c1 572 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 573
c4bed79a 574 # support access to the filename as a normal param
575 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 576 # append, if there's already params with this name
7fa2c9c1 577 if (exists $parameters->{$name}) {
578 if (ref $parameters->{$name} eq 'ARRAY') {
579 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 580 }
581 else {
7fa2c9c1 582 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 583 }
584 }
585 else {
7fa2c9c1 586 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 587 }
fbcc39ad 588 }
589}
590
767480fd 591=head2 $self->write($c, $buffer)
c9afa5fc 592
767480fd 593Writes the buffer to the client.
4ab87e27 594
c9afa5fc 595=cut
596
767480fd 597sub write {
598 my ( $self, $c, $buffer ) = @_;
599
600 $c->response->write($buffer);
601}
fbcc39ad 602
b5ecfcf0 603=head2 $self->read($c, [$maxlength])
fbcc39ad 604
ea72fece 605Reads from the input stream by calling C<< $self->read_chunk >>.
606
607Maintains the read_length and read_position counters as data is read.
608
fbcc39ad 609=cut
610
611sub read {
612 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 613
f083854e 614 $c->request->read($maxlength);
fbcc39ad 615}
fc7ec1d9 616
87f50436 617=head2 $self->read_chunk($c, \$buffer, $length)
23f9d934 618
10011c19 619Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 620of data. Returns the number of bytes read. A return of 0 indicates that
621there is no more data to be read.
fc7ec1d9 622
fbcc39ad 623=cut
61b1e958 624
e6b46d80 625sub read_chunk {
ce7abbda 626 my ($self, $ctx) = (shift, shift);
87f50436 627 return $ctx->request->read_chunk(@_);
e6b46d80 628}
61b1e958 629
9560b708 630=head2 $self->run($app, $server)
63b763c5 631
9560b708 632Start the engine. Builds a PSGI application and calls the
acbecf08 633run method on the server passed in, which then causes the
634engine to loop, handling requests..
4ab87e27 635
fbcc39ad 636=cut
fc7ec1d9 637
44d28c7d 638sub run {
51857616 639 my ($self, $app, $psgi, @args) = @_;
acbecf08 640 # @args left here rather than just a $options, $server for back compat with the
641 # old style scripts which send a few args, then a hashref
642
643 # They should never actually be used in the normal case as the Plack engine is
644 # passed in got all the 'standard' args via the loader in the script already.
645
646 # FIXME - we should stash the options in an attribute so that custom args
647 # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
1e5dad00 648 my $server = pop @args if (scalar @args && blessed $args[-1]);
649 my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
ccb13b15 650 # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
651 if (scalar @args && !ref($args[0])) {
652 if (my $listen = shift @args) {
653 $options->{listen} ||= [$listen];
654 }
655 }
acbecf08 656 if (! $server ) {
f7a3f8fd 657 $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
f7f55b2f 658 # We're not being called from a script, so auto detect what backend to
659 # run on. This should never happen, as mod_perl never calls ->run,
660 # instead the $app->handle method is called per request.
acbecf08 661 $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
662 }
aee7cdcc 663 $app->run_options($options);
acbecf08 664 $server->run($psgi, $options);
a1791811 665}
44d28c7d 666
9560b708 667=head2 build_psgi_app ($app, @args)
668
e3f6b891 669Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
9560b708 670
671=cut
672
22a5833d 673sub build_psgi_app {
a1791811 674 my ($self, $app, @args) = @_;
c2f4a965 675
fcffcb05 676 return sub {
44d28c7d 677 my ($env) = @_;
678
679 return sub {
680 my ($respond) = @_;
f2b83422 681 confess("Did not get a response callback for writer, cannot continiue") unless $respond;
faa02805 682 $app->handle_request(env => $env, response_cb => $respond);
44d28c7d 683 };
684 };
685}
fc7ec1d9 686
933ba403 687=head2 $self->unescape_uri($uri)
688
6a44fe01 689Unescapes a given URI using the most efficient method available. Engines such
690as Apache may implement this using Apache's C-based modules, for example.
933ba403 691
692=cut
693
694sub unescape_uri {
8c7d83e1 695 my ( $self, $str ) = @_;
7d22a537 696
697 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
698
8c7d83e1 699 return $str;
933ba403 700}
34d28dfd 701
4ab87e27 702=head2 $self->finalize_output
703
704<obsolete>, see finalize_body
705
0c76ec45 706=head2 $self->env
707
6356febf 708Hash containing environment variables including many special variables inserted
0c76ec45 709by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
710
6356febf 711Before accessing environment variables consider whether the same information is
0c76ec45 712not directly available via Catalyst objects $c->request, $c->engine ...
713
6356febf 714BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 715application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
ae7da8f5 716as in some environments the %ENV hash does not contain what you would expect.
0c76ec45 717
fbcc39ad 718=head1 AUTHORS
719
2f381252 720Catalyst Contributors, see Catalyst.pm
fc7ec1d9 721
722=head1 COPYRIGHT
723
536bee89 724This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 725the same terms as Perl itself.
726
727=cut
728
7291;