more plack compat
[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 ();
191665f3 16use Plack::Request::Upload;
17use Hash::MultiValue;
361ba9b2 18use utf8;
fbcc39ad 19
d495753a 20use namespace::clean -except => 'meta';
21
faa02805 22# Amount of data to read from input on each pass
23our $CHUNKSIZE = 64 * 1024;
a50e5b46 24
faa02805 25# XXX - this is only here for compat, do not use!
26has env => ( is => 'rw', writer => '_set_env' );
0c6352ff 27my $WARN_ABOUT_ENV = 0;
28around 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
ddcd2fc4 38# XXX - Only here for Engine::PSGI compat
39sub prepare_connection {
40 my ($self, $ctx) = @_;
41 $ctx->request->prepare_connection;
42}
4bd82c41 43
fc7ec1d9 44=head1 NAME
45
46Catalyst::Engine - The Catalyst Engine
47
48=head1 SYNOPSIS
49
50See L<Catalyst>.
51
52=head1 DESCRIPTION
53
23f9d934 54=head1 METHODS
fc7ec1d9 55
cd3bb248 56
b5ecfcf0 57=head2 $self->finalize_body($c)
06e1b616 58
717fc5c9 59Finalize body. Prints the response output as blocking stream if it looks like
60a filehandle, otherwise write it out all in one go. If there is no body in
61the response, we assume you are handling it 'manually', such as for nonblocking
e37f92f5 62style or asynchronous streaming responses. You do this by calling L<\write>
4e6e0ab2 63several times (which sends HTTP headers if needed) or you close over C<$response->write_fh>.
e37f92f5 64
65See L<Catalyst::Response\write> and L<Catalyst::Response\write_fh> for more.
06e1b616 66
67=cut
68
fbcc39ad 69sub finalize_body {
70 my ( $self, $c ) = @_;
e37f92f5 71 return if $c->response->has_write_fh;
72
7257e9db 73 my $body = $c->response->body;
f9b6d612 74 no warnings 'uninitialized';
7e95ba12 75 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
be1c9503 76 my $got;
77 do {
1235b30f 78 $got = read $body, my ($buffer), $CHUNKSIZE;
3a64ecc9 79 $got = 0 unless $self->write( $c, $buffer );
be1c9503 80 } while $got > 0;
81
7257e9db 82 close $body;
f4a57de4 83 }
84 else {
7257e9db 85 $self->write( $c, $body );
f4a57de4 86 }
ca3023fc 87
e37f92f5 88 my $res = $c->response;
89 $res->_writer->close;
90 $res->_clear_writer;
030674d0 91
ca3023fc 92 return;
fbcc39ad 93}
6dc87a0f 94
b5ecfcf0 95=head2 $self->finalize_cookies($c)
6dc87a0f 96
fa32ac82 97Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
98response headers.
4ab87e27 99
6dc87a0f 100=cut
101
102sub finalize_cookies {
fbcc39ad 103 my ( $self, $c ) = @_;
6dc87a0f 104
fbcc39ad 105 my @cookies;
7fa2c9c1 106 my $response = $c->response;
c82ed742 107
91772de9 108 foreach my $name (keys %{ $response->cookies }) {
109
110 my $val = $response->cookies->{$name};
fbcc39ad 111
2832cb5d 112 my $cookie = (
7e95ba12 113 blessed($val)
2832cb5d 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},
b21bc468 121 -secure => $val->{secure} || 0,
122 -httponly => $val->{httponly} || 0,
2832cb5d 123 )
6dc87a0f 124 );
0f12bef2 125 if (!defined $cookie) {
126 $c->log->warn("undef passed in '$name' cookie value - not setting cookie")
127 if $c->debug;
128 next;
129 }
6dc87a0f 130
fbcc39ad 131 push @cookies, $cookie->as_string;
6dc87a0f 132 }
6dc87a0f 133
b39840da 134 for my $cookie (@cookies) {
7fa2c9c1 135 $response->headers->push_header( 'Set-Cookie' => $cookie );
fbcc39ad 136 }
137}
969647fd 138
b5ecfcf0 139=head2 $self->finalize_error($c)
969647fd 140
6e5b548e 141Output an appropriate error message. Called if there's an error in $c
4ab87e27 142after the dispatch has finished. Will output debug messages if Catalyst
143is in debug mode, or a `please come back later` message otherwise.
144
969647fd 145=cut
146
c96cdcef 147sub _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: "
1565e158 154 . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
c96cdcef 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>
162EOF
163}
164
969647fd 165sub finalize_error {
fbcc39ad 166 my ( $self, $c ) = @_;
969647fd 167
7299a7b4 168 $c->res->content_type('text/html; charset=utf-8');
df960201 169 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
361ba9b2 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 }
969647fd 178
179 my ( $title, $error, $infos );
180 if ( $c->debug ) {
62d9b030 181
182 # For pretty dumps
b5ecfcf0 183 $error = join '', map {
184 '<p><code class="error">'
185 . encode_entities($_)
186 . '</code></p>'
187 } @{ $c->error };
969647fd 188 $error ||= 'No output';
2666dd3b 189 $error = qq{<pre wrap="">$error</pre>};
969647fd 190 $title = $name = "$name on Catalyst $Catalyst::VERSION";
d82cc9ae 191 $name = "<h1>$name</h1>";
fbcc39ad 192
258733f1 193 # Don't show context in the dump
194 $c->res->_clear_context;
195
fbcc39ad 196 # Don't show body parser in the dump
0f56bbcf 197 $c->req->_clear_body;
fbcc39ad 198
c6ef5e69 199 my @infos;
200 my $i = 0;
c6ef5e69 201 for my $dump ( $c->dump_these ) {
c96cdcef 202 push @infos, $self->_dump_error_page_element($i, $dump);
c6ef5e69 203 $i++;
204 }
205 $infos = join "\n", @infos;
969647fd 206 }
207 else {
208 $title = $name;
209 $error = '';
210 $infos = <<"";
211<pre>
212(en) Please come back later
0c2b4ac0 213(fr) SVP veuillez revenir plus tard
969647fd 214(de) Bitte versuchen sie es spaeter nocheinmal
d82cc9ae 215(at) Konnten's bitt'schoen spaeter nochmal reinschauen
969647fd 216(no) Vennligst prov igjen senere
d82cc9ae 217(dk) Venligst prov igen senere
218(pl) Prosze sprobowac pozniej
2f381252 219(pt) Por favor volte mais tarde
b31c0f2e 220(ru) Попробуйте еще раз позже
221(ua) Спробуйте ще раз пізніше
969647fd 222</pre>
223
224 $name = '';
225 }
e060fe05 226 $c->res->body( <<"" );
7299a7b4 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">
969647fd 230<head>
7299a7b4 231 <meta http-equiv="Content-Language" content="en" />
232 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 233 <title>$title</title>
7299a7b4 234 <script type="text/javascript">
c6ef5e69 235 <!--
236 function toggleDump (dumpElement) {
7299a7b4 237 var e = document.getElementById( dumpElement );
238 if (e.style.display == "none") {
239 e.style.display = "";
c6ef5e69 240 }
241 else {
7299a7b4 242 e.style.display = "none";
c6ef5e69 243 }
244 }
245 -->
246 </script>
969647fd 247 <style type="text/css">
248 body {
249 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
250 Tahoma, Arial, helvetica, sans-serif;
34d28dfd 251 color: #333;
969647fd 252 background-color: #eee;
253 margin: 0px;
254 padding: 0px;
255 }
c6ef5e69 256 :link, :link:hover, :visited, :visited:hover {
34d28dfd 257 color: #000;
c6ef5e69 258 }
969647fd 259 div.box {
9619f23c 260 position: relative;
969647fd 261 background-color: #ccc;
262 border: 1px solid #aaa;
263 padding: 4px;
264 margin: 10px;
969647fd 265 }
266 div.error {
34d28dfd 267 background-color: #cce;
969647fd 268 border: 1px solid #755;
269 padding: 8px;
270 margin: 4px;
271 margin-bottom: 10px;
969647fd 272 }
273 div.infos {
34d28dfd 274 background-color: #eee;
969647fd 275 border: 1px solid #575;
276 padding: 8px;
277 margin: 4px;
278 margin-bottom: 10px;
969647fd 279 }
280 div.name {
34d28dfd 281 background-color: #cce;
969647fd 282 border: 1px solid #557;
283 padding: 8px;
284 margin: 4px;
969647fd 285 }
7f8e0078 286 code.error {
287 display: block;
288 margin: 1em 0;
289 overflow: auto;
7f8e0078 290 }
9619f23c 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 }
2666dd3b 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 */
b0ad47c1 307 pre {
2666dd3b 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 }
969647fd 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
4b66aa19 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.
d82cc9ae 327 $c->res->{body} .= ( ' ' x 512 );
328
361ba9b2 329 $c->res->{body} = Encode::encode("UTF-8", $c->res->{body});
330
d82cc9ae 331 # Return 500
33117422 332 $c->res->status(500);
969647fd 333}
334
b5ecfcf0 335=head2 $self->finalize_headers($c)
fc7ec1d9 336
9c4288ea 337Allows engines to write headers to response
4ab87e27 338
fc7ec1d9 339=cut
340
44d28c7d 341sub finalize_headers {
342 my ($self, $ctx) = @_;
343
89ba65d5 344 $ctx->finalize_headers unless $ctx->response->finalized_headers;
44d28c7d 345 return;
346}
fc7ec1d9 347
b5ecfcf0 348=head2 $self->finalize_uploads($c)
fc7ec1d9 349
4ab87e27 350Clean up after uploads, deleting temp files.
351
fc7ec1d9 352=cut
353
fbcc39ad 354sub finalize_uploads {
355 my ( $self, $c ) = @_;
99fe1710 356
671123ba 357 # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
358 # on the HTTP::Body object.
7fa2c9c1 359 my $request = $c->request;
91772de9 360 foreach my $key (keys %{ $request->uploads }) {
361 my $upload = $request->uploads->{$key};
7fa2c9c1 362 unlink grep { -e $_ } map { $_->tempname }
363 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 364 }
7fa2c9c1 365
fc7ec1d9 366}
367
b5ecfcf0 368=head2 $self->prepare_body($c)
fc7ec1d9 369
4ab87e27 370sets up the L<Catalyst::Request> object body using L<HTTP::Body>
371
fc7ec1d9 372=cut
373
fbcc39ad 374sub prepare_body {
375 my ( $self, $c ) = @_;
99fe1710 376
398f13db 377 $c->request->prepare_body;
fc7ec1d9 378}
379
b5ecfcf0 380=head2 $self->prepare_body_chunk($c)
4bd82c41 381
4ab87e27 382Add a chunk to the request body.
383
4bd82c41 384=cut
385
398f13db 386# XXX - Can this be deleted?
4bd82c41 387sub prepare_body_chunk {
388 my ( $self, $c, $chunk ) = @_;
4f5ebacd 389
398f13db 390 $c->request->prepare_body_chunk($chunk);
4bd82c41 391}
392
b5ecfcf0 393=head2 $self->prepare_body_parameters($c)
06e1b616 394
b0ad47c1 395Sets up parameters from body.
4ab87e27 396
06e1b616 397=cut
398
fbcc39ad 399sub prepare_body_parameters {
400 my ( $self, $c ) = @_;
b0ad47c1 401
398f13db 402 $c->request->prepare_body_parameters;
44d28c7d 403}
fc7ec1d9 404
b5ecfcf0 405=head2 $self->prepare_parameters($c)
fc7ec1d9 406
11e7af55 407Sets up parameters from query and post parameters.
408If parameters have already been set up will clear
409existing parameters and set up again.
4ab87e27 410
fc7ec1d9 411=cut
412
fbcc39ad 413sub prepare_parameters {
414 my ( $self, $c ) = @_;
fc7ec1d9 415
11e7af55 416 $c->request->_clear_parameters;
417 return $c->request->parameters;
fbcc39ad 418}
419
b5ecfcf0 420=head2 $self->prepare_path($c)
fc7ec1d9 421
4ab87e27 422abstract method, implemented by engines.
423
fc7ec1d9 424=cut
425
44d28c7d 426sub prepare_path {
427 my ($self, $ctx) = @_;
428
faa02805 429 my $env = $ctx->request->env;
44d28c7d 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
661de072 437 my $path;
438 if (!$ctx->config->{use_request_uri_for_path}) {
4904ee27 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;
661de072 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 }
44d28c7d 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
44d28c7d 466 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
467 my $uri = $scheme . '://' . $host . '/' . $path . $query;
468
4ee03d72 469 $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
44d28c7d 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}
fc7ec1d9 481
b5ecfcf0 482=head2 $self->prepare_request($c)
fc7ec1d9 483
b5ecfcf0 484=head2 $self->prepare_query_parameters($c)
fc7ec1d9 485
4ab87e27 486process the query string and extract query parameters.
487
fc7ec1d9 488=cut
489
e0616220 490sub prepare_query_parameters {
44d28c7d 491 my ($self, $c) = @_;
faa02805 492 my $env = $c->request->env;
bd822b43 493
494 if(my $query_obj = $env->{'plack.request.query'}) {
495 $c->request->query_parameters($query_obj->as_hashref_mixed);
496 return;
497 }
498
faa02805 499 my $query_string = exists $env->{QUERY_STRING}
500 ? $env->{QUERY_STRING}
44d28c7d 501 : '';
b0ad47c1 502
3b4d1251 503 # Check for keywords (no = signs)
504 # (yes, index() is faster than a regex :))
933ba403 505 if ( index( $query_string, '=' ) < 0 ) {
bd822b43 506 $c->request->query_keywords($self->unescape_uri($query_string));
507 $env->{'plack.request.query'} ||= Hash::MultiValue->new(
508 map { (URI::Escape::uri_unescape($_), '') }
509 split(/\+/, $query_string, -1));
510
933ba403 511 return;
512 }
513
514 my %query;
e0616220 515
516 # replace semi-colons
517 $query_string =~ s/;/&/g;
b0ad47c1 518
2f381252 519 my @params = grep { length $_ } split /&/, $query_string;
e0616220 520
933ba403 521 for my $item ( @params ) {
b0ad47c1 522
523 my ($param, $value)
933ba403 524 = map { $self->unescape_uri($_) }
e5542b70 525 split( /=/, $item, 2 );
b0ad47c1 526
933ba403 527 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 528
933ba403 529 if ( exists $query{$param} ) {
530 if ( ref $query{$param} ) {
531 push @{ $query{$param} }, $value;
532 }
533 else {
534 $query{$param} = [ $query{$param}, $value ];
535 }
536 }
537 else {
538 $query{$param} = $value;
539 }
e0616220 540 }
bd822b43 541
542 $env->{'plack.request.query'} ||= Hash::MultiValue->from_mixed(\%query);
933ba403 543 $c->request->query_parameters( \%query );
e0616220 544}
fbcc39ad 545
b5ecfcf0 546=head2 $self->prepare_read($c)
fbcc39ad 547
47b9d68e 548Prepare to read by initializing the Content-Length from headers.
4ab87e27 549
fbcc39ad 550=cut
fc7ec1d9 551
fbcc39ad 552sub prepare_read {
553 my ( $self, $c ) = @_;
4f5ebacd 554
878b821c 555 # Initialize the amount of data we think we need to read
faa02805 556 $c->request->_read_length;
fbcc39ad 557}
fc7ec1d9 558
b5ecfcf0 559=head2 $self->prepare_request(@arguments)
fc7ec1d9 560
c4a17516 561Populate the context object from the request object.
4ab87e27 562
fc7ec1d9 563=cut
564
44d28c7d 565sub prepare_request {
566 my ($self, $ctx, %args) = @_;
0eb98ebd 567 $ctx->log->psgienv($args{env}) if $ctx->log->can('psgienv');
faa02805 568 $ctx->request->_set_env($args{env});
569 $self->_set_env($args{env}); # Nasty back compat!
570 $ctx->response->_set_response_cb($args{response_cb});
44d28c7d 571}
fc7ec1d9 572
b5ecfcf0 573=head2 $self->prepare_uploads($c)
c9afa5fc 574
fbcc39ad 575=cut
576
577sub prepare_uploads {
578 my ( $self, $c ) = @_;
7fa2c9c1 579
580 my $request = $c->request;
0f56bbcf 581 return unless $request->_body;
7fa2c9c1 582
0f56bbcf 583 my $uploads = $request->_body->upload;
7fa2c9c1 584 my $parameters = $request->parameters;
191665f3 585 my @plack_uploads;
91772de9 586 foreach my $name (keys %$uploads) {
587 my $files = $uploads->{$name};
fbcc39ad 588 my @uploads;
7fa2c9c1 589 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
590 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
591 my $u = Catalyst::Request::Upload->new
592 (
593 size => $upload->{size},
a160c98d 594 type => scalar $headers->content_type,
7fa2c9c1 595 headers => $headers,
596 tempname => $upload->{tempname},
597 filename => $upload->{filename},
598 );
fbcc39ad 599 push @uploads, $u;
191665f3 600
601 # Plack compatibility.
602 my %copy = (%$upload, headers=>$headers);
603 push @plack_uploads, $name, Plack::Request::Upload->new(%copy);
fbcc39ad 604 }
7fa2c9c1 605 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 606
191665f3 607
c4bed79a 608 # support access to the filename as a normal param
609 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 610 # append, if there's already params with this name
7fa2c9c1 611 if (exists $parameters->{$name}) {
612 if (ref $parameters->{$name} eq 'ARRAY') {
613 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 614 }
615 else {
7fa2c9c1 616 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 617 }
618 }
619 else {
7fa2c9c1 620 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 621 }
fbcc39ad 622 }
191665f3 623
624 $self->env->{'plack.request.upload'} ||= Hash::MultiValue->new(@plack_uploads);
fbcc39ad 625}
626
767480fd 627=head2 $self->write($c, $buffer)
c9afa5fc 628
767480fd 629Writes the buffer to the client.
4ab87e27 630
c9afa5fc 631=cut
632
767480fd 633sub write {
634 my ( $self, $c, $buffer ) = @_;
635
636 $c->response->write($buffer);
637}
fbcc39ad 638
b5ecfcf0 639=head2 $self->read($c, [$maxlength])
fbcc39ad 640
ea72fece 641Reads from the input stream by calling C<< $self->read_chunk >>.
642
643Maintains the read_length and read_position counters as data is read.
644
fbcc39ad 645=cut
646
647sub read {
648 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 649
f083854e 650 $c->request->read($maxlength);
fbcc39ad 651}
fc7ec1d9 652
87f50436 653=head2 $self->read_chunk($c, \$buffer, $length)
23f9d934 654
10011c19 655Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 656of data. Returns the number of bytes read. A return of 0 indicates that
657there is no more data to be read.
fc7ec1d9 658
fbcc39ad 659=cut
61b1e958 660
e6b46d80 661sub read_chunk {
ce7abbda 662 my ($self, $ctx) = (shift, shift);
87f50436 663 return $ctx->request->read_chunk(@_);
e6b46d80 664}
61b1e958 665
9560b708 666=head2 $self->run($app, $server)
63b763c5 667
9560b708 668Start the engine. Builds a PSGI application and calls the
acbecf08 669run method on the server passed in, which then causes the
670engine to loop, handling requests..
4ab87e27 671
fbcc39ad 672=cut
fc7ec1d9 673
44d28c7d 674sub run {
51857616 675 my ($self, $app, $psgi, @args) = @_;
acbecf08 676 # @args left here rather than just a $options, $server for back compat with the
677 # old style scripts which send a few args, then a hashref
678
679 # They should never actually be used in the normal case as the Plack engine is
680 # passed in got all the 'standard' args via the loader in the script already.
681
682 # FIXME - we should stash the options in an attribute so that custom args
683 # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
1e5dad00 684 my $server = pop @args if (scalar @args && blessed $args[-1]);
685 my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
ccb13b15 686 # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
687 if (scalar @args && !ref($args[0])) {
688 if (my $listen = shift @args) {
689 $options->{listen} ||= [$listen];
690 }
691 }
acbecf08 692 if (! $server ) {
f7a3f8fd 693 $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
f7f55b2f 694 # We're not being called from a script, so auto detect what backend to
695 # run on. This should never happen, as mod_perl never calls ->run,
696 # instead the $app->handle method is called per request.
acbecf08 697 $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
698 }
aee7cdcc 699 $app->run_options($options);
acbecf08 700 $server->run($psgi, $options);
a1791811 701}
44d28c7d 702
9560b708 703=head2 build_psgi_app ($app, @args)
704
e3f6b891 705Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
9560b708 706
707=cut
708
22a5833d 709sub build_psgi_app {
a1791811 710 my ($self, $app, @args) = @_;
c2f4a965 711
fcffcb05 712 return sub {
44d28c7d 713 my ($env) = @_;
714
715 return sub {
716 my ($respond) = @_;
e33d788d 717 confess("Did not get a response callback for writer, cannot continue") unless $respond;
faa02805 718 $app->handle_request(env => $env, response_cb => $respond);
44d28c7d 719 };
720 };
721}
fc7ec1d9 722
933ba403 723=head2 $self->unescape_uri($uri)
724
6a44fe01 725Unescapes a given URI using the most efficient method available. Engines such
726as Apache may implement this using Apache's C-based modules, for example.
933ba403 727
728=cut
729
730sub unescape_uri {
8c7d83e1 731 my ( $self, $str ) = @_;
7d22a537 732
733 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
734
8c7d83e1 735 return $str;
933ba403 736}
34d28dfd 737
4ab87e27 738=head2 $self->finalize_output
739
740<obsolete>, see finalize_body
741
0c76ec45 742=head2 $self->env
743
6356febf 744Hash containing environment variables including many special variables inserted
0c76ec45 745by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
746
6356febf 747Before accessing environment variables consider whether the same information is
0c76ec45 748not directly available via Catalyst objects $c->request, $c->engine ...
749
6356febf 750BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 751application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
ae7da8f5 752as in some environments the %ENV hash does not contain what you would expect.
0c76ec45 753
fbcc39ad 754=head1 AUTHORS
755
2f381252 756Catalyst Contributors, see Catalyst.pm
fc7ec1d9 757
758=head1 COPYRIGHT
759
536bee89 760This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 761the same terms as Perl itself.
762
763=cut
764
58f86b1a 765__PACKAGE__->meta->make_immutable;
766
fc7ec1d9 7671;