Doc fixes, get a bit nearer to working with mod_perl again
[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
7fa2c9c1 348 my $request = $c->request;
91772de9 349 foreach my $key (keys %{ $request->uploads }) {
350 my $upload = $request->uploads->{$key};
7fa2c9c1 351 unlink grep { -e $_ } map { $_->tempname }
352 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 353 }
7fa2c9c1 354
fc7ec1d9 355}
356
b5ecfcf0 357=head2 $self->prepare_body($c)
fc7ec1d9 358
4ab87e27 359sets up the L<Catalyst::Request> object body using L<HTTP::Body>
360
fc7ec1d9 361=cut
362
fbcc39ad 363sub prepare_body {
364 my ( $self, $c ) = @_;
99fe1710 365
df960201 366 my $appclass = ref($c) || $c;
878b821c 367 if ( my $length = $self->read_length ) {
7fa2c9c1 368 my $request = $c->request;
0f56bbcf 369 unless ( $request->_body ) {
7fa2c9c1 370 my $type = $request->header('Content-Type');
0f56bbcf 371 $request->_body(HTTP::Body->new( $type, $length ));
df960201 372 $request->_body->tmpdir( $appclass->config->{uploadtmp} )
373 if exists $appclass->config->{uploadtmp};
847e3257 374 }
b0ad47c1 375
ea72fece 376 # Check for definedness as you could read '0'
377 while ( defined ( my $buffer = $self->read($c) ) ) {
4f5ebacd 378 $c->prepare_body_chunk($buffer);
fbcc39ad 379 }
fdb3773e 380
381 # paranoia against wrong Content-Length header
847e3257 382 my $remaining = $length - $self->read_position;
34d28dfd 383 if ( $remaining > 0 ) {
fdb3773e 384 $self->finalize_read($c);
34d28dfd 385 Catalyst::Exception->throw(
847e3257 386 "Wrong Content-Length value: $length" );
fdb3773e 387 }
fc7ec1d9 388 }
847e3257 389 else {
390 # Defined but will cause all body code to be skipped
0f56bbcf 391 $c->request->_body(0);
847e3257 392 }
fc7ec1d9 393}
394
b5ecfcf0 395=head2 $self->prepare_body_chunk($c)
4bd82c41 396
4ab87e27 397Add a chunk to the request body.
398
4bd82c41 399=cut
400
401sub prepare_body_chunk {
402 my ( $self, $c, $chunk ) = @_;
4f5ebacd 403
0f56bbcf 404 $c->request->_body->add($chunk);
4bd82c41 405}
406
b5ecfcf0 407=head2 $self->prepare_body_parameters($c)
06e1b616 408
b0ad47c1 409Sets up parameters from body.
4ab87e27 410
06e1b616 411=cut
412
fbcc39ad 413sub prepare_body_parameters {
414 my ( $self, $c ) = @_;
b0ad47c1 415
0f56bbcf 416 return unless $c->request->_body;
b0ad47c1 417
0f56bbcf 418 $c->request->body_parameters( $c->request->_body->param );
fbcc39ad 419}
0556eb49 420
b5ecfcf0 421=head2 $self->prepare_connection($c)
0556eb49 422
4ab87e27 423Abstract method implemented in engines.
424
0556eb49 425=cut
426
44d28c7d 427sub prepare_connection {
428 my ($self, $ctx) = @_;
429
430 my $env = $self->env;
431 my $request = $ctx->request;
432
433 $request->address( $env->{REMOTE_ADDR} );
434 $request->hostname( $env->{REMOTE_HOST} )
435 if exists $env->{REMOTE_HOST};
436 $request->protocol( $env->{SERVER_PROTOCOL} );
437 $request->remote_user( $env->{REMOTE_USER} );
438 $request->method( $env->{REQUEST_METHOD} );
c9de76f0 439 $request->secure( $env->{'psgi.url_scheme'} eq 'https' ? 1 : 0 );
44d28c7d 440
441 return;
442}
0556eb49 443
b5ecfcf0 444=head2 $self->prepare_cookies($c)
fc7ec1d9 445
fa32ac82 446Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
4ab87e27 447
fc7ec1d9 448=cut
449
6dc87a0f 450sub prepare_cookies {
fbcc39ad 451 my ( $self, $c ) = @_;
6dc87a0f 452
453 if ( my $header = $c->request->header('Cookie') ) {
fa32ac82 454 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
6dc87a0f 455 }
456}
fc7ec1d9 457
b5ecfcf0 458=head2 $self->prepare_headers($c)
fc7ec1d9 459
460=cut
461
44d28c7d 462sub prepare_headers {
463 my ($self, $ctx) = @_;
464
465 my $env = $self->env;
466 my $headers = $ctx->request->headers;
467
468 for my $header (keys %{ $env }) {
469 next unless $header =~ /^(HTTP|CONTENT|COOKIE)/i;
470 (my $field = $header) =~ s/^HTTPS?_//;
471 $field =~ tr/_/-/;
472 $headers->header($field => $env->{$header});
473 }
474}
fc7ec1d9 475
b5ecfcf0 476=head2 $self->prepare_parameters($c)
fc7ec1d9 477
4ab87e27 478sets up parameters from query and post parameters.
479
fc7ec1d9 480=cut
481
fbcc39ad 482sub prepare_parameters {
483 my ( $self, $c ) = @_;
fc7ec1d9 484
7fa2c9c1 485 my $request = $c->request;
486 my $parameters = $request->parameters;
487 my $body_parameters = $request->body_parameters;
488 my $query_parameters = $request->query_parameters;
fbcc39ad 489 # We copy, no references
91772de9 490 foreach my $name (keys %$query_parameters) {
491 my $param = $query_parameters->{$name};
7fa2c9c1 492 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
fbcc39ad 493 }
fc7ec1d9 494
fbcc39ad 495 # Merge query and body parameters
91772de9 496 foreach my $name (keys %$body_parameters) {
497 my $param = $body_parameters->{$name};
7fa2c9c1 498 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
499 if ( my $existing = $parameters->{$name} ) {
500 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
fbcc39ad 501 }
7fa2c9c1 502 $parameters->{$name} = @values > 1 ? \@values : $values[0];
fbcc39ad 503 }
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
515 my $env = $self->env;
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
523 my $req_uri = $env->{REQUEST_URI};
524 $req_uri =~ s/\?.*$//;
c9de76f0 525 my $path = $req_uri;
44d28c7d 526 $path =~ s{^/+}{};
527
528 # Using URI directly is way too slow, so we construct the URLs manually
529 my $uri_class = "URI::$scheme";
530
531 # HTTP_HOST will include the port even if it's 80/443
532 $host =~ s/:(?:80|443)$//;
533
534 if ($port !~ /^(?:80|443)$/ && $host !~ /:/) {
535 $host .= ":$port";
536 }
537
44d28c7d 538 my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
539 my $uri = $scheme . '://' . $host . '/' . $path . $query;
540
4ee03d72 541 $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
44d28c7d 542
543 # set the base URI
544 # base must end in a slash
545 $base_path .= '/' unless $base_path =~ m{/$};
546
547 my $base_uri = $scheme . '://' . $host . $base_path;
548
549 $ctx->request->base( bless \$base_uri, $uri_class );
550
551 return;
552}
fc7ec1d9 553
b5ecfcf0 554=head2 $self->prepare_request($c)
fc7ec1d9 555
b5ecfcf0 556=head2 $self->prepare_query_parameters($c)
fc7ec1d9 557
4ab87e27 558process the query string and extract query parameters.
559
fc7ec1d9 560=cut
561
e0616220 562sub prepare_query_parameters {
44d28c7d 563 my ($self, $c) = @_;
564
565 my $query_string = exists $self->env->{QUERY_STRING}
566 ? $self->env->{QUERY_STRING}
567 : '';
b0ad47c1 568
3b4d1251 569 # Check for keywords (no = signs)
570 # (yes, index() is faster than a regex :))
933ba403 571 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 572 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 573 return;
574 }
575
576 my %query;
e0616220 577
578 # replace semi-colons
579 $query_string =~ s/;/&/g;
b0ad47c1 580
2f381252 581 my @params = grep { length $_ } split /&/, $query_string;
e0616220 582
933ba403 583 for my $item ( @params ) {
b0ad47c1 584
585 my ($param, $value)
933ba403 586 = map { $self->unescape_uri($_) }
e5542b70 587 split( /=/, $item, 2 );
b0ad47c1 588
933ba403 589 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 590
933ba403 591 if ( exists $query{$param} ) {
592 if ( ref $query{$param} ) {
593 push @{ $query{$param} }, $value;
594 }
595 else {
596 $query{$param} = [ $query{$param}, $value ];
597 }
598 }
599 else {
600 $query{$param} = $value;
601 }
e0616220 602 }
933ba403 603
604 $c->request->query_parameters( \%query );
e0616220 605}
fbcc39ad 606
b5ecfcf0 607=head2 $self->prepare_read($c)
fbcc39ad 608
4ab87e27 609prepare to read from the engine.
610
fbcc39ad 611=cut
fc7ec1d9 612
fbcc39ad 613sub prepare_read {
614 my ( $self, $c ) = @_;
4f5ebacd 615
878b821c 616 # Initialize the read position
4f5ebacd 617 $self->read_position(0);
b0ad47c1 618
878b821c 619 # Initialize the amount of data we think we need to read
620 $self->read_length( $c->request->header('Content-Length') || 0 );
fbcc39ad 621}
fc7ec1d9 622
b5ecfcf0 623=head2 $self->prepare_request(@arguments)
fc7ec1d9 624
4ab87e27 625Populate the context object from the request object.
626
fc7ec1d9 627=cut
628
44d28c7d 629sub prepare_request {
630 my ($self, $ctx, %args) = @_;
631 $self->_set_env($args{env});
632}
fc7ec1d9 633
b5ecfcf0 634=head2 $self->prepare_uploads($c)
c9afa5fc 635
fbcc39ad 636=cut
637
638sub prepare_uploads {
639 my ( $self, $c ) = @_;
7fa2c9c1 640
641 my $request = $c->request;
0f56bbcf 642 return unless $request->_body;
7fa2c9c1 643
0f56bbcf 644 my $uploads = $request->_body->upload;
7fa2c9c1 645 my $parameters = $request->parameters;
91772de9 646 foreach my $name (keys %$uploads) {
647 my $files = $uploads->{$name};
fbcc39ad 648 my @uploads;
7fa2c9c1 649 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
650 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
651 my $u = Catalyst::Request::Upload->new
652 (
653 size => $upload->{size},
654 type => $headers->content_type,
655 headers => $headers,
656 tempname => $upload->{tempname},
657 filename => $upload->{filename},
658 );
fbcc39ad 659 push @uploads, $u;
660 }
7fa2c9c1 661 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 662
c4bed79a 663 # support access to the filename as a normal param
664 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 665 # append, if there's already params with this name
7fa2c9c1 666 if (exists $parameters->{$name}) {
667 if (ref $parameters->{$name} eq 'ARRAY') {
668 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 669 }
670 else {
7fa2c9c1 671 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 672 }
673 }
674 else {
7fa2c9c1 675 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 676 }
fbcc39ad 677 }
678}
679
b5ecfcf0 680=head2 $self->prepare_write($c)
c9afa5fc 681
4ab87e27 682Abstract method. Implemented by the engines.
683
c9afa5fc 684=cut
685
fbcc39ad 686sub prepare_write { }
687
b5ecfcf0 688=head2 $self->read($c, [$maxlength])
fbcc39ad 689
ea72fece 690Reads from the input stream by calling C<< $self->read_chunk >>.
691
692Maintains the read_length and read_position counters as data is read.
693
fbcc39ad 694=cut
695
696sub read {
697 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 698
fbcc39ad 699 my $remaining = $self->read_length - $self->read_position;
4bd82c41 700 $maxlength ||= $CHUNKSIZE;
4f5ebacd 701
fbcc39ad 702 # Are we done reading?
703 if ( $remaining <= 0 ) {
4f5ebacd 704 $self->finalize_read($c);
fbcc39ad 705 return;
706 }
c9afa5fc 707
fbcc39ad 708 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
709 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
710 if ( defined $rc ) {
ea72fece 711 if (0 == $rc) { # Nothing more to read even though Content-Length
9e1f645b 712 # said there should be.
ea72fece 713 $self->finalize_read;
714 return;
715 }
fbcc39ad 716 $self->read_position( $self->read_position + $rc );
717 return $buffer;
718 }
719 else {
4f5ebacd 720 Catalyst::Exception->throw(
721 message => "Unknown error reading input: $!" );
fbcc39ad 722 }
723}
fc7ec1d9 724
b5ecfcf0 725=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 726
10011c19 727Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 728of data. Returns the number of bytes read. A return of 0 indicates that
729there is no more data to be read.
fc7ec1d9 730
fbcc39ad 731=cut
61b1e958 732
e6b46d80 733sub read_chunk {
ce7abbda 734 my ($self, $ctx) = (shift, shift);
e6b46d80 735 return $self->env->{'psgi.input'}->read(@_);
736}
61b1e958 737
b5ecfcf0 738=head2 $self->read_length
ca39d576 739
fbcc39ad 740The length of input data to be read. This is obtained from the Content-Length
741header.
fc7ec1d9 742
b5ecfcf0 743=head2 $self->read_position
fc7ec1d9 744
fbcc39ad 745The amount of input data that has already been read.
63b763c5 746
9560b708 747=head2 $self->run($app, $server)
63b763c5 748
9560b708 749Start the engine. Builds a PSGI application and calls the
750run method on the server passed in..
4ab87e27 751
fbcc39ad 752=cut
fc7ec1d9 753
44d28c7d 754sub run {
c821df21 755 my ($self, $app, $server, @args) = @_;
9560b708 756 $server ||= Plack::Loader->auto(); # We're not being called from a script,
757 # so auto detect mod_perl or whatever
a1791811 758 # FIXME - Do something sensible with the options we're passed
22a5833d 759 $server->run($self->build_psgi_app($app, @args));
a1791811 760}
44d28c7d 761
9560b708 762=head2 build_psgi_app ($app, @args)
763
764Builds and returns a PSGI application closure, wrapping it in the reverse proxy
765middleware if the using_frontend_proxy config setting is set.
766
767=cut
768
22a5833d 769sub build_psgi_app {
a1791811 770 my ($self, $app, @args) = @_;
c2f4a965 771
772 my $psgi_app = sub {
44d28c7d 773 my ($env) = @_;
774
775 return sub {
776 my ($respond) = @_;
777 $self->_set_response_cb($respond);
778 $app->handle_request(env => $env);
779 };
780 };
c2f4a965 781
782 $psgi_app = Plack::Middleware::Conditional->wrap(
783 $psgi_app,
784 condition => sub {
785 my ($env) = @_;
786 return if $app->config->{ignore_frontend_proxy};
787 return $env->{REMOTE_ADDR} eq '127.0.0.1' || $app->config->{using_frontend_proxy};
788 },
789 builder => sub { Plack::Middleware::ReverseProxy->wrap($_[0]) },
790 );
791
792 return $psgi_app;
44d28c7d 793}
fc7ec1d9 794
b5ecfcf0 795=head2 $self->write($c, $buffer)
fc7ec1d9 796
e512dd24 797Writes the buffer to the client.
4ab87e27 798
fc7ec1d9 799=cut
800
fbcc39ad 801sub write {
802 my ( $self, $c, $buffer ) = @_;
4f5ebacd 803
02570318 804 unless ( $self->_prepared_write ) {
4f5ebacd 805 $self->prepare_write($c);
02570318 806 $self->_prepared_write(1);
fc7ec1d9 807 }
b0ad47c1 808
094a0974 809 return 0 if !defined $buffer;
b0ad47c1 810
44d28c7d 811 my $len = length($buffer);
812 $self->_writer->write($buffer);
b0ad47c1 813
44d28c7d 814 return $len;
fc7ec1d9 815}
816
933ba403 817=head2 $self->unescape_uri($uri)
818
6a44fe01 819Unescapes a given URI using the most efficient method available. Engines such
820as Apache may implement this using Apache's C-based modules, for example.
933ba403 821
822=cut
823
824sub unescape_uri {
8c7d83e1 825 my ( $self, $str ) = @_;
7d22a537 826
827 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
828
8c7d83e1 829 return $str;
933ba403 830}
34d28dfd 831
4ab87e27 832=head2 $self->finalize_output
833
834<obsolete>, see finalize_body
835
0c76ec45 836=head2 $self->env
837
838Hash containing enviroment variables including many special variables inserted
839by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
840
841Before accesing enviroment variables consider whether the same information is
842not directly available via Catalyst objects $c->request, $c->engine ...
843
844BEWARE: If you really need to access some enviroment variable from your Catalyst
845application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
846as in some enviroments the %ENV hash does not contain what you would expect.
847
fbcc39ad 848=head1 AUTHORS
849
2f381252 850Catalyst Contributors, see Catalyst.pm
fc7ec1d9 851
852=head1 COPYRIGHT
853
536bee89 854This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 855the same terms as Perl itself.
856
857=cut
858
8591;