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