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