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