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