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