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