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