updated changes and versions, commented on deprecations in the delta file
[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
faa02805 25# XXX - this is only here for compat, do not use!
26has env => ( is => 'rw', writer => '_set_env' );
0c6352ff 27my $WARN_ABOUT_ENV = 0;
28around env => sub {
29 my ($orig, $self, @args) = @_;
30 if(@args) {
31 warn "env as a writer is deprecated, you probably need to upgrade Catalyst::Engine::PSGI"
32 unless $WARN_ABOUT_ENV++;
33 return $self->_set_env(@args);
34 }
35 return $self->$orig;
36};
37
ddcd2fc4 38# XXX - Only here for Engine::PSGI compat
39sub prepare_connection {
40 my ($self, $ctx) = @_;
41 $ctx->request->prepare_connection;
42}
4bd82c41 43
fc7ec1d9 44=head1 NAME
45
46Catalyst::Engine - The Catalyst Engine
47
48=head1 SYNOPSIS
49
50See L<Catalyst>.
51
52=head1 DESCRIPTION
53
23f9d934 54=head1 METHODS
fc7ec1d9 55
cd3bb248 56
b5ecfcf0 57=head2 $self->finalize_body($c)
06e1b616 58
717fc5c9 59Finalize body. Prints the response output as blocking stream if it looks like
60a filehandle, otherwise write it out all in one go. If there is no body in
61the response, we assume you are handling it 'manually', such as for nonblocking
fc89737e 62style or asynchronous streaming responses. You do this by calling L</write>
63several times (which sends HTTP headers if needed) or you close over
64C<< $response->write_fh >>.
e37f92f5 65
fc89737e 66See L<Catalyst::Response/write> and L<Catalyst::Response/write_fh> for more.
06e1b616 67
68=cut
69
fbcc39ad 70sub finalize_body {
71 my ( $self, $c ) = @_;
46fff667 72 my $res = $c->response; # We use this all over
73
74 ## If we've asked for the write 'filehandle' that means the application is
75 ## doing something custom and is expected to close the response
76 return if $res->_has_write_fh;
77
78 if($res->_has_response_cb) {
79 ## we have not called the response callback yet, so we are safe to send
80 ## the whole body to PSGI
81
82 my @headers;
83 $res->headers->scan(sub { push @headers, @_ });
84
85 ## We need to figure out what kind of body we have...
86 my $body = $res->body;
87 if(defined $body) {
56894295 88 if(
89 (blessed($body) && $body->can('getline'))
90 or ref($body) eq 'GLOB'
91 ) {
92 # Body is an IO handle that meets the PSGI spec
93 } elsif(blessed($body) && $body->can('read')) {
94 # In the past, Catalyst only looked for read not getline. It is very possible
95 # that one might have an object that respected read but did not have getline.
96 # As a result, we need to handle this case for backcompat.
97
98 # We will just do the old loop for now but someone could write a proxy
99 # object to wrap getline and proxy read
100 my $got;
101 do {
102 $got = read $body, my ($buffer), $CHUNKSIZE;
103 $got = 0 unless $self->write($c, $buffer );
104 } while $got > 0;
105
8e098614 106 # I really am guessing this case is pathological. I'd like to remove it
107 # but need to give people a bit of heads up
108 $c->log->warn('!!! Setting $response->body to an object that supports "read" but not "getline" is deprecated. !!!')
109 unless $self->{__FH_READ_DEPRECATION_NOTICE_qwvsretf43}++;
110
56894295 111 close $body;
112 return;
46fff667 113 } else {
114 # Looks like for backcompat reasons we need to be able to deal
115 # with stringyfiable objects.
116 $body = "$body" if blessed($body); # Assume there's some sort of overloading..
117 $body = [$body];
118 }
119 } else {
120 $body = [undef];
121 }
122
123 $res->_response_cb->([ $res->status, \@headers, $body]);
124 $res->_clear_response_cb;
125
126 } else {
127 ## Now, if there's no response callback anymore, that means someone has
128 ## called ->write in order to stream 'some stuff along the way'. I think
129 ## for backcompat we still need to handle a ->body. I guess I could see
130 ## someone calling ->write to presend some stuff, and then doing the rest
131 ## via ->body, like in a template.
132
133 ## We'll just use the old, existing code for this (or most of it)
134
135 if(my $body = $res->body) {
136 no warnings 'uninitialized';
137 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
138
139 ## In this case we have no choice and will fall back on the old
140 ## manual streaming stuff.
141
142 my $got;
143 do {
144 $got = read $body, my ($buffer), $CHUNKSIZE;
145 $got = 0 unless $self->write($c, $buffer );
146 } while $got > 0;
147
148 close $body;
149 }
150 else {
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});
637 $self->_set_env($args{env}); # Nasty back compat!
638 $ctx->response->_set_response_cb($args{response_cb});
44d28c7d 639}
fc7ec1d9 640
b5ecfcf0 641=head2 $self->prepare_uploads($c)
c9afa5fc 642
fbcc39ad 643=cut
644
645sub prepare_uploads {
646 my ( $self, $c ) = @_;
7fa2c9c1 647
648 my $request = $c->request;
0f56bbcf 649 return unless $request->_body;
7fa2c9c1 650
0f56bbcf 651 my $uploads = $request->_body->upload;
7fa2c9c1 652 my $parameters = $request->parameters;
91772de9 653 foreach my $name (keys %$uploads) {
654 my $files = $uploads->{$name};
fbcc39ad 655 my @uploads;
7fa2c9c1 656 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
657 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
658 my $u = Catalyst::Request::Upload->new
659 (
660 size => $upload->{size},
a160c98d 661 type => scalar $headers->content_type,
7fa2c9c1 662 headers => $headers,
663 tempname => $upload->{tempname},
664 filename => $upload->{filename},
665 );
fbcc39ad 666 push @uploads, $u;
667 }
7fa2c9c1 668 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 669
c4bed79a 670 # support access to the filename as a normal param
671 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 672 # append, if there's already params with this name
7fa2c9c1 673 if (exists $parameters->{$name}) {
674 if (ref $parameters->{$name} eq 'ARRAY') {
675 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 676 }
677 else {
7fa2c9c1 678 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 679 }
680 }
681 else {
7fa2c9c1 682 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 683 }
fbcc39ad 684 }
685}
686
767480fd 687=head2 $self->write($c, $buffer)
c9afa5fc 688
767480fd 689Writes the buffer to the client.
4ab87e27 690
c9afa5fc 691=cut
692
767480fd 693sub write {
694 my ( $self, $c, $buffer ) = @_;
695
696 $c->response->write($buffer);
697}
fbcc39ad 698
b5ecfcf0 699=head2 $self->read($c, [$maxlength])
fbcc39ad 700
ea72fece 701Reads from the input stream by calling C<< $self->read_chunk >>.
702
703Maintains the read_length and read_position counters as data is read.
704
fbcc39ad 705=cut
706
707sub read {
708 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 709
f083854e 710 $c->request->read($maxlength);
fbcc39ad 711}
fc7ec1d9 712
87f50436 713=head2 $self->read_chunk($c, \$buffer, $length)
23f9d934 714
10011c19 715Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 716of data. Returns the number of bytes read. A return of 0 indicates that
717there is no more data to be read.
fc7ec1d9 718
fbcc39ad 719=cut
61b1e958 720
e6b46d80 721sub read_chunk {
ce7abbda 722 my ($self, $ctx) = (shift, shift);
87f50436 723 return $ctx->request->read_chunk(@_);
e6b46d80 724}
61b1e958 725
9560b708 726=head2 $self->run($app, $server)
63b763c5 727
9560b708 728Start the engine. Builds a PSGI application and calls the
acbecf08 729run method on the server passed in, which then causes the
730engine to loop, handling requests..
4ab87e27 731
fbcc39ad 732=cut
fc7ec1d9 733
44d28c7d 734sub run {
51857616 735 my ($self, $app, $psgi, @args) = @_;
acbecf08 736 # @args left here rather than just a $options, $server for back compat with the
737 # old style scripts which send a few args, then a hashref
738
739 # They should never actually be used in the normal case as the Plack engine is
740 # passed in got all the 'standard' args via the loader in the script already.
741
742 # FIXME - we should stash the options in an attribute so that custom args
743 # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
1e5dad00 744 my $server = pop @args if (scalar @args && blessed $args[-1]);
745 my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
ccb13b15 746 # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
747 if (scalar @args && !ref($args[0])) {
748 if (my $listen = shift @args) {
749 $options->{listen} ||= [$listen];
750 }
751 }
acbecf08 752 if (! $server ) {
f7a3f8fd 753 $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
f7f55b2f 754 # We're not being called from a script, so auto detect what backend to
755 # run on. This should never happen, as mod_perl never calls ->run,
756 # instead the $app->handle method is called per request.
acbecf08 757 $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
758 }
aee7cdcc 759 $app->run_options($options);
acbecf08 760 $server->run($psgi, $options);
a1791811 761}
44d28c7d 762
9560b708 763=head2 build_psgi_app ($app, @args)
764
e3f6b891 765Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
9560b708 766
767=cut
768
22a5833d 769sub build_psgi_app {
a1791811 770 my ($self, $app, @args) = @_;
c2f4a965 771
fcffcb05 772 return sub {
44d28c7d 773 my ($env) = @_;
774
775 return sub {
776 my ($respond) = @_;
e33d788d 777 confess("Did not get a response callback for writer, cannot continue") unless $respond;
faa02805 778 $app->handle_request(env => $env, response_cb => $respond);
44d28c7d 779 };
780 };
781}
fc7ec1d9 782
933ba403 783=head2 $self->unescape_uri($uri)
784
6a44fe01 785Unescapes a given URI using the most efficient method available. Engines such
786as Apache may implement this using Apache's C-based modules, for example.
933ba403 787
788=cut
789
790sub unescape_uri {
8c7d83e1 791 my ( $self, $str ) = @_;
7d22a537 792
793 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
794
8c7d83e1 795 return $str;
933ba403 796}
34d28dfd 797
4ab87e27 798=head2 $self->finalize_output
799
800<obsolete>, see finalize_body
801
0c76ec45 802=head2 $self->env
803
6356febf 804Hash containing environment variables including many special variables inserted
0c76ec45 805by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
806
6356febf 807Before accessing environment variables consider whether the same information is
0c76ec45 808not directly available via Catalyst objects $c->request, $c->engine ...
809
6356febf 810BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 811application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
ae7da8f5 812as in some environments the %ENV hash does not contain what you would expect.
0c76ec45 813
fbcc39ad 814=head1 AUTHORS
815
2f381252 816Catalyst Contributors, see Catalyst.pm
fc7ec1d9 817
818=head1 COPYRIGHT
819
536bee89 820This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 821the same terms as Perl itself.
822
823=cut
824
58f86b1a 825__PACKAGE__->meta->make_immutable;
826
fc7ec1d9 8271;