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