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