remove Test::WWW::Mechanize::Catalyst from Makefile.PL (circular dep)
[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;
34d28dfd 43 if ( ref $body && ( $body->can('read') || ref($body) eq 'GLOB' ) ) {
7257e9db 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');
34d28dfd 96 my $name = $c->config->{name} || join(' ', split('::', ref $c));
969647fd 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
34d28dfd 123 my $req = _fixup_debug_info($c->req);
124 my $res = _fixup_debug_info($c->res);
125 my $stash = _fixup_debug_info($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;
34d28dfd 183 color: #333;
969647fd 184 background-color: #eee;
185 margin: 0px;
186 padding: 0px;
187 }
c6ef5e69 188 :link, :link:hover, :visited, :visited:hover {
34d28dfd 189 color: #000;
c6ef5e69 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;
969647fd 197 }
198 div.error {
34d28dfd 199 background-color: #cce;
969647fd 200 border: 1px solid #755;
201 padding: 8px;
202 margin: 4px;
203 margin-bottom: 10px;
969647fd 204 }
205 div.infos {
34d28dfd 206 background-color: #eee;
969647fd 207 border: 1px solid #575;
208 padding: 8px;
209 margin: 4px;
210 margin-bottom: 10px;
969647fd 211 }
212 div.name {
34d28dfd 213 background-color: #cce;
969647fd 214 border: 1px solid #557;
215 padding: 8px;
216 margin: 4px;
969647fd 217 }
7f8e0078 218 code.error {
219 display: block;
220 margin: 1em 0;
221 overflow: auto;
7f8e0078 222 }
9619f23c 223 div.name h1, div.error p {
224 margin: 0;
225 }
226 h2 {
227 margin-top: 0;
228 margin-bottom: 10px;
229 font-size: medium;
230 font-weight: bold;
231 text-decoration: underline;
232 }
233 h1 {
234 font-size: medium;
235 font-weight: normal;
236 }
2666dd3b 237 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
238 /* Browser specific (not valid) styles to make preformatted text wrap */
239 pre {
240 white-space: pre-wrap; /* css-3 */
241 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
242 white-space: -pre-wrap; /* Opera 4-6 */
243 white-space: -o-pre-wrap; /* Opera 7 */
244 word-wrap: break-word; /* Internet Explorer 5.5+ */
245 }
969647fd 246 </style>
247</head>
248<body>
249 <div class="box">
250 <div class="error">$error</div>
251 <div class="infos">$infos</div>
252 <div class="name">$name</div>
253 </div>
254</body>
255</html>
256
d82cc9ae 257
258 # Trick IE
259 $c->res->{body} .= ( ' ' x 512 );
260
261 # Return 500
33117422 262 $c->res->status(500);
969647fd 263}
264
b5ecfcf0 265=head2 $self->finalize_headers($c)
fc7ec1d9 266
4ab87e27 267Abstract method, allows engines to write headers to response
268
fc7ec1d9 269=cut
270
271sub finalize_headers { }
272
b5ecfcf0 273=head2 $self->finalize_read($c)
fc7ec1d9 274
275=cut
276
fbcc39ad 277sub finalize_read {
278 my ( $self, $c ) = @_;
4f5ebacd 279
fbcc39ad 280 undef $self->{_prepared_read};
fc7ec1d9 281}
282
b5ecfcf0 283=head2 $self->finalize_uploads($c)
fc7ec1d9 284
4ab87e27 285Clean up after uploads, deleting temp files.
286
fc7ec1d9 287=cut
288
fbcc39ad 289sub finalize_uploads {
290 my ( $self, $c ) = @_;
99fe1710 291
fbcc39ad 292 if ( keys %{ $c->request->uploads } ) {
293 for my $key ( keys %{ $c->request->uploads } ) {
294 my $upload = $c->request->uploads->{$key};
295 unlink map { $_->tempname }
296 grep { -e $_->tempname }
297 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
c85ff642 298 }
c85ff642 299 }
fc7ec1d9 300}
301
b5ecfcf0 302=head2 $self->prepare_body($c)
fc7ec1d9 303
4ab87e27 304sets up the L<Catalyst::Request> object body using L<HTTP::Body>
305
fc7ec1d9 306=cut
307
fbcc39ad 308sub prepare_body {
309 my ( $self, $c ) = @_;
99fe1710 310
fbcc39ad 311 $self->read_length( $c->request->header('Content-Length') || 0 );
312 my $type = $c->request->header('Content-Type');
99fe1710 313
fbcc39ad 314 unless ( $c->request->{_body} ) {
315 $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
34d28dfd 316 $c->request->{_body}->{tmpdir} = $c->config->{uploadtmp}
317 if exists $c->config->{uploadtmp};
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;
34d28dfd 327 if ( $remaining > 0 ) {
fdb3773e 328 $self->finalize_read($c);
34d28dfd 329 Catalyst::Exception->throw(
330 "Wrong Content-Length value: " . $self->read_length );
fdb3773e 331 }
fc7ec1d9 332 }
333}
334
b5ecfcf0 335=head2 $self->prepare_body_chunk($c)
4bd82c41 336
4ab87e27 337Add a chunk to the request body.
338
4bd82c41 339=cut
340
341sub prepare_body_chunk {
342 my ( $self, $c, $chunk ) = @_;
4f5ebacd 343
344 $c->request->{_body}->add($chunk);
4bd82c41 345}
346
b5ecfcf0 347=head2 $self->prepare_body_parameters($c)
06e1b616 348
4ab87e27 349Sets up parameters from body.
350
06e1b616 351=cut
352
fbcc39ad 353sub prepare_body_parameters {
354 my ( $self, $c ) = @_;
355 $c->request->body_parameters( $c->request->{_body}->param );
356}
0556eb49 357
b5ecfcf0 358=head2 $self->prepare_connection($c)
0556eb49 359
4ab87e27 360Abstract method implemented in engines.
361
0556eb49 362=cut
363
364sub prepare_connection { }
365
b5ecfcf0 366=head2 $self->prepare_cookies($c)
fc7ec1d9 367
4ab87e27 368Parse cookies from header. Sets a L<CGI::Cookie> object.
369
fc7ec1d9 370=cut
371
6dc87a0f 372sub prepare_cookies {
fbcc39ad 373 my ( $self, $c ) = @_;
6dc87a0f 374
375 if ( my $header = $c->request->header('Cookie') ) {
376 $c->req->cookies( { CGI::Cookie->parse($header) } );
377 }
378}
fc7ec1d9 379
b5ecfcf0 380=head2 $self->prepare_headers($c)
fc7ec1d9 381
382=cut
383
384sub prepare_headers { }
385
b5ecfcf0 386=head2 $self->prepare_parameters($c)
fc7ec1d9 387
4ab87e27 388sets up parameters from query and post parameters.
389
fc7ec1d9 390=cut
391
fbcc39ad 392sub prepare_parameters {
393 my ( $self, $c ) = @_;
fc7ec1d9 394
fbcc39ad 395 # We copy, no references
396 while ( my ( $name, $param ) = each %{ $c->request->query_parameters } ) {
397 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
398 $c->request->parameters->{$name} = $param;
399 }
fc7ec1d9 400
fbcc39ad 401 # Merge query and body parameters
402 while ( my ( $name, $param ) = each %{ $c->request->body_parameters } ) {
403 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
404 if ( my $old_param = $c->request->parameters->{$name} ) {
405 if ( ref $old_param eq 'ARRAY' ) {
406 push @{ $c->request->parameters->{$name} },
407 ref $param eq 'ARRAY' ? @$param : $param;
408 }
409 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
410 }
411 else { $c->request->parameters->{$name} = $param }
412 }
413}
414
b5ecfcf0 415=head2 $self->prepare_path($c)
fc7ec1d9 416
4ab87e27 417abstract method, implemented by engines.
418
fc7ec1d9 419=cut
420
421sub prepare_path { }
422
b5ecfcf0 423=head2 $self->prepare_request($c)
fc7ec1d9 424
b5ecfcf0 425=head2 $self->prepare_query_parameters($c)
fc7ec1d9 426
4ab87e27 427process the query string and extract query parameters.
428
fc7ec1d9 429=cut
430
e0616220 431sub prepare_query_parameters {
432 my ( $self, $c, $query_string ) = @_;
433
434 # replace semi-colons
435 $query_string =~ s/;/&/g;
436
437 my $u = URI->new( '', 'http' );
f4a57de4 438 $u->query($query_string);
439 for my $key ( $u->query_param ) {
440 my @vals = $u->query_param($key);
441 $c->request->query_parameters->{$key} = @vals > 1 ? [@vals] : $vals[0];
e0616220 442 }
443}
fbcc39ad 444
b5ecfcf0 445=head2 $self->prepare_read($c)
fbcc39ad 446
4ab87e27 447prepare to read from the engine.
448
fbcc39ad 449=cut
fc7ec1d9 450
fbcc39ad 451sub prepare_read {
452 my ( $self, $c ) = @_;
4f5ebacd 453
fbcc39ad 454 # Reset the read position
4f5ebacd 455 $self->read_position(0);
fbcc39ad 456}
fc7ec1d9 457
b5ecfcf0 458=head2 $self->prepare_request(@arguments)
fc7ec1d9 459
4ab87e27 460Populate the context object from the request object.
461
fc7ec1d9 462=cut
463
fbcc39ad 464sub prepare_request { }
fc7ec1d9 465
b5ecfcf0 466=head2 $self->prepare_uploads($c)
c9afa5fc 467
fbcc39ad 468=cut
469
470sub prepare_uploads {
471 my ( $self, $c ) = @_;
472 my $uploads = $c->request->{_body}->upload;
473 for my $name ( keys %$uploads ) {
474 my $files = $uploads->{$name};
475 $files = ref $files eq 'ARRAY' ? $files : [$files];
476 my @uploads;
477 for my $upload (@$files) {
478 my $u = Catalyst::Request::Upload->new;
479 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
480 $u->type( $u->headers->content_type );
481 $u->tempname( $upload->{tempname} );
482 $u->size( $upload->{size} );
483 $u->filename( $upload->{filename} );
484 push @uploads, $u;
485 }
486 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
f4a57de4 487
c4bed79a 488 # support access to the filename as a normal param
489 my @filenames = map { $_->{filename} } @uploads;
f4a57de4 490 $c->request->parameters->{$name} =
491 @filenames > 1 ? \@filenames : $filenames[0];
fbcc39ad 492 }
493}
494
b5ecfcf0 495=head2 $self->prepare_write($c)
c9afa5fc 496
4ab87e27 497Abstract method. Implemented by the engines.
498
c9afa5fc 499=cut
500
fbcc39ad 501sub prepare_write { }
502
b5ecfcf0 503=head2 $self->read($c, [$maxlength])
fbcc39ad 504
505=cut
506
507sub read {
508 my ( $self, $c, $maxlength ) = @_;
4f5ebacd 509
fbcc39ad 510 unless ( $self->{_prepared_read} ) {
4f5ebacd 511 $self->prepare_read($c);
fbcc39ad 512 $self->{_prepared_read} = 1;
513 }
4f5ebacd 514
fbcc39ad 515 my $remaining = $self->read_length - $self->read_position;
4bd82c41 516 $maxlength ||= $CHUNKSIZE;
4f5ebacd 517
fbcc39ad 518 # Are we done reading?
519 if ( $remaining <= 0 ) {
4f5ebacd 520 $self->finalize_read($c);
fbcc39ad 521 return;
522 }
c9afa5fc 523
fbcc39ad 524 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
525 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
526 if ( defined $rc ) {
527 $self->read_position( $self->read_position + $rc );
528 return $buffer;
529 }
530 else {
4f5ebacd 531 Catalyst::Exception->throw(
532 message => "Unknown error reading input: $!" );
fbcc39ad 533 }
534}
fc7ec1d9 535
b5ecfcf0 536=head2 $self->read_chunk($c, $buffer, $length)
23f9d934 537
fbcc39ad 538Each engine inplements read_chunk as its preferred way of reading a chunk
539of data.
fc7ec1d9 540
fbcc39ad 541=cut
61b1e958 542
fbcc39ad 543sub read_chunk { }
61b1e958 544
b5ecfcf0 545=head2 $self->read_length
ca39d576 546
fbcc39ad 547The length of input data to be read. This is obtained from the Content-Length
548header.
fc7ec1d9 549
b5ecfcf0 550=head2 $self->read_position
fc7ec1d9 551
fbcc39ad 552The amount of input data that has already been read.
63b763c5 553
b5ecfcf0 554=head2 $self->run($c)
63b763c5 555
4ab87e27 556Start the engine. Implemented by the various engine classes.
557
fbcc39ad 558=cut
fc7ec1d9 559
fbcc39ad 560sub run { }
fc7ec1d9 561
b5ecfcf0 562=head2 $self->write($c, $buffer)
fc7ec1d9 563
4ab87e27 564Writes the buffer to the client. Can only be called once for a request.
565
fc7ec1d9 566=cut
567
fbcc39ad 568sub write {
569 my ( $self, $c, $buffer ) = @_;
4f5ebacd 570
fbcc39ad 571 unless ( $self->{_prepared_write} ) {
4f5ebacd 572 $self->prepare_write($c);
fbcc39ad 573 $self->{_prepared_write} = 1;
fc7ec1d9 574 }
4f5ebacd 575
576 print STDOUT $buffer;
fc7ec1d9 577}
578
34d28dfd 579sub _fixup_debug_info {
580 my $info = encode_entities Dumper shift;
581 my @info = split "\n", $info;
582 pop @info; shift @info;
583 return join "\n",@info;
584}
585
4ab87e27 586=head2 $self->finalize_output
587
588<obsolete>, see finalize_body
589
fbcc39ad 590=head1 AUTHORS
591
592Sebastian Riedel, <sri@cpan.org>
fc7ec1d9 593
fbcc39ad 594Andy Grundman, <andy@hybridized.org>
fc7ec1d9 595
596=head1 COPYRIGHT
597
598This program is free software, you can redistribute it and/or modify it under
599the same terms as Perl itself.
600
601=cut
602
6031;