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