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