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