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