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