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