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