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