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