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