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