Fix up required Plack version to ensure we have a recent enough version to work with...
[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;
44d28c7d 13use Moose::Util::TypeConstraints;
a1791811 14use Plack::Loader;
c2f4a965 15use Plack::Middleware::Conditional;
16use Plack::Middleware::ReverseProxy;
fbcc39ad 17
d495753a 18use namespace::clean -except => 'meta';
19
44d28c7d 20has env => (is => 'ro', writer => '_set_env', clearer => '_clear_env');
a50e5b46 21
fbcc39ad 22# input position and length
7fa2c9c1 23has read_length => (is => 'rw');
24has read_position => (is => 'rw');
fbcc39ad 25
02570318 26has _prepared_write => (is => 'rw');
27
44d28c7d 28has _response_cb => (
eebe046f 29 is => 'ro',
30 isa => 'CodeRef',
31 writer => '_set_response_cb',
32 clearer => '_clear_response_cb',
44d28c7d 33);
34
35has _writer => (
eebe046f 36 is => 'ro',
37 isa => duck_type([qw(write close)]),
38 writer => '_set_writer',
39 clearer => '_clear_writer',
44d28c7d 40);
41
4bd82c41 42# Amount of data to read from input on each pass
4bb8bd62 43our $CHUNKSIZE = 64 * 1024;
4bd82c41 44
fc7ec1d9 45=head1 NAME
46
47Catalyst::Engine - The Catalyst Engine
48
49=head1 SYNOPSIS
50
51See L<Catalyst>.
52
53=head1 DESCRIPTION
54
23f9d934 55=head1 METHODS
fc7ec1d9 56
cd3bb248 57
b5ecfcf0 58=head2 $self->finalize_body($c)
06e1b616 59
fbcc39ad 60Finalize body. Prints the response output.
06e1b616 61
62=cut
63
fbcc39ad 64sub finalize_body {
65 my ( $self, $c ) = @_;
7257e9db 66 my $body = $c->response->body;
f9b6d612 67 no warnings 'uninitialized';
7e95ba12 68 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
be1c9503 69 my $got;
70 do {
1235b30f 71 $got = read $body, my ($buffer), $CHUNKSIZE;
3a64ecc9 72 $got = 0 unless $self->write( $c, $buffer );
be1c9503 73 } while $got > 0;
74
7257e9db 75 close $body;
f4a57de4 76 }
77 else {
7257e9db 78 $self->write( $c, $body );
f4a57de4 79 }
ca3023fc 80
81 $self->_writer->close;
eebe046f 82 $self->_clear_writer;
030674d0 83 $self->_clear_env;
84
ca3023fc 85 return;
fbcc39ad 86}
6dc87a0f 87
b5ecfcf0 88=head2 $self->finalize_cookies($c)
6dc87a0f 89
fa32ac82 90Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
91response headers.
4ab87e27 92
6dc87a0f 93=cut
94
95sub finalize_cookies {
fbcc39ad 96 my ( $self, $c ) = @_;
6dc87a0f 97
fbcc39ad 98 my @cookies;
7fa2c9c1 99 my $response = $c->response;
c82ed742 100
91772de9 101 foreach my $name (keys %{ $response->cookies }) {
102
103 my $val = $response->cookies->{$name};
fbcc39ad 104
2832cb5d 105 my $cookie = (
7e95ba12 106 blessed($val)
2832cb5d 107 ? $val
108 : CGI::Simple::Cookie->new(
109 -name => $name,
110 -value => $val->{value},
111 -expires => $val->{expires},
112 -domain => $val->{domain},
113 -path => $val->{path},
b21bc468 114 -secure => $val->{secure} || 0,
115 -httponly => $val->{httponly} || 0,
2832cb5d 116 )
6dc87a0f 117 );
118
fbcc39ad 119 push @cookies, $cookie->as_string;
6dc87a0f 120 }
6dc87a0f 121
b39840da 122 for my $cookie (@cookies) {
7fa2c9c1 123 $response->headers->push_header( 'Set-Cookie' => $cookie );
fbcc39ad 124 }
125}
969647fd 126
b5ecfcf0 127=head2 $self->finalize_error($c)
969647fd 128
6e5b548e 129Output an appropriate error message. Called if there's an error in $c
4ab87e27 130after the dispatch has finished. Will output debug messages if Catalyst
131is in debug mode, or a `please come back later` message otherwise.
132
969647fd 133=cut
134
c96cdcef 135sub _dump_error_page_element {
136 my ($self, $i, $element) = @_;
137 my ($name, $val) = @{ $element };
138
139 # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
140 # scrolling. Suggestions for more pleasant ways to do this welcome.
141 local $val->{'__MOP__'} = "Stringified: "
1565e158 142 . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
c96cdcef 143
144 my $text = encode_entities( dump( $val ));
145 sprintf <<"EOF", $name, $text;
146<h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
147<div id="dump_$i">
148 <pre wrap="">%s</pre>
149</div>
150EOF
151}
152
969647fd 153sub finalize_error {
fbcc39ad 154 my ( $self, $c ) = @_;
969647fd 155
7299a7b4 156 $c->res->content_type('text/html; charset=utf-8');
df960201 157 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
969647fd 158
159 my ( $title, $error, $infos );
160 if ( $c->debug ) {
62d9b030 161
162 # For pretty dumps
b5ecfcf0 163 $error = join '', map {
164 '<p><code class="error">'
165 . encode_entities($_)
166 . '</code></p>'
167 } @{ $c->error };
969647fd 168 $error ||= 'No output';
2666dd3b 169 $error = qq{<pre wrap="">$error</pre>};
969647fd 170 $title = $name = "$name on Catalyst $Catalyst::VERSION";
d82cc9ae 171 $name = "<h1>$name</h1>";
fbcc39ad 172
173 # Don't show context in the dump
02570318 174 $c->req->_clear_context;
175 $c->res->_clear_context;
fbcc39ad 176
177 # Don't show body parser in the dump
0f56bbcf 178 $c->req->_clear_body;
fbcc39ad 179
c6ef5e69 180 my @infos;
181 my $i = 0;
c6ef5e69 182 for my $dump ( $c->dump_these ) {
c96cdcef 183 push @infos, $self->_dump_error_page_element($i, $dump);
c6ef5e69 184 $i++;
185 }
186 $infos = join "\n", @infos;
969647fd 187 }
188 else {
189 $title = $name;
190 $error = '';
191 $infos = <<"";
192<pre>
193(en) Please come back later
0c2b4ac0 194(fr) SVP veuillez revenir plus tard
969647fd 195(de) Bitte versuchen sie es spaeter nocheinmal
d82cc9ae 196(at) Konnten's bitt'schoen spaeter nochmal reinschauen
969647fd 197(no) Vennligst prov igjen senere
d82cc9ae 198(dk) Venligst prov igen senere
199(pl) Prosze sprobowac pozniej
2f381252 200(pt) Por favor volte mais tarde
b31c0f2e 201(ru) Попробуйте еще раз позже
202(ua) Спробуйте ще раз пізніше
969647fd 203</pre>
204
205 $name = '';
206 }
e060fe05 207 $c->res->body( <<"" );
7299a7b4 208<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
209 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
210<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
969647fd 211<head>
7299a7b4 212 <meta http-equiv="Content-Language" content="en" />
213 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 214 <title>$title</title>
7299a7b4 215 <script type="text/javascript">
c6ef5e69 216 <!--
217 function toggleDump (dumpElement) {
7299a7b4 218 var e = document.getElementById( dumpElement );
219 if (e.style.display == "none") {
220 e.style.display = "";
c6ef5e69 221 }
222 else {
7299a7b4 223 e.style.display = "none";
c6ef5e69 224 }
225 }
226 -->
227 </script>
969647fd 228 <style type="text/css">
229 body {
230 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
231 Tahoma, Arial, helvetica, sans-serif;
34d28dfd 232 color: #333;
969647fd 233 background-color: #eee;
234 margin: 0px;
235 padding: 0px;
236 }
c6ef5e69 237 :link, :link:hover, :visited, :visited:hover {
34d28dfd 238 color: #000;
c6ef5e69 239 }
969647fd 240 div.box {
9619f23c 241 position: relative;
969647fd 242 background-color: #ccc;
243 border: 1px solid #aaa;
244 padding: 4px;
245 margin: 10px;
969647fd 246 }
247 div.error {
34d28dfd 248 background-color: #cce;
969647fd 249 border: 1px solid #755;
250 padding: 8px;
251 margin: 4px;
252 margin-bottom: 10px;
969647fd 253 }
254 div.infos {
34d28dfd 255 background-color: #eee;
969647fd 256 border: 1px solid #575;
257 padding: 8px;
258 margin: 4px;
259 margin-bottom: 10px;
969647fd 260 }
261 div.name {
34d28dfd 262 background-color: #cce;
969647fd 263 border: 1px solid #557;
264 padding: 8px;
265 margin: 4px;
969647fd 266 }
7f8e0078 267 code.error {
268 display: block;
269 margin: 1em 0;
270 overflow: auto;
7f8e0078 271 }
9619f23c 272 div.name h1, div.error p {
273 margin: 0;
274 }
275 h2 {
276 margin-top: 0;
277 margin-bottom: 10px;
278 font-size: medium;
279 font-weight: bold;
280 text-decoration: underline;
281 }
282 h1 {
283 font-size: medium;
284 font-weight: normal;
285 }
2666dd3b 286 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
287 /* Browser specific (not valid) styles to make preformatted text wrap */
b0ad47c1 288 pre {
2666dd3b 289 white-space: pre-wrap; /* css-3 */
290 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
291 white-space: -pre-wrap; /* Opera 4-6 */
292 white-space: -o-pre-wrap; /* Opera 7 */
293 word-wrap: break-word; /* Internet Explorer 5.5+ */
294 }
969647fd 295 </style>
296</head>
297<body>
298 <div class="box">
299 <div class="error">$error</div>
300 <div class="infos">$infos</div>
301 <div class="name">$name</div>
302 </div>
303</body>
304</html>
305
d82cc9ae 306
4b66aa19 307 # Trick IE. Old versions of IE would display their own error page instead
308 # of ours if we'd give it less than 512 bytes.
d82cc9ae 309 $c->res->{body} .= ( ' ' x 512 );
310
311 # Return 500
33117422 312 $c->res->status(500);
969647fd 313}
314
b5ecfcf0 315=head2 $self->finalize_headers($c)
fc7ec1d9 316
4ab87e27 317Abstract method, allows engines to write headers to response
318
fc7ec1d9 319=cut
320
44d28c7d 321sub finalize_headers {
322 my ($self, $ctx) = @_;
323
324 my @headers;
325 $ctx->response->headers->scan(sub { push @headers, @_ });
326
327 $self->_set_writer($self->_response_cb->([ $ctx->response->status, \@headers ]));
eebe046f 328 $self->_clear_response_cb;
44d28c7d 329
330 return;
331}
fc7ec1d9 332
b5ecfcf0 333=head2 $self->finalize_read($c)
fc7ec1d9 334
335=cut
336
878b821c 337sub finalize_read { }
fc7ec1d9 338
b5ecfcf0 339=head2 $self->finalize_uploads($c)
fc7ec1d9 340
4ab87e27 341Clean up after uploads, deleting temp files.
342
fc7ec1d9 343=cut
344
fbcc39ad 345sub finalize_uploads {
346 my ( $self, $c ) = @_;
99fe1710 347
671123ba 348 # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
349 # on the HTTP::Body object.
7fa2c9c1 350 my $request = $c->request;
91772de9 351 foreach my $key (keys %{ $request->uploads }) {
352 my $upload = $request->uploads->{$key};
7fa2c9c1 353 unlink grep { -e $_ } map { $_->tempname }
354 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 355 }
7fa2c9c1 356
fc7ec1d9 357}
358
b5ecfcf0 359=head2 $self->prepare_body($c)
fc7ec1d9 360
4ab87e27 361sets up the L<Catalyst::Request> object body using L<HTTP::Body>
362
fc7ec1d9 363=cut
364
fbcc39ad 365sub prepare_body {
366 my ( $self, $c ) = @_;
99fe1710 367
df960201 368 my $appclass = ref($c) || $c;
878b821c 369 if ( my $length = $self->read_length ) {
7fa2c9c1 370 my $request = $c->request;
0f56bbcf 371 unless ( $request->_body ) {
7fa2c9c1 372 my $type = $request->header('Content-Type');
0f56bbcf 373 $request->_body(HTTP::Body->new( $type, $length ));
671123ba 374 $request->_body->cleanup(1); # Make extra sure!
df960201 375 $request->_body->tmpdir( $appclass->config->{uploadtmp} )
376 if exists $appclass->config->{uploadtmp};
847e3257 377 }
b0ad47c1 378
ea72fece 379 # Check for definedness as you could read '0'
380 while ( defined ( my $buffer = $self->read($c) ) ) {
4f5ebacd 381 $c->prepare_body_chunk($buffer);
fbcc39ad 382 }
fdb3773e 383
384 # paranoia against wrong Content-Length header
847e3257 385 my $remaining = $length - $self->read_position;
34d28dfd 386 if ( $remaining > 0 ) {
fdb3773e 387 $self->finalize_read($c);
34d28dfd 388 Catalyst::Exception->throw(
847e3257 389 "Wrong Content-Length value: $length" );
fdb3773e 390 }
fc7ec1d9 391 }
847e3257 392 else {
393 # Defined but will cause all body code to be skipped
0f56bbcf 394 $c->request->_body(0);
847e3257 395 }
fc7ec1d9 396}
397
b5ecfcf0 398=head2 $self->prepare_body_chunk($c)
4bd82c41 399
4ab87e27 400Add a chunk to the request body.
401
4bd82c41 402=cut
403
404sub prepare_body_chunk {
405 my ( $self, $c, $chunk ) = @_;
4f5ebacd 406
0f56bbcf 407 $c->request->_body->add($chunk);
4bd82c41 408}
409
b5ecfcf0 410=head2 $self->prepare_body_parameters($c)
06e1b616 411
b0ad47c1 412Sets up parameters from body.
4ab87e27 413
06e1b616 414=cut
415
fbcc39ad 416sub prepare_body_parameters {
417 my ( $self, $c ) = @_;
b0ad47c1 418
0f56bbcf 419 return unless $c->request->_body;
b0ad47c1 420
0f56bbcf 421 $c->request->body_parameters( $c->request->_body->param );
fbcc39ad 422}
0556eb49 423
b5ecfcf0 424=head2 $self->prepare_connection($c)
0556eb49 425
4ab87e27 426Abstract method implemented in engines.
427
0556eb49 428=cut
429
44d28c7d 430sub prepare_connection {
431 my ($self, $ctx) = @_;
432
433 my $env = $self->env;
434 my $request = $ctx->request;
435
436 $request->address( $env->{REMOTE_ADDR} );
437 $request->hostname( $env->{REMOTE_HOST} )
438 if exists $env->{REMOTE_HOST};
439 $request->protocol( $env->{SERVER_PROTOCOL} );
440 $request->remote_user( $env->{REMOTE_USER} );
441 $request->method( $env->{REQUEST_METHOD} );
c9de76f0 442 $request->secure( $env->{'psgi.url_scheme'} eq 'https' ? 1 : 0 );
44d28c7d 443
444 return;
445}
0556eb49 446
b5ecfcf0 447=head2 $self->prepare_cookies($c)
fc7ec1d9 448
fa32ac82 449Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
4ab87e27 450
fc7ec1d9 451=cut
452
6dc87a0f 453sub prepare_cookies {
fbcc39ad 454 my ( $self, $c ) = @_;
6dc87a0f 455
456 if ( my $header = $c->request->header('Cookie') ) {
fa32ac82 457 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
6dc87a0f 458 }
459}
fc7ec1d9 460
b5ecfcf0 461=head2 $self->prepare_headers($c)
fc7ec1d9 462
463=cut
464
44d28c7d 465sub prepare_headers {
466 my ($self, $ctx) = @_;
467
468 my $env = $self->env;
469 my $headers = $ctx->request->headers;
470
471 for my $header (keys %{ $env }) {
472 next unless $header =~ /^(HTTP|CONTENT|COOKIE)/i;
473 (my $field = $header) =~ s/^HTTPS?_//;
474 $field =~ tr/_/-/;
475 $headers->header($field => $env->{$header});
476 }
477}
fc7ec1d9 478
b5ecfcf0 479=head2 $self->prepare_parameters($c)
fc7ec1d9 480
4ab87e27 481sets up parameters from query and post parameters.
482
fc7ec1d9 483=cut
484
fbcc39ad 485sub prepare_parameters {
486 my ( $self, $c ) = @_;
fc7ec1d9 487
7fa2c9c1 488 my $request = $c->request;
489 my $parameters = $request->parameters;
490 my $body_parameters = $request->body_parameters;
491 my $query_parameters = $request->query_parameters;
fbcc39ad 492 # We copy, no references
91772de9 493 foreach my $name (keys %$query_parameters) {
494 my $param = $query_parameters->{$name};
7fa2c9c1 495 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
fbcc39ad 496 }
fc7ec1d9 497
fbcc39ad 498 # Merge query and body parameters
91772de9 499 foreach my $name (keys %$body_parameters) {
500 my $param = $body_parameters->{$name};
7fa2c9c1 501 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
502 if ( my $existing = $parameters->{$name} ) {
503 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
fbcc39ad 504 }
7fa2c9c1 505 $parameters->{$name} = @values > 1 ? \@values : $values[0];
fbcc39ad 506 }
507}
508
b5ecfcf0 509=head2 $self->prepare_path($c)
fc7ec1d9 510
4ab87e27 511abstract method, implemented by engines.
512
fc7ec1d9 513=cut
514
44d28c7d 515sub prepare_path {
516 my ($self, $ctx) = @_;
517
518 my $env = $self->env;
519
520 my $scheme = $ctx->request->secure ? 'https' : 'http';
521 my $host = $env->{HTTP_HOST} || $env->{SERVER_NAME};
522 my $port = $env->{SERVER_PORT} || 80;
523 my $base_path = $env->{SCRIPT_NAME} || "/";
524
525 # set the request URI
526 my $req_uri = $env->{REQUEST_URI};
527 $req_uri =~ s/\?.*$//;
c9de76f0 528 my $path = $req_uri;
44d28c7d 529 $path =~ s{^/+}{};
530
531 # Using URI directly is way too slow, so we construct the URLs manually
532 my $uri_class = "URI::$scheme";
533
534 # HTTP_HOST will include the port even if it's 80/443
535 $host =~ s/:(?:80|443)$//;
536
537 if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
538 $host .= ":$port";
539 }
540
44d28c7d 541 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
542 my $uri = $scheme . '://' . $host . '/' . $path . $query;
543
4ee03d72 544 $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
44d28c7d 545
546 # set the base URI
547 # base must end in a slash
548 $base_path .= '/' unless $base_path =~ m{/$};
549
550 my $base_uri = $scheme . '://' . $host . $base_path;
551
552 $ctx->request->base( bless \$base_uri, $uri_class );
553
554 return;
555}
fc7ec1d9 556
b5ecfcf0 557=head2 $self->prepare_request($c)
fc7ec1d9 558
b5ecfcf0 559=head2 $self->prepare_query_parameters($c)
fc7ec1d9 560
4ab87e27 561process the query string and extract query parameters.
562
fc7ec1d9 563=cut
564
e0616220 565sub prepare_query_parameters {
44d28c7d 566 my ($self, $c) = @_;
567
568 my $query_string = exists $self->env->{QUERY_STRING}
569 ? $self->env->{QUERY_STRING}
570 : '';
b0ad47c1 571
3b4d1251 572 # Check for keywords (no = signs)
573 # (yes, index() is faster than a regex :))
933ba403 574 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 575 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 576 return;
577 }
578
579 my %query;
e0616220 580
581 # replace semi-colons
582 $query_string =~ s/;/&/g;
b0ad47c1 583
2f381252 584 my @params = grep { length $_ } split /&/, $query_string;
e0616220 585
933ba403 586 for my $item ( @params ) {
b0ad47c1 587
588 my ($param, $value)
933ba403 589 = map { $self->unescape_uri($_) }
e5542b70 590 split( /=/, $item, 2 );
b0ad47c1 591
933ba403 592 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 593
933ba403 594 if ( exists $query{$param} ) {
595 if ( ref $query{$param} ) {
596 push @{ $query{$param} }, $value;
597 }
598 else {
599 $query{$param} = [ $query{$param}, $value ];
600 }
601 }
602 else {
603 $query{$param} = $value;
604 }
e0616220 605 }
933ba403 606
607 $c->request->query_parameters( \%query );
e0616220 608}
fbcc39ad 609
b5ecfcf0 610=head2 $self->prepare_read($c)
fbcc39ad 611
4ab87e27 612prepare to read from the engine.
613
fbcc39ad 614=cut
fc7ec1d9 615
fbcc39ad 616sub prepare_read {
617 my ( $self, $c ) = @_;
4f5ebacd 618
878b821c 619 # Initialize the read position
4f5ebacd 620 $self->read_position(0);
b0ad47c1 621
878b821c 622 # Initialize the amount of data we think we need to read
623 $self->read_length( $c->request->header('Content-Length') || 0 );
fbcc39ad 624}
fc7ec1d9 625
b5ecfcf0 626=head2 $self->prepare_request(@arguments)
fc7ec1d9 627
4ab87e27 628Populate the context object from the request object.
629
fc7ec1d9 630=cut
631
44d28c7d 632sub prepare_request {
633 my ($self, $ctx, %args) = @_;
634 $self->_set_env($args{env});
635}
fc7ec1d9 636
b5ecfcf0 637=head2 $self->prepare_uploads($c)
c9afa5fc 638
fbcc39ad 639=cut
640
641sub prepare_uploads {
642 my ( $self, $c ) = @_;
7fa2c9c1 643
644 my $request = $c->request;
0f56bbcf 645 return unless $request->_body;
7fa2c9c1 646
0f56bbcf 647 my $uploads = $request->_body->upload;
7fa2c9c1 648 my $parameters = $request->parameters;
91772de9 649 foreach my $name (keys %$uploads) {
650 my $files = $uploads->{$name};
fbcc39ad 651 my @uploads;
7fa2c9c1 652 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
653 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
654 my $u = Catalyst::Request::Upload->new
655 (
656 size => $upload->{size},
a160c98d 657 type => scalar $headers->content_type,
7fa2c9c1 658 headers => $headers,
659 tempname => $upload->{tempname},
660 filename => $upload->{filename},
661 );
fbcc39ad 662 push @uploads, $u;
663 }
7fa2c9c1 664 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 665
c4bed79a 666 # support access to the filename as a normal param
667 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 668 # append, if there's already params with this name
7fa2c9c1 669 if (exists $parameters->{$name}) {
670 if (ref $parameters->{$name} eq 'ARRAY') {
671 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 672 }
673 else {
7fa2c9c1 674 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 675 }
676 }
677 else {
7fa2c9c1 678 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 679 }
fbcc39ad 680 }
681}
682
b5ecfcf0 683=head2 $self->prepare_write($c)
c9afa5fc 684
4ab87e27 685Abstract method. Implemented by the engines.
686
c9afa5fc 687=cut
688
fbcc39ad 689sub prepare_write { }
690
b5ecfcf0 691=head2 $self->read($c, [$maxlength])
fbcc39ad 692
ea72fece 693Reads from the input stream by calling C<< $self->read_chunk >>.
694
695Maintains the read_length and read_position counters as data is read.
696
fbcc39ad 697=cut
698
699sub read {
700 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 701
fbcc39ad 702 my $remaining = $self->read_length - $self->read_position;
4bd82c41 703 $maxlength ||= $CHUNKSIZE;
4f5ebacd 704
fbcc39ad 705 # Are we done reading?
706 if ( $remaining <= 0 ) {
4f5ebacd 707 $self->finalize_read($c);
fbcc39ad 708 return;
709 }
c9afa5fc 710
fbcc39ad 711 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
712 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
713 if ( defined $rc ) {
ea72fece 714 if (0 == $rc) { # Nothing more to read even though Content-Length
9e1f645b 715 # said there should be.
ea72fece 716 $self->finalize_read;
717 return;
718 }
fbcc39ad 719 $self->read_position( $self->read_position + $rc );
720 return $buffer;
721 }
722 else {
4f5ebacd 723 Catalyst::Exception->throw(
724 message => "Unknown error reading input: $!" );
fbcc39ad 725 }
726}
fc7ec1d9 727
b5ecfcf0 728=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 729
10011c19 730Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 731of data. Returns the number of bytes read. A return of 0 indicates that
732there is no more data to be read.
fc7ec1d9 733
fbcc39ad 734=cut
61b1e958 735
e6b46d80 736sub read_chunk {
ce7abbda 737 my ($self, $ctx) = (shift, shift);
e6b46d80 738 return $self->env->{'psgi.input'}->read(@_);
739}
61b1e958 740
b5ecfcf0 741=head2 $self->read_length
ca39d576 742
fbcc39ad 743The length of input data to be read. This is obtained from the Content-Length
744header.
fc7ec1d9 745
b5ecfcf0 746=head2 $self->read_position
fc7ec1d9 747
fbcc39ad 748The amount of input data that has already been read.
63b763c5 749
9560b708 750=head2 $self->run($app, $server)
63b763c5 751
9560b708 752Start the engine. Builds a PSGI application and calls the
753run method on the server passed in..
4ab87e27 754
fbcc39ad 755=cut
fc7ec1d9 756
44d28c7d 757sub run {
4b0f97fc 758 my ($self, $app, @args) = @_;
759 my $server = pop @args if blessed $args[-1];
9560b708 760 $server ||= Plack::Loader->auto(); # We're not being called from a script,
29bb04ad 761 # so auto detect what backend to run on.
762 # This does *NOT* cover mod_perl.
a1791811 763 # FIXME - Do something sensible with the options we're passed
4b0f97fc 764 my $psgi = $self->build_psgi_app($app, @args);
765 $server->run($psgi);
a1791811 766}
44d28c7d 767
9560b708 768=head2 build_psgi_app ($app, @args)
769
770Builds and returns a PSGI application closure, wrapping it in the reverse proxy
771middleware if the using_frontend_proxy config setting is set.
772
773=cut
774
22a5833d 775sub build_psgi_app {
a1791811 776 my ($self, $app, @args) = @_;
c2f4a965 777
778 my $psgi_app = sub {
44d28c7d 779 my ($env) = @_;
780
781 return sub {
782 my ($respond) = @_;
783 $self->_set_response_cb($respond);
784 $app->handle_request(env => $env);
785 };
786 };
c2f4a965 787
788 $psgi_app = Plack::Middleware::Conditional->wrap(
789 $psgi_app,
790 condition => sub {
791 my ($env) = @_;
792 return if $app->config->{ignore_frontend_proxy};
793 return $env->{REMOTE_ADDR} eq '127.0.0.1' || $app->config->{using_frontend_proxy};
794 },
795 builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
796 );
797
798 return $psgi_app;
44d28c7d 799}
fc7ec1d9 800
b5ecfcf0 801=head2 $self->write($c, $buffer)
fc7ec1d9 802
e512dd24 803Writes the buffer to the client.
4ab87e27 804
fc7ec1d9 805=cut
806
fbcc39ad 807sub write {
808 my ( $self, $c, $buffer ) = @_;
4f5ebacd 809
02570318 810 unless ( $self->_prepared_write ) {
4f5ebacd 811 $self->prepare_write($c);
02570318 812 $self->_prepared_write(1);
fc7ec1d9 813 }
b0ad47c1 814
094a0974 815 return 0 if !defined $buffer;
b0ad47c1 816
44d28c7d 817 my $len = length($buffer);
818 $self->_writer->write($buffer);
b0ad47c1 819
44d28c7d 820 return $len;
fc7ec1d9 821}
822
933ba403 823=head2 $self->unescape_uri($uri)
824
6a44fe01 825Unescapes a given URI using the most efficient method available. Engines such
826as Apache may implement this using Apache's C-based modules, for example.
933ba403 827
828=cut
829
830sub unescape_uri {
8c7d83e1 831 my ( $self, $str ) = @_;
7d22a537 832
833 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
834
8c7d83e1 835 return $str;
933ba403 836}
34d28dfd 837
4ab87e27 838=head2 $self->finalize_output
839
840<obsolete>, see finalize_body
841
0c76ec45 842=head2 $self->env
843
6356febf 844Hash containing environment variables including many special variables inserted
0c76ec45 845by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
846
6356febf 847Before accessing environment variables consider whether the same information is
0c76ec45 848not directly available via Catalyst objects $c->request, $c->engine ...
849
6356febf 850BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 851application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
852as in some enviroments the %ENV hash does not contain what you would expect.
853
fbcc39ad 854=head1 AUTHORS
855
2f381252 856Catalyst Contributors, see Catalyst.pm
fc7ec1d9 857
858=head1 COPYRIGHT
859
536bee89 860This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 861the same terms as Perl itself.
862
863=cut
864
8651;