added docs for engine/dispatcher
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
fbcc39ad 4use base 'Class::Accessor::Fast';
6dc87a0f 5use CGI::Cookie;
fc7ec1d9 6use Data::Dumper;
7use HTML::Entities;
fbcc39ad 8use HTTP::Body;
fc7ec1d9 9use HTTP::Headers;
e0616220 10use URI::QueryParam;
fbcc39ad 11
12# input position and length
4f5ebacd 13__PACKAGE__->mk_accessors(qw/read_position read_length/);
fbcc39ad 14
15# Stringify to class
16use overload '""' => sub { return ref shift }, fallback => 1;
fc7ec1d9 17
4bd82c41 18# Amount of data to read from input on each pass
19our $CHUNKSIZE = 4096;
20
fc7ec1d9 21=head1 NAME
22
23Catalyst::Engine - The Catalyst Engine
24
25=head1 SYNOPSIS
26
27See L<Catalyst>.
28
29=head1 DESCRIPTION
30
23f9d934 31=head1 METHODS
fc7ec1d9 32
cd3bb248 33
b5ecfcf0 34=head2 $self->finalize_body($c)
06e1b616 35
fbcc39ad 36Finalize body. Prints the response output.
06e1b616 37
38=cut
39
fbcc39ad 40sub finalize_body {
41 my ( $self, $c ) = @_;
f4a57de4 42 if ( ref $c->response->body && $c->response->body->can('read') ) {
f4a57de4 43 while ( !$c->response->body->eof() ) {
6e692ab4 44 $c->response->body->read( my $buffer, $CHUNKSIZE );
6484fba0 45 last unless $self->write( $c, $buffer );
f4a57de4 46 }
6e692ab4 47 $c->response->body->close();
f4a57de4 48 }
49 else {
6e692ab4 50 $self->write( $c, $c->response->body );
f4a57de4 51 }
fbcc39ad 52}
6dc87a0f 53
b5ecfcf0 54=head2 $self->finalize_cookies($c)
6dc87a0f 55
4ab87e27 56Create CGI::Cookies from $c->res->cookies, and set them as response headers.
57
6dc87a0f 58=cut
59
60sub finalize_cookies {
fbcc39ad 61 my ( $self, $c ) = @_;
6dc87a0f 62
fbcc39ad 63 my @cookies;
6dc87a0f 64 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
fbcc39ad 65
6dc87a0f 66 my $cookie = CGI::Cookie->new(
67 -name => $name,
68 -value => $cookie->{value},
69 -expires => $cookie->{expires},
70 -domain => $cookie->{domain},
71 -path => $cookie->{path},
72 -secure => $cookie->{secure} || 0
73 );
74
fbcc39ad 75 push @cookies, $cookie->as_string;
6dc87a0f 76 }
6dc87a0f 77
b39840da 78 for my $cookie (@cookies) {
79 $c->res->headers->push_header( 'Set-Cookie' => $cookie );
fbcc39ad 80 }
81}
969647fd 82
b5ecfcf0 83=head2 $self->finalize_error($c)
969647fd 84
4ab87e27 85Output an apropriate error message, called if there's an error in $c
86after the dispatch has finished. Will output debug messages if Catalyst
87is in debug mode, or a `please come back later` message otherwise.
88
969647fd 89=cut
90
91sub finalize_error {
fbcc39ad 92 my ( $self, $c ) = @_;
969647fd 93
7299a7b4 94 $c->res->content_type('text/html; charset=utf-8');
969647fd 95 my $name = $c->config->{name} || 'Catalyst Application';
96
97 my ( $title, $error, $infos );
98 if ( $c->debug ) {
62d9b030 99
100 # For pretty dumps
101 local $Data::Dumper::Terse = 1;
b5ecfcf0 102 $error = join '', map {
103 '<p><code class="error">'
104 . encode_entities($_)
105 . '</code></p>'
106 } @{ $c->error };
969647fd 107 $error ||= 'No output';
2666dd3b 108 $error = qq{<pre wrap="">$error</pre>};
969647fd 109 $title = $name = "$name on Catalyst $Catalyst::VERSION";
d82cc9ae 110 $name = "<h1>$name</h1>";
fbcc39ad 111
112 # Don't show context in the dump
113 delete $c->req->{_context};
114 delete $c->res->{_context};
115
116 # Don't show body parser in the dump
117 delete $c->req->{_body};
118
119 # Don't show response header state in dump
120 delete $c->res->{_finalized_headers};
121
969647fd 122 my $req = encode_entities Dumper $c->req;
123 my $res = encode_entities Dumper $c->res;
124 my $stash = encode_entities Dumper $c->stash;
969647fd 125
c6ef5e69 126 my @infos;
127 my $i = 0;
c6ef5e69 128 for my $dump ( $c->dump_these ) {
c6ef5e69 129 my $name = $dump->[0];
130 my $value = encode_entities( Dumper $dump->[1] );
131 push @infos, sprintf <<"EOF", $name, $value;
9619f23c 132<h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
c6ef5e69 133<div id="dump_$i">
2666dd3b 134 <pre wrap="">%s</pre>
c6ef5e69 135</div>
136EOF
137 $i++;
138 }
139 $infos = join "\n", @infos;
969647fd 140 }
141 else {
142 $title = $name;
143 $error = '';
144 $infos = <<"";
145<pre>
146(en) Please come back later
147(de) Bitte versuchen sie es spaeter nocheinmal
d82cc9ae 148(at) Konnten's bitt'schoen spaeter nochmal reinschauen
969647fd 149(no) Vennligst prov igjen senere
d82cc9ae 150(dk) Venligst prov igen senere
151(pl) Prosze sprobowac pozniej
969647fd 152</pre>
153
154 $name = '';
155 }
e060fe05 156 $c->res->body( <<"" );
7299a7b4 157<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
158 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
159<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
969647fd 160<head>
7299a7b4 161 <meta http-equiv="Content-Language" content="en" />
162 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
969647fd 163 <title>$title</title>
7299a7b4 164 <script type="text/javascript">
c6ef5e69 165 <!--
166 function toggleDump (dumpElement) {
7299a7b4 167 var e = document.getElementById( dumpElement );
168 if (e.style.display == "none") {
169 e.style.display = "";
c6ef5e69 170 }
171 else {
7299a7b4 172 e.style.display = "none";
c6ef5e69 173 }
174 }
175 -->
176 </script>
969647fd 177 <style type="text/css">
178 body {
179 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
180 Tahoma, Arial, helvetica, sans-serif;
181 color: #ddd;
182 background-color: #eee;
183 margin: 0px;
184 padding: 0px;
185 }
c6ef5e69 186 :link, :link:hover, :visited, :visited:hover {
187 color: #ddd;
188 }
969647fd 189 div.box {
9619f23c 190 position: relative;
969647fd 191 background-color: #ccc;
192 border: 1px solid #aaa;
193 padding: 4px;
194 margin: 10px;
195 -moz-border-radius: 10px;
196 }
197 div.error {
198 background-color: #977;
199 border: 1px solid #755;
200 padding: 8px;
201 margin: 4px;
202 margin-bottom: 10px;
203 -moz-border-radius: 10px;
204 }
205 div.infos {
206 background-color: #797;
207 border: 1px solid #575;
208 padding: 8px;
209 margin: 4px;
210 margin-bottom: 10px;
211 -moz-border-radius: 10px;
212 }
213 div.name {
214 background-color: #779;
215 border: 1px solid #557;
216 padding: 8px;
217 margin: 4px;
218 -moz-border-radius: 10px;
219 }
7f8e0078 220 code.error {
221 display: block;
222 margin: 1em 0;
223 overflow: auto;
7f8e0078 224 }
9619f23c 225 div.name h1, div.error p {
226 margin: 0;
227 }
228 h2 {
229 margin-top: 0;
230 margin-bottom: 10px;
231 font-size: medium;
232 font-weight: bold;
233 text-decoration: underline;
234 }
235 h1 {
236 font-size: medium;
237 font-weight: normal;
238 }
2666dd3b 239 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
240 /* Browser specific (not valid) styles to make preformatted text wrap */
241 pre {
242 white-space: pre-wrap; /* css-3 */
243 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
244 white-space: -pre-wrap; /* Opera 4-6 */
245 white-space: -o-pre-wrap; /* Opera 7 */
246 word-wrap: break-word; /* Internet Explorer 5.5+ */
247 }
969647fd 248 </style>
249</head>
250<body>
251 <div class="box">
252 <div class="error">$error</div>
253 <div class="infos">$infos</div>
254 <div class="name">$name</div>
255 </div>
256</body>
257</html>
258
d82cc9ae 259
260 # Trick IE
261 $c->res->{body} .= ( ' ' x 512 );
262
263 # Return 500
33117422 264 $c->res->status(500);
969647fd 265}
266
b5ecfcf0 267=head2 $self->finalize_headers($c)
fc7ec1d9 268
4ab87e27 269Abstract method, allows engines to write headers to response
270
fc7ec1d9 271=cut
272
273sub finalize_headers { }
274
b5ecfcf0 275=head2 $self->finalize_read($c)
fc7ec1d9 276
277=cut
278
fbcc39ad 279sub finalize_read {
280 my ( $self, $c ) = @_;
4f5ebacd 281
fbcc39ad 282 undef $self->{_prepared_read};
fc7ec1d9 283}
284
b5ecfcf0 285=head2 $self->finalize_uploads($c)
fc7ec1d9 286
4ab87e27 287Clean up after uploads, deleting temp files.
288
fc7ec1d9 289=cut
290
fbcc39ad 291sub finalize_uploads {
292 my ( $self, $c ) = @_;
99fe1710 293
fbcc39ad 294 if ( keys %{ $c->request->uploads } ) {
295 for my $key ( keys %{ $c->request->uploads } ) {
296 my $upload = $c->request->uploads->{$key};
297 unlink map { $_->tempname }
298 grep { -e $_->tempname }
299 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
c85ff642 300 }
c85ff642 301 }
fc7ec1d9 302}
303
b5ecfcf0 304=head2 $self->prepare_body($c)
fc7ec1d9 305
4ab87e27 306sets up the L<Catalyst::Request> object body using L<HTTP::Body>
307
fc7ec1d9 308=cut
309
fbcc39ad 310sub prepare_body {
311 my ( $self, $c ) = @_;
99fe1710 312
fbcc39ad 313 $self->read_length( $c->request->header('Content-Length') || 0 );
314 my $type = $c->request->header('Content-Type');
99fe1710 315
fbcc39ad 316 unless ( $c->request->{_body} ) {
317 $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
fc7ec1d9 318 }
4f5ebacd 319
fbcc39ad 320 if ( $self->read_length > 0 ) {
4f5ebacd 321 while ( my $buffer = $self->read($c) ) {
322 $c->prepare_body_chunk($buffer);
fbcc39ad 323 }
fdb3773e 324
325 # paranoia against wrong Content-Length header
326 my $remaining = $self->read_length - $self->read_position;
327 if ($remaining > 0) {
328 $self->finalize_read($c);
329 Catalyst::Exception->throw("Wrong Content-Length value: ". $self->read_length);
330 }
fc7ec1d9 331 }
332}
333
b5ecfcf0 334=head2 $self->prepare_body_chunk($c)
4bd82c41 335
4ab87e27 336Add a chunk to the request body.
337
4bd82c41 338=cut
339
340sub prepare_body_chunk {
341 my ( $self, $c, $chunk ) = @_;
4f5ebacd 342
343 $c->request->{_body}->add($chunk);
4bd82c41 344}
345
b5ecfcf0 346=head2 $self->prepare_body_parameters($c)
06e1b616 347
4ab87e27 348Sets up parameters from body.
349
06e1b616 350=cut
351
fbcc39ad 352sub prepare_body_parameters {
353 my ( $self, $c ) = @_;
354 $c->request->body_parameters( $c->request->{_body}->param );
355}
0556eb49 356
b5ecfcf0 357=head2 $self->prepare_connection($c)
0556eb49 358
4ab87e27 359Abstract method implemented in engines.
360
0556eb49 361=cut
362
363sub prepare_connection { }
364
b5ecfcf0 365=head2 $self->prepare_cookies($c)
fc7ec1d9 366
4ab87e27 367Parse cookies from header. Sets a L<CGI::Cookie> object.
368
fc7ec1d9 369=cut
370
6dc87a0f 371sub prepare_cookies {
fbcc39ad 372 my ( $self, $c ) = @_;
6dc87a0f 373
374 if ( my $header = $c->request->header('Cookie') ) {
375 $c->req->cookies( { CGI::Cookie->parse($header) } );
376 }
377}
fc7ec1d9 378
b5ecfcf0 379=head2 $self->prepare_headers($c)
fc7ec1d9 380
381=cut
382
383sub prepare_headers { }
384
b5ecfcf0 385=head2 $self->prepare_parameters($c)
fc7ec1d9 386
4ab87e27 387sets up parameters from query and post parameters.
388
fc7ec1d9 389=cut
390
fbcc39ad 391sub prepare_parameters {
392 my ( $self, $c ) = @_;
fc7ec1d9 393
fbcc39ad 394 # We copy, no references
395 while ( my ( $name, $param ) = each %{ $c->request->query_parameters } ) {
396 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
397 $c->request->parameters->{$name} = $param;
398 }
fc7ec1d9 399
fbcc39ad 400 # Merge query and body parameters
401 while ( my ( $name, $param ) = each %{ $c->request->body_parameters } ) {
402 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
403 if ( my $old_param = $c->request->parameters->{$name} ) {
404 if ( ref $old_param eq 'ARRAY' ) {
405 push @{ $c->request->parameters->{$name} },
406 ref $param eq 'ARRAY' ? @$param : $param;
407 }
408 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
409 }
410 else { $c->request->parameters->{$name} = $param }
411 }
412}
413
b5ecfcf0 414=head2 $self->prepare_path($c)
fc7ec1d9 415
4ab87e27 416abstract method, implemented by engines.
417
fc7ec1d9 418=cut
419
420sub prepare_path { }
421
b5ecfcf0 422=head2 $self->prepare_request($c)
fc7ec1d9 423
b5ecfcf0 424=head2 $self->prepare_query_parameters($c)
fc7ec1d9 425
4ab87e27 426process the query string and extract query parameters.
427
fc7ec1d9 428=cut
429
e0616220 430sub prepare_query_parameters {
431 my ( $self, $c, $query_string ) = @_;
432
433 # replace semi-colons
434 $query_string =~ s/;/&/g;
435
436 my $u = URI->new( '', 'http' );
f4a57de4 437 $u->query($query_string);
438 for my $key ( $u->query_param ) {
439 my @vals = $u->query_param($key);
440 $c->request->query_parameters->{$key} = @vals > 1 ? [@vals] : $vals[0];
e0616220 441 }
442}
fbcc39ad 443
b5ecfcf0 444=head2 $self->prepare_read($c)
fbcc39ad 445
4ab87e27 446prepare to read from the engine.
447
fbcc39ad 448=cut
fc7ec1d9 449
fbcc39ad 450sub prepare_read {
451 my ( $self, $c ) = @_;
4f5ebacd 452
fbcc39ad 453 # Reset the read position
4f5ebacd 454 $self->read_position(0);
fbcc39ad 455}
fc7ec1d9 456
b5ecfcf0 457=head2 $self->prepare_request(@arguments)
fc7ec1d9 458
4ab87e27 459Populate the context object from the request object.
460
fc7ec1d9 461=cut
462
fbcc39ad 463sub prepare_request { }
fc7ec1d9 464
b5ecfcf0 465=head2 $self->prepare_uploads($c)
c9afa5fc 466
fbcc39ad 467=cut
468
469sub prepare_uploads {
470 my ( $self, $c ) = @_;
471 my $uploads = $c->request->{_body}->upload;
472 for my $name ( keys %$uploads ) {
473 my $files = $uploads->{$name};
474 $files = ref $files eq 'ARRAY' ? $files : [$files];
475 my @uploads;
476 for my $upload (@$files) {
477 my $u = Catalyst::Request::Upload->new;
478 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
479 $u->type( $u->headers->content_type );
480 $u->tempname( $upload->{tempname} );
481 $u->size( $upload->{size} );
482 $u->filename( $upload->{filename} );
483 push @uploads, $u;
484 }
485 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 486
c4bed79a 487 # support access to the filename as a normal param
488 my @filenames = map { $_->{filename} } @uploads;
f4a57de4 489 $c->request->parameters->{$name} =
490 @filenames > 1 ? \@filenames : $filenames[0];
fbcc39ad 491 }
492}
493
b5ecfcf0 494=head2 $self->prepare_write($c)
c9afa5fc 495
4ab87e27 496Abstract method. Implemented by the engines.
497
c9afa5fc 498=cut
499
fbcc39ad 500sub prepare_write { }
501
b5ecfcf0 502=head2 $self->read($c, [$maxlength])
fbcc39ad 503
504=cut
505
506sub read {
507 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 508
fbcc39ad 509 unless ( $self->{_prepared_read} ) {
4f5ebacd 510 $self->prepare_read($c);
fbcc39ad 511 $self->{_prepared_read} = 1;
512 }
4f5ebacd 513
fbcc39ad 514 my $remaining = $self->read_length - $self->read_position;
4bd82c41 515 $maxlength ||= $CHUNKSIZE;
4f5ebacd 516
fbcc39ad 517 # Are we done reading?
518 if ( $remaining <= 0 ) {
4f5ebacd 519 $self->finalize_read($c);
fbcc39ad 520 return;
521 }
c9afa5fc 522
fbcc39ad 523 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
524 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
525 if ( defined $rc ) {
526 $self->read_position( $self->read_position + $rc );
527 return $buffer;
528 }
529 else {
4f5ebacd 530 Catalyst::Exception->throw(
531 message => "Unknown error reading input: $!" );
fbcc39ad 532 }
533}
fc7ec1d9 534
b5ecfcf0 535=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 536
fbcc39ad 537Each engine inplements read_chunk as its preferred way of reading a chunk
538of data.
fc7ec1d9 539
fbcc39ad 540=cut
61b1e958 541
fbcc39ad 542sub read_chunk { }
61b1e958 543
b5ecfcf0 544=head2 $self->read_length
ca39d576 545
fbcc39ad 546The length of input data to be read. This is obtained from the Content-Length
547header.
fc7ec1d9 548
b5ecfcf0 549=head2 $self->read_position
fc7ec1d9 550
fbcc39ad 551The amount of input data that has already been read.
63b763c5 552
b5ecfcf0 553=head2 $self->run($c)
63b763c5 554
4ab87e27 555Start the engine. Implemented by the various engine classes.
556
fbcc39ad 557=cut
fc7ec1d9 558
fbcc39ad 559sub run { }
fc7ec1d9 560
b5ecfcf0 561=head2 $self->write($c, $buffer)
fc7ec1d9 562
4ab87e27 563Writes the buffer to the client. Can only be called once for a request.
564
fc7ec1d9 565=cut
566
fbcc39ad 567sub write {
568 my ( $self, $c, $buffer ) = @_;
4f5ebacd 569
fbcc39ad 570 unless ( $self->{_prepared_write} ) {
4f5ebacd 571 $self->prepare_write($c);
fbcc39ad 572 $self->{_prepared_write} = 1;
fc7ec1d9 573 }
4f5ebacd 574
575 print STDOUT $buffer;
fc7ec1d9 576}
577
4ab87e27 578=head2 $self->finalize_output
579
580<obsolete>, see finalize_body
581
fbcc39ad 582=head1 AUTHORS
583
584Sebastian Riedel, <sri@cpan.org>
fc7ec1d9 585
fbcc39ad 586Andy Grundman, <andy@hybridized.org>
fc7ec1d9 587
588=head1 COPYRIGHT
589
590This program is free software, you can redistribute it and/or modify it under
591the same terms as Perl itself.
592
593=cut
594
5951;