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