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