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