Fix spelling errors - RT#54335
[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
7fa2c9c1 314 my $request = $c->request;
91772de9 315 foreach my $key (keys %{ $request->uploads }) {
316 my $upload = $request->uploads->{$key};
7fa2c9c1 317 unlink grep { -e $_ } map { $_->tempname }
318 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
c85ff642 319 }
7fa2c9c1 320
fc7ec1d9 321}
322
b5ecfcf0 323=head2 $self->prepare_body($c)
fc7ec1d9 324
4ab87e27 325sets up the L<Catalyst::Request> object body using L<HTTP::Body>
326
fc7ec1d9 327=cut
328
fbcc39ad 329sub prepare_body {
330 my ( $self, $c ) = @_;
99fe1710 331
df960201 332 my $appclass = ref($c) || $c;
878b821c 333 if ( my $length = $self->read_length ) {
7fa2c9c1 334 my $request = $c->request;
0f56bbcf 335 unless ( $request->_body ) {
7fa2c9c1 336 my $type = $request->header('Content-Type');
0f56bbcf 337 $request->_body(HTTP::Body->new( $type, $length ));
df960201 338 $request->_body->tmpdir( $appclass->config->{uploadtmp} )
339 if exists $appclass->config->{uploadtmp};
847e3257 340 }
b0ad47c1 341
ea72fece 342 # Check for definedness as you could read '0'
343 while ( defined ( my $buffer = $self->read($c) ) ) {
4f5ebacd 344 $c->prepare_body_chunk($buffer);
fbcc39ad 345 }
fdb3773e 346
347 # paranoia against wrong Content-Length header
847e3257 348 my $remaining = $length - $self->read_position;
34d28dfd 349 if ( $remaining > 0 ) {
fdb3773e 350 $self->finalize_read($c);
34d28dfd 351 Catalyst::Exception->throw(
847e3257 352 "Wrong Content-Length value: $length" );
fdb3773e 353 }
fc7ec1d9 354 }
847e3257 355 else {
356 # Defined but will cause all body code to be skipped
0f56bbcf 357 $c->request->_body(0);
847e3257 358 }
fc7ec1d9 359}
360
b5ecfcf0 361=head2 $self->prepare_body_chunk($c)
4bd82c41 362
4ab87e27 363Add a chunk to the request body.
364
4bd82c41 365=cut
366
367sub prepare_body_chunk {
368 my ( $self, $c, $chunk ) = @_;
4f5ebacd 369
0f56bbcf 370 $c->request->_body->add($chunk);
4bd82c41 371}
372
b5ecfcf0 373=head2 $self->prepare_body_parameters($c)
06e1b616 374
b0ad47c1 375Sets up parameters from body.
4ab87e27 376
06e1b616 377=cut
378
fbcc39ad 379sub prepare_body_parameters {
380 my ( $self, $c ) = @_;
b0ad47c1 381
0f56bbcf 382 return unless $c->request->_body;
b0ad47c1 383
0f56bbcf 384 $c->request->body_parameters( $c->request->_body->param );
fbcc39ad 385}
0556eb49 386
b5ecfcf0 387=head2 $self->prepare_connection($c)
0556eb49 388
4ab87e27 389Abstract method implemented in engines.
390
0556eb49 391=cut
392
393sub prepare_connection { }
394
b5ecfcf0 395=head2 $self->prepare_cookies($c)
fc7ec1d9 396
fa32ac82 397Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
4ab87e27 398
fc7ec1d9 399=cut
400
6dc87a0f 401sub prepare_cookies {
fbcc39ad 402 my ( $self, $c ) = @_;
6dc87a0f 403
404 if ( my $header = $c->request->header('Cookie') ) {
fa32ac82 405 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
6dc87a0f 406 }
407}
fc7ec1d9 408
b5ecfcf0 409=head2 $self->prepare_headers($c)
fc7ec1d9 410
411=cut
412
413sub prepare_headers { }
414
b5ecfcf0 415=head2 $self->prepare_parameters($c)
fc7ec1d9 416
4ab87e27 417sets up parameters from query and post parameters.
418
fc7ec1d9 419=cut
420
fbcc39ad 421sub prepare_parameters {
422 my ( $self, $c ) = @_;
fc7ec1d9 423
7fa2c9c1 424 my $request = $c->request;
425 my $parameters = $request->parameters;
426 my $body_parameters = $request->body_parameters;
427 my $query_parameters = $request->query_parameters;
fbcc39ad 428 # We copy, no references
91772de9 429 foreach my $name (keys %$query_parameters) {
430 my $param = $query_parameters->{$name};
7fa2c9c1 431 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
fbcc39ad 432 }
fc7ec1d9 433
fbcc39ad 434 # Merge query and body parameters
91772de9 435 foreach my $name (keys %$body_parameters) {
436 my $param = $body_parameters->{$name};
7fa2c9c1 437 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
438 if ( my $existing = $parameters->{$name} ) {
439 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
fbcc39ad 440 }
7fa2c9c1 441 $parameters->{$name} = @values > 1 ? \@values : $values[0];
fbcc39ad 442 }
443}
444
b5ecfcf0 445=head2 $self->prepare_path($c)
fc7ec1d9 446
4ab87e27 447abstract method, implemented by engines.
448
fc7ec1d9 449=cut
450
451sub prepare_path { }
452
b5ecfcf0 453=head2 $self->prepare_request($c)
fc7ec1d9 454
b5ecfcf0 455=head2 $self->prepare_query_parameters($c)
fc7ec1d9 456
4ab87e27 457process the query string and extract query parameters.
458
fc7ec1d9 459=cut
460
e0616220 461sub prepare_query_parameters {
462 my ( $self, $c, $query_string ) = @_;
b0ad47c1 463
3b4d1251 464 # Check for keywords (no = signs)
465 # (yes, index() is faster than a regex :))
933ba403 466 if ( index( $query_string, '=' ) < 0 ) {
3b4d1251 467 $c->request->query_keywords( $self->unescape_uri($query_string) );
933ba403 468 return;
469 }
470
471 my %query;
e0616220 472
473 # replace semi-colons
474 $query_string =~ s/;/&/g;
b0ad47c1 475
2f381252 476 my @params = grep { length $_ } split /&/, $query_string;
e0616220 477
933ba403 478 for my $item ( @params ) {
b0ad47c1 479
480 my ($param, $value)
933ba403 481 = map { $self->unescape_uri($_) }
e5542b70 482 split( /=/, $item, 2 );
b0ad47c1 483
933ba403 484 $param = $self->unescape_uri($item) unless defined $param;
b0ad47c1 485
933ba403 486 if ( exists $query{$param} ) {
487 if ( ref $query{$param} ) {
488 push @{ $query{$param} }, $value;
489 }
490 else {
491 $query{$param} = [ $query{$param}, $value ];
492 }
493 }
494 else {
495 $query{$param} = $value;
496 }
e0616220 497 }
933ba403 498
499 $c->request->query_parameters( \%query );
e0616220 500}
fbcc39ad 501
b5ecfcf0 502=head2 $self->prepare_read($c)
fbcc39ad 503
4ab87e27 504prepare to read from the engine.
505
fbcc39ad 506=cut
fc7ec1d9 507
fbcc39ad 508sub prepare_read {
509 my ( $self, $c ) = @_;
4f5ebacd 510
878b821c 511 # Initialize the read position
4f5ebacd 512 $self->read_position(0);
b0ad47c1 513
878b821c 514 # Initialize the amount of data we think we need to read
515 $self->read_length( $c->request->header('Content-Length') || 0 );
fbcc39ad 516}
fc7ec1d9 517
b5ecfcf0 518=head2 $self->prepare_request(@arguments)
fc7ec1d9 519
4ab87e27 520Populate the context object from the request object.
521
fc7ec1d9 522=cut
523
fbcc39ad 524sub prepare_request { }
fc7ec1d9 525
b5ecfcf0 526=head2 $self->prepare_uploads($c)
c9afa5fc 527
fbcc39ad 528=cut
529
530sub prepare_uploads {
531 my ( $self, $c ) = @_;
7fa2c9c1 532
533 my $request = $c->request;
0f56bbcf 534 return unless $request->_body;
7fa2c9c1 535
0f56bbcf 536 my $uploads = $request->_body->upload;
7fa2c9c1 537 my $parameters = $request->parameters;
91772de9 538 foreach my $name (keys %$uploads) {
539 my $files = $uploads->{$name};
fbcc39ad 540 my @uploads;
7fa2c9c1 541 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
542 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
543 my $u = Catalyst::Request::Upload->new
544 (
545 size => $upload->{size},
a160c98d 546 type => scalar $headers->content_type,
7fa2c9c1 547 headers => $headers,
548 tempname => $upload->{tempname},
549 filename => $upload->{filename},
550 );
fbcc39ad 551 push @uploads, $u;
552 }
7fa2c9c1 553 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 554
c4bed79a 555 # support access to the filename as a normal param
556 my @filenames = map { $_->{filename} } @uploads;
a7e05d9d 557 # append, if there's already params with this name
7fa2c9c1 558 if (exists $parameters->{$name}) {
559 if (ref $parameters->{$name} eq 'ARRAY') {
560 push @{ $parameters->{$name} }, @filenames;
a7e05d9d 561 }
562 else {
7fa2c9c1 563 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
a7e05d9d 564 }
565 }
566 else {
7fa2c9c1 567 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
a7e05d9d 568 }
fbcc39ad 569 }
570}
571
b5ecfcf0 572=head2 $self->prepare_write($c)
c9afa5fc 573
4ab87e27 574Abstract method. Implemented by the engines.
575
c9afa5fc 576=cut
577
fbcc39ad 578sub prepare_write { }
579
b5ecfcf0 580=head2 $self->read($c, [$maxlength])
fbcc39ad 581
ea72fece 582Reads from the input stream by calling C<< $self->read_chunk >>.
583
584Maintains the read_length and read_position counters as data is read.
585
fbcc39ad 586=cut
587
588sub read {
589 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 590
fbcc39ad 591 my $remaining = $self->read_length - $self->read_position;
4bd82c41 592 $maxlength ||= $CHUNKSIZE;
4f5ebacd 593
fbcc39ad 594 # Are we done reading?
595 if ( $remaining <= 0 ) {
4f5ebacd 596 $self->finalize_read($c);
fbcc39ad 597 return;
598 }
c9afa5fc 599
fbcc39ad 600 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
601 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
602 if ( defined $rc ) {
ea72fece 603 if (0 == $rc) { # Nothing more to read even though Content-Length
604 # said there should be. FIXME - Warn in the log here?
605 $self->finalize_read;
606 return;
607 }
fbcc39ad 608 $self->read_position( $self->read_position + $rc );
609 return $buffer;
610 }
611 else {
4f5ebacd 612 Catalyst::Exception->throw(
613 message => "Unknown error reading input: $!" );
fbcc39ad 614 }
615}
fc7ec1d9 616
b5ecfcf0 617=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 618
10011c19 619Each engine implements read_chunk as its preferred way of reading a chunk
ea72fece 620of data. Returns the number of bytes read. A return of 0 indicates that
621there is no more data to be read.
fc7ec1d9 622
fbcc39ad 623=cut
61b1e958 624
fbcc39ad 625sub read_chunk { }
61b1e958 626
b5ecfcf0 627=head2 $self->read_length
ca39d576 628
fbcc39ad 629The length of input data to be read. This is obtained from the Content-Length
630header.
fc7ec1d9 631
b5ecfcf0 632=head2 $self->read_position
fc7ec1d9 633
fbcc39ad 634The amount of input data that has already been read.
63b763c5 635
b5ecfcf0 636=head2 $self->run($c)
63b763c5 637
4ab87e27 638Start the engine. Implemented by the various engine classes.
639
fbcc39ad 640=cut
fc7ec1d9 641
fbcc39ad 642sub run { }
fc7ec1d9 643
b5ecfcf0 644=head2 $self->write($c, $buffer)
fc7ec1d9 645
e512dd24 646Writes the buffer to the client.
4ab87e27 647
fc7ec1d9 648=cut
649
fbcc39ad 650sub write {
651 my ( $self, $c, $buffer ) = @_;
4f5ebacd 652
02570318 653 unless ( $self->_prepared_write ) {
4f5ebacd 654 $self->prepare_write($c);
02570318 655 $self->_prepared_write(1);
fc7ec1d9 656 }
b0ad47c1 657
094a0974 658 return 0 if !defined $buffer;
b0ad47c1 659
d04b2ffd 660 my $len = length($buffer);
661 my $wrote = syswrite STDOUT, $buffer;
b0ad47c1 662
d04b2ffd 663 if ( !defined $wrote && $! == EWOULDBLOCK ) {
664 # Unable to write on the first try, will retry in the loop below
665 $wrote = 0;
666 }
b0ad47c1 667
d04b2ffd 668 if ( defined $wrote && $wrote < $len ) {
669 # We didn't write the whole buffer
670 while (1) {
671 my $ret = syswrite STDOUT, $buffer, $CHUNKSIZE, $wrote;
672 if ( defined $ret ) {
673 $wrote += $ret;
674 }
675 else {
676 next if $! == EWOULDBLOCK;
677 return;
678 }
b0ad47c1 679
d04b2ffd 680 last if $wrote >= $len;
e2b0ddd3 681 }
e512dd24 682 }
b0ad47c1 683
e512dd24 684 return $wrote;
fc7ec1d9 685}
686
933ba403 687=head2 $self->unescape_uri($uri)
688
6a44fe01 689Unescapes a given URI using the most efficient method available. Engines such
690as Apache may implement this using Apache's C-based modules, for example.
933ba403 691
692=cut
693
694sub unescape_uri {
8c7d83e1 695 my ( $self, $str ) = @_;
7d22a537 696
697 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
698
8c7d83e1 699 return $str;
933ba403 700}
34d28dfd 701
4ab87e27 702=head2 $self->finalize_output
703
704<obsolete>, see finalize_body
705
0c76ec45 706=head2 $self->env
707
6356febf 708Hash containing environment variables including many special variables inserted
0c76ec45 709by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
710
6356febf 711Before accessing environment variables consider whether the same information is
0c76ec45 712not directly available via Catalyst objects $c->request, $c->engine ...
713
6356febf 714BEWARE: If you really need to access some environment variable from your Catalyst
0c76ec45 715application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
716as in some enviroments the %ENV hash does not contain what you would expect.
717
fbcc39ad 718=head1 AUTHORS
719
2f381252 720Catalyst Contributors, see Catalyst.pm
fc7ec1d9 721
722=head1 COPYRIGHT
723
536bee89 724This library is free software. You can redistribute it and/or modify it under
fc7ec1d9 725the same terms as Perl itself.
726
727=cut
728
7291;