first pass at making Catalyst act enough like PSGI middleware to be broadly compatible
[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) = @_;
492
faa02805 493 my $env = $c->request->env;
494 my $query_string = exists $env->{QUERY_STRING}
495 ? $env->{QUERY_STRING}
44d28c7d 496 : '';
b0ad47c1 497
3b4d1251 498 # Check for keywords (no = signs)
499 # (yes, index() is faster than a regex :))
933ba403 500 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 501 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 502 return;
503 }
504
505 my %query;
e0616220 506
507 # replace semi-colons
508 $query_string =~ s/;/&/g;
b0ad47c1 509
2f381252 510 my @params = grep { length $_ } split /&/, $query_string;
e0616220 511
933ba403 512 for my $item ( @params ) {
b0ad47c1 513
514 my ($param, $value)
933ba403 515 = map { $self->unescape_uri($_) }
e5542b70 516 split( /=/, $item, 2 );
b0ad47c1 517
933ba403 518 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 519
933ba403 520 if ( exists $query{$param} ) {
521 if ( ref $query{$param} ) {
522 push @{ $query{$param} }, $value;
523 }
524 else {
525 $query{$param} = [ $query{$param}, $value ];
526 }
527 }
528 else {
529 $query{$param} = $value;
530 }
e0616220 531 }
933ba403 532 $c->request->query_parameters( \%query );
e0616220 533}
fbcc39ad 534
b5ecfcf0 535=head2 $self->prepare_read($c)
fbcc39ad 536
47b9d68e 537Prepare to read by initializing the Content-Length from headers.
4ab87e27 538
fbcc39ad 539=cut
fc7ec1d9 540
fbcc39ad 541sub prepare_read {
542 my ( $self, $c ) = @_;
4f5ebacd 543
878b821c 544 # Initialize the amount of data we think we need to read
faa02805 545 $c->request->_read_length;
fbcc39ad 546}
fc7ec1d9 547
b5ecfcf0 548=head2 $self->prepare_request(@arguments)
fc7ec1d9 549
c4a17516 550Populate the context object from the request object.
4ab87e27 551
fc7ec1d9 552=cut
553
44d28c7d 554sub prepare_request {
555 my ($self, $ctx, %args) = @_;
0eb98ebd 556 $ctx->log->psgienv($args{env}) if $ctx->log->can('psgienv');
faa02805 557 $ctx->request->_set_env($args{env});
558 $self->_set_env($args{env}); # Nasty back compat!
559 $ctx->response->_set_response_cb($args{response_cb});
44d28c7d 560}
fc7ec1d9 561
b5ecfcf0 562=head2 $self->prepare_uploads($c)
c9afa5fc 563
fbcc39ad 564=cut
565
566sub prepare_uploads {
567 my ( $self, $c ) = @_;
7fa2c9c1 568
569 my $request = $c->request;
0f56bbcf 570 return unless $request->_body;
7fa2c9c1 571
0f56bbcf 572 my $uploads = $request->_body->upload;
7fa2c9c1 573 my $parameters = $request->parameters;
191665f3 574 my @plack_uploads;
91772de9 575 foreach my $name (keys %$uploads) {
576 my $files = $uploads->{$name};
fbcc39ad 577 my @uploads;
7fa2c9c1 578 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
579 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
580 my $u = Catalyst::Request::Upload->new
581 (
582 size => $upload->{size},
a160c98d 583 type => scalar $headers->content_type,
7fa2c9c1 584 headers => $headers,
585 tempname => $upload->{tempname},
586 filename => $upload->{filename},
587 );
fbcc39ad 588 push @uploads, $u;
191665f3 589
590 # Plack compatibility.
591 my %copy = (%$upload, headers=>$headers);
592 push @plack_uploads, $name, Plack::Request::Upload->new(%copy);
fbcc39ad 593 }
7fa2c9c1 594 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 595
191665f3 596
c4bed79a 597 # support access to the filename as a normal param
598 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 599 # append, if there's already params with this name
7fa2c9c1 600 if (exists $parameters->{$name}) {
601 if (ref $parameters->{$name} eq 'ARRAY') {
602 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 603 }
604 else {
7fa2c9c1 605 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 606 }
607 }
608 else {
7fa2c9c1 609 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 610 }
fbcc39ad 611 }
191665f3 612
613 $self->env->{'plack.request.upload'} ||= Hash::MultiValue->new(@plack_uploads);
fbcc39ad 614}
615
767480fd 616=head2 $self->write($c, $buffer)
c9afa5fc 617
767480fd 618Writes the buffer to the client.
4ab87e27 619
c9afa5fc 620=cut
621
767480fd 622sub write {
623 my ( $self, $c, $buffer ) = @_;
624
625 $c->response->write($buffer);
626}
fbcc39ad 627
b5ecfcf0 628=head2 $self->read($c, [$maxlength])
fbcc39ad 629
ea72fece 630Reads from the input stream by calling C<< $self->read_chunk >>.
631
632Maintains the read_length and read_position counters as data is read.
633
fbcc39ad 634=cut
635
636sub read {
637 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 638
f083854e 639 $c->request->read($maxlength);
fbcc39ad 640}
fc7ec1d9 641
87f50436 642=head2 $self->read_chunk($c, \$buffer, $length)
23f9d934 643
10011c19 644Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 645of data. Returns the number of bytes read. A return of 0 indicates that
646there is no more data to be read.
fc7ec1d9 647
fbcc39ad 648=cut
61b1e958 649
e6b46d80 650sub read_chunk {
ce7abbda 651 my ($self, $ctx) = (shift, shift);
87f50436 652 return $ctx->request->read_chunk(@_);
e6b46d80 653}
61b1e958 654
9560b708 655=head2 $self->run($app, $server)
63b763c5 656
9560b708 657Start the engine. Builds a PSGI application and calls the
acbecf08 658run method on the server passed in, which then causes the
659engine to loop, handling requests..
4ab87e27 660
fbcc39ad 661=cut
fc7ec1d9 662
44d28c7d 663sub run {
51857616 664 my ($self, $app, $psgi, @args) = @_;
acbecf08 665 # @args left here rather than just a $options, $server for back compat with the
666 # old style scripts which send a few args, then a hashref
667
668 # They should never actually be used in the normal case as the Plack engine is
669 # passed in got all the 'standard' args via the loader in the script already.
670
671 # FIXME - we should stash the options in an attribute so that custom args
672 # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
1e5dad00 673 my $server = pop @args if (scalar @args && blessed $args[-1]);
674 my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
ccb13b15 675 # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
676 if (scalar @args && !ref($args[0])) {
677 if (my $listen = shift @args) {
678 $options->{listen} ||= [$listen];
679 }
680 }
acbecf08 681 if (! $server ) {
f7a3f8fd 682 $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
f7f55b2f 683 # We're not being called from a script, so auto detect what backend to
684 # run on. This should never happen, as mod_perl never calls ->run,
685 # instead the $app->handle method is called per request.
acbecf08 686 $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
687 }
aee7cdcc 688 $app->run_options($options);
acbecf08 689 $server->run($psgi, $options);
a1791811 690}
44d28c7d 691
9560b708 692=head2 build_psgi_app ($app, @args)
693
e3f6b891 694Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
9560b708 695
696=cut
697
22a5833d 698sub build_psgi_app {
a1791811 699 my ($self, $app, @args) = @_;
c2f4a965 700
fcffcb05 701 return sub {
44d28c7d 702 my ($env) = @_;
703
704 return sub {
705 my ($respond) = @_;
e33d788d 706 confess("Did not get a response callback for writer, cannot continue") unless $respond;
faa02805 707 $app->handle_request(env => $env, response_cb => $respond);
44d28c7d 708 };
709 };
710}
fc7ec1d9 711
933ba403 712=head2 $self->unescape_uri($uri)
713
6a44fe01 714Unescapes a given URI using the most efficient method available. Engines such
715as Apache may implement this using Apache's C-based modules, for example.
933ba403 716
717=cut
718
719sub unescape_uri {
8c7d83e1 720 my ( $self, $str ) = @_;
7d22a537 721
722 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
723
8c7d83e1 724 return $str;
933ba403 725}
34d28dfd 726
4ab87e27 727=head2 $self->finalize_output
728
729<obsolete>, see finalize_body
730
0c76ec45 731=head2 $self->env
732
6356febf 733Hash containing environment variables including many special variables inserted
0c76ec45 734by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
735
6356febf 736Before accessing environment variables consider whether the same information is
0c76ec45 737not directly available via Catalyst objects $c->request, $c->engine ...
738
6356febf 739BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 740application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
ae7da8f5 741as in some environments the %ENV hash does not contain what you would expect.
0c76ec45 742
fbcc39ad 743=head1 AUTHORS
744
2f381252 745Catalyst Contributors, see Catalyst.pm
fc7ec1d9 746
747=head1 COPYRIGHT
748
536bee89 749This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 750the same terms as Perl itself.
751
752=cut
753
58f86b1a 754__PACKAGE__->meta->make_immutable;
755
fc7ec1d9 7561;