1 package Catalyst::Engine;
4 use base 'Class::Accessor::Fast';
5 use CGI::Simple::Cookie;
6 use Data::Dump qw/dump/;
14 # input position and length
15 __PACKAGE__->mk_accessors(qw/read_position read_length/);
18 use overload '""' => sub { return ref shift }, fallback => 1;
20 # Amount of data to read from input on each pass
21 our $CHUNKSIZE = 64 * 1024;
25 Catalyst::Engine - The Catalyst Engine
36 =head2 $self->finalize_body($c)
38 Finalize body. Prints the response output.
43 my ( $self, $c ) = @_;
44 my $body = $c->response->body;
45 no warnings 'uninitialized';
46 if ( Scalar::Util::blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
47 while ( !eof $body ) {
48 read $body, my ($buffer), $CHUNKSIZE;
49 last unless $self->write( $c, $buffer );
54 $self->write( $c, $body );
58 =head2 $self->finalize_cookies($c)
60 Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
65 sub finalize_cookies {
66 my ( $self, $c ) = @_;
70 foreach my $name ( keys %{ $c->response->cookies } ) {
72 my $val = $c->response->cookies->{$name};
75 Scalar::Util::blessed($val)
77 : CGI::Simple::Cookie->new(
79 -value => $val->{value},
80 -expires => $val->{expires},
81 -domain => $val->{domain},
82 -path => $val->{path},
83 -secure => $val->{secure} || 0
87 push @cookies, $cookie->as_string;
90 for my $cookie (@cookies) {
91 $c->res->headers->push_header( 'Set-Cookie' => $cookie );
95 =head2 $self->finalize_error($c)
97 Output an apropriate error message, called if there's an error in $c
98 after the dispatch has finished. Will output debug messages if Catalyst
99 is in debug mode, or a `please come back later` message otherwise.
104 my ( $self, $c ) = @_;
106 $c->res->content_type('text/html; charset=utf-8');
107 my $name = $c->config->{name} || join(' ', split('::', ref $c));
109 my ( $title, $error, $infos );
113 $error = join '', map {
114 '<p><code class="error">'
115 . encode_entities($_)
118 $error ||= 'No output';
119 $error = qq{<pre wrap="">$error</pre>};
120 $title = $name = "$name on Catalyst $Catalyst::VERSION";
121 $name = "<h1>$name</h1>";
123 # Don't show context in the dump
124 delete $c->req->{_context};
125 delete $c->res->{_context};
127 # Don't show body parser in the dump
128 delete $c->req->{_body};
130 # Don't show response header state in dump
131 delete $c->res->{_finalized_headers};
135 for my $dump ( $c->dump_these ) {
136 my $name = $dump->[0];
137 my $value = encode_entities( dump( $dump->[1] ));
138 push @infos, sprintf <<"EOF", $name, $value;
139 <h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
141 <pre wrap="">%s</pre>
146 $infos = join "\n", @infos;
153 (en) Please come back later
154 (fr) SVP veuillez revenir plus tard
155 (de) Bitte versuchen sie es spaeter nocheinmal
156 (at) Konnten's bitt'schoen spaeter nochmal reinschauen
157 (no) Vennligst prov igjen senere
158 (dk) Venligst prov igen senere
159 (pl) Prosze sprobowac pozniej
164 $c->res->body( <<"" );
165 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
166 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
167 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
169 <meta http-equiv="Content-Language" content="en" />
170 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
171 <title>$title</title>
172 <script type="text/javascript">
174 function toggleDump (dumpElement) {
175 var e = document.getElementById( dumpElement );
176 if (e.style.display == "none") {
177 e.style.display = "";
180 e.style.display = "none";
185 <style type="text/css">
187 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
188 Tahoma, Arial, helvetica, sans-serif;
190 background-color: #eee;
194 :link, :link:hover, :visited, :visited:hover {
199 background-color: #ccc;
200 border: 1px solid #aaa;
205 background-color: #cce;
206 border: 1px solid #755;
212 background-color: #eee;
213 border: 1px solid #575;
219 background-color: #cce;
220 border: 1px solid #557;
229 div.name h1, div.error p {
237 text-decoration: underline;
243 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
244 /* Browser specific (not valid) styles to make preformatted text wrap */
246 white-space: pre-wrap; /* css-3 */
247 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
248 white-space: -pre-wrap; /* Opera 4-6 */
249 white-space: -o-pre-wrap; /* Opera 7 */
250 word-wrap: break-word; /* Internet Explorer 5.5+ */
256 <div class="error">$error</div>
257 <div class="infos">$infos</div>
258 <div class="name">$name</div>
265 $c->res->{body} .= ( ' ' x 512 );
268 $c->res->status(500);
271 =head2 $self->finalize_headers($c)
273 Abstract method, allows engines to write headers to response
277 sub finalize_headers { }
279 =head2 $self->finalize_read($c)
284 my ( $self, $c ) = @_;
286 undef $self->{_prepared_read};
289 =head2 $self->finalize_uploads($c)
291 Clean up after uploads, deleting temp files.
295 sub finalize_uploads {
296 my ( $self, $c ) = @_;
298 if ( keys %{ $c->request->uploads } ) {
299 for my $key ( keys %{ $c->request->uploads } ) {
300 my $upload = $c->request->uploads->{$key};
301 unlink map { $_->tempname }
302 grep { -e $_->tempname }
303 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
308 =head2 $self->prepare_body($c)
310 sets up the L<Catalyst::Request> object body using L<HTTP::Body>
315 my ( $self, $c ) = @_;
317 my $length = $c->request->header('Content-Length') || 0;
319 $self->read_length( $length );
322 unless ( $c->request->{_body} ) {
323 my $type = $c->request->header('Content-Type');
324 $c->request->{_body} = HTTP::Body->new( $type, $length );
325 $c->request->{_body}->{tmpdir} = $c->config->{uploadtmp}
326 if exists $c->config->{uploadtmp};
329 while ( my $buffer = $self->read($c) ) {
330 $c->prepare_body_chunk($buffer);
333 # paranoia against wrong Content-Length header
334 my $remaining = $length - $self->read_position;
335 if ( $remaining > 0 ) {
336 $self->finalize_read($c);
337 Catalyst::Exception->throw(
338 "Wrong Content-Length value: $length" );
342 # Defined but will cause all body code to be skipped
343 $c->request->{_body} = 0;
347 =head2 $self->prepare_body_chunk($c)
349 Add a chunk to the request body.
353 sub prepare_body_chunk {
354 my ( $self, $c, $chunk ) = @_;
356 $c->request->{_body}->add($chunk);
359 =head2 $self->prepare_body_parameters($c)
361 Sets up parameters from body.
365 sub prepare_body_parameters {
366 my ( $self, $c ) = @_;
368 return unless $c->request->{_body};
370 $c->request->body_parameters( $c->request->{_body}->param );
373 =head2 $self->prepare_connection($c)
375 Abstract method implemented in engines.
379 sub prepare_connection { }
381 =head2 $self->prepare_cookies($c)
383 Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
387 sub prepare_cookies {
388 my ( $self, $c ) = @_;
390 if ( my $header = $c->request->header('Cookie') ) {
391 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
395 =head2 $self->prepare_headers($c)
399 sub prepare_headers { }
401 =head2 $self->prepare_parameters($c)
403 sets up parameters from query and post parameters.
407 sub prepare_parameters {
408 my ( $self, $c ) = @_;
410 # We copy, no references
411 foreach my $name ( keys %{ $c->request->query_parameters } ) {
412 my $param = $c->request->query_parameters->{$name};
413 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
414 $c->request->parameters->{$name} = $param;
417 # Merge query and body parameters
418 foreach my $name ( keys %{ $c->request->body_parameters } ) {
419 my $param = $c->request->body_parameters->{$name};
420 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
421 if ( my $old_param = $c->request->parameters->{$name} ) {
422 if ( ref $old_param eq 'ARRAY' ) {
423 push @{ $c->request->parameters->{$name} },
424 ref $param eq 'ARRAY' ? @$param : $param;
426 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
428 else { $c->request->parameters->{$name} = $param }
432 =head2 $self->prepare_path($c)
434 abstract method, implemented by engines.
440 =head2 $self->prepare_request($c)
442 =head2 $self->prepare_query_parameters($c)
444 process the query string and extract query parameters.
448 sub prepare_query_parameters {
449 my ( $self, $c, $query_string ) = @_;
451 # Make sure query has params
452 if ( index( $query_string, '=' ) < 0 ) {
458 # replace semi-colons
459 $query_string =~ s/;/&/g;
461 my @params = split /&/, $query_string;
463 for my $item ( @params ) {
466 = map { $self->unescape_uri($_) }
469 $param = $self->unescape_uri($item) unless defined $param;
471 if ( exists $query{$param} ) {
472 if ( ref $query{$param} ) {
473 push @{ $query{$param} }, $value;
476 $query{$param} = [ $query{$param}, $value ];
480 $query{$param} = $value;
484 $c->request->query_parameters( \%query );
487 =head2 $self->prepare_read($c)
489 prepare to read from the engine.
494 my ( $self, $c ) = @_;
496 # Reset the read position
497 $self->read_position(0);
500 =head2 $self->prepare_request(@arguments)
502 Populate the context object from the request object.
506 sub prepare_request { }
508 =head2 $self->prepare_uploads($c)
512 sub prepare_uploads {
513 my ( $self, $c ) = @_;
515 return unless $c->request->{_body};
517 my $uploads = $c->request->{_body}->upload;
518 for my $name ( keys %$uploads ) {
519 my $files = $uploads->{$name};
520 $files = ref $files eq 'ARRAY' ? $files : [$files];
522 for my $upload (@$files) {
523 my $u = Catalyst::Request::Upload->new;
524 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
525 $u->type( $u->headers->content_type );
526 $u->tempname( $upload->{tempname} );
527 $u->size( $upload->{size} );
528 $u->filename( $upload->{filename} );
531 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
533 # support access to the filename as a normal param
534 my @filenames = map { $_->{filename} } @uploads;
535 # append, if there's already params with this name
536 if (exists $c->request->parameters->{$name}) {
537 if (ref $c->request->parameters->{$name} eq 'ARRAY') {
538 push @{ $c->request->parameters->{$name} }, @filenames;
541 $c->request->parameters->{$name} =
542 [ $c->request->parameters->{$name}, @filenames ];
546 $c->request->parameters->{$name} =
547 @filenames > 1 ? \@filenames : $filenames[0];
552 =head2 $self->prepare_write($c)
554 Abstract method. Implemented by the engines.
558 sub prepare_write { }
560 =head2 $self->read($c, [$maxlength])
565 my ( $self, $c, $maxlength ) = @_;
567 unless ( $self->{_prepared_read} ) {
568 $self->prepare_read($c);
569 $self->{_prepared_read} = 1;
572 my $remaining = $self->read_length - $self->read_position;
573 $maxlength ||= $CHUNKSIZE;
575 # Are we done reading?
576 if ( $remaining <= 0 ) {
577 $self->finalize_read($c);
581 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
582 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
584 $self->read_position( $self->read_position + $rc );
588 Catalyst::Exception->throw(
589 message => "Unknown error reading input: $!" );
593 =head2 $self->read_chunk($c, $buffer, $length)
595 Each engine inplements read_chunk as its preferred way of reading a chunk
602 =head2 $self->read_length
604 The length of input data to be read. This is obtained from the Content-Length
607 =head2 $self->read_position
609 The amount of input data that has already been read.
611 =head2 $self->run($c)
613 Start the engine. Implemented by the various engine classes.
619 =head2 $self->write($c, $buffer)
621 Writes the buffer to the client. Can only be called once for a request.
626 my ( $self, $c, $buffer ) = @_;
628 unless ( $self->{_prepared_write} ) {
629 $self->prepare_write($c);
630 $self->{_prepared_write} = 1;
633 print STDOUT $buffer;
636 =head2 $self->unescape_uri($uri)
638 Unescapes a given URI using the most efficient method available. Engines such
639 as Apache may implement this using Apache's C-based modules, for example.
646 my $e = URI::Escape::uri_unescape(@_);
652 =head2 $self->finalize_output
654 <obsolete>, see finalize_body
658 Sebastian Riedel, <sri@cpan.org>
660 Andy Grundman, <andy@hybridized.org>
664 This program is free software, you can redistribute it and/or modify it under
665 the same terms as Perl itself.