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