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