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;
23 # See if we can use libapreq2 for URI unescaping
24 use constant HAS_APR => eval {
30 Catalyst::Engine - The Catalyst Engine
41 =head2 $self->finalize_body($c)
43 Finalize body. Prints the response output.
48 my ( $self, $c ) = @_;
49 my $body = $c->response->body;
50 no warnings 'uninitialized';
51 if ( Scalar::Util::blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
52 while ( !eof $body ) {
53 read $body, my ($buffer), $CHUNKSIZE;
54 last unless $self->write( $c, $buffer );
59 $self->write( $c, $body );
63 =head2 $self->finalize_cookies($c)
65 Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
70 sub finalize_cookies {
71 my ( $self, $c ) = @_;
75 foreach my $name ( keys %{ $c->response->cookies } ) {
77 my $val = $c->response->cookies->{$name};
80 Scalar::Util::blessed($val)
82 : CGI::Simple::Cookie->new(
84 -value => $val->{value},
85 -expires => $val->{expires},
86 -domain => $val->{domain},
87 -path => $val->{path},
88 -secure => $val->{secure} || 0
92 push @cookies, $cookie->as_string;
95 for my $cookie (@cookies) {
96 $c->res->headers->push_header( 'Set-Cookie' => $cookie );
100 =head2 $self->finalize_error($c)
102 Output an apropriate error message, called if there's an error in $c
103 after the dispatch has finished. Will output debug messages if Catalyst
104 is in debug mode, or a `please come back later` message otherwise.
109 my ( $self, $c ) = @_;
111 $c->res->content_type('text/html; charset=utf-8');
112 my $name = $c->config->{name} || join(' ', split('::', ref $c));
114 my ( $title, $error, $infos );
118 $error = join '', map {
119 '<p><code class="error">'
120 . encode_entities($_)
123 $error ||= 'No output';
124 $error = qq{<pre wrap="">$error</pre>};
125 $title = $name = "$name on Catalyst $Catalyst::VERSION";
126 $name = "<h1>$name</h1>";
128 # Don't show context in the dump
129 delete $c->req->{_context};
130 delete $c->res->{_context};
132 # Don't show body parser in the dump
133 delete $c->req->{_body};
135 # Don't show response header state in dump
136 delete $c->res->{_finalized_headers};
140 for my $dump ( $c->dump_these ) {
141 my $name = $dump->[0];
142 my $value = encode_entities( dump( $dump->[1] ));
143 push @infos, sprintf <<"EOF", $name, $value;
144 <h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
146 <pre wrap="">%s</pre>
151 $infos = join "\n", @infos;
158 (en) Please come back later
159 (fr) SVP veuillez revenir plus tard
160 (de) Bitte versuchen sie es spaeter nocheinmal
161 (at) Konnten's bitt'schoen spaeter nochmal reinschauen
162 (no) Vennligst prov igjen senere
163 (dk) Venligst prov igen senere
164 (pl) Prosze sprobowac pozniej
169 $c->res->body( <<"" );
170 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
171 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
172 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
174 <meta http-equiv="Content-Language" content="en" />
175 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
176 <title>$title</title>
177 <script type="text/javascript">
179 function toggleDump (dumpElement) {
180 var e = document.getElementById( dumpElement );
181 if (e.style.display == "none") {
182 e.style.display = "";
185 e.style.display = "none";
190 <style type="text/css">
192 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
193 Tahoma, Arial, helvetica, sans-serif;
195 background-color: #eee;
199 :link, :link:hover, :visited, :visited:hover {
204 background-color: #ccc;
205 border: 1px solid #aaa;
210 background-color: #cce;
211 border: 1px solid #755;
217 background-color: #eee;
218 border: 1px solid #575;
224 background-color: #cce;
225 border: 1px solid #557;
234 div.name h1, div.error p {
242 text-decoration: underline;
248 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
249 /* Browser specific (not valid) styles to make preformatted text wrap */
251 white-space: pre-wrap; /* css-3 */
252 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
253 white-space: -pre-wrap; /* Opera 4-6 */
254 white-space: -o-pre-wrap; /* Opera 7 */
255 word-wrap: break-word; /* Internet Explorer 5.5+ */
261 <div class="error">$error</div>
262 <div class="infos">$infos</div>
263 <div class="name">$name</div>
270 $c->res->{body} .= ( ' ' x 512 );
273 $c->res->status(500);
276 =head2 $self->finalize_headers($c)
278 Abstract method, allows engines to write headers to response
282 sub finalize_headers { }
284 =head2 $self->finalize_read($c)
289 my ( $self, $c ) = @_;
291 undef $self->{_prepared_read};
294 =head2 $self->finalize_uploads($c)
296 Clean up after uploads, deleting temp files.
300 sub finalize_uploads {
301 my ( $self, $c ) = @_;
303 if ( keys %{ $c->request->uploads } ) {
304 for my $key ( keys %{ $c->request->uploads } ) {
305 my $upload = $c->request->uploads->{$key};
306 unlink map { $_->tempname }
307 grep { -e $_->tempname }
308 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
313 =head2 $self->prepare_body($c)
315 sets up the L<Catalyst::Request> object body using L<HTTP::Body>
320 my ( $self, $c ) = @_;
322 my $length = $c->request->header('Content-Length') || 0;
324 $self->read_length( $length );
327 unless ( $c->request->{_body} ) {
328 my $type = $c->request->header('Content-Type');
329 $c->request->{_body} = HTTP::Body->new( $type, $length );
330 $c->request->{_body}->{tmpdir} = $c->config->{uploadtmp}
331 if exists $c->config->{uploadtmp};
334 while ( my $buffer = $self->read($c) ) {
335 $c->prepare_body_chunk($buffer);
338 # paranoia against wrong Content-Length header
339 my $remaining = $length - $self->read_position;
340 if ( $remaining > 0 ) {
341 $self->finalize_read($c);
342 Catalyst::Exception->throw(
343 "Wrong Content-Length value: $length" );
347 # Defined but will cause all body code to be skipped
348 $c->request->{_body} = 0;
352 =head2 $self->prepare_body_chunk($c)
354 Add a chunk to the request body.
358 sub prepare_body_chunk {
359 my ( $self, $c, $chunk ) = @_;
361 $c->request->{_body}->add($chunk);
364 =head2 $self->prepare_body_parameters($c)
366 Sets up parameters from body.
370 sub prepare_body_parameters {
371 my ( $self, $c ) = @_;
373 return unless $c->request->{_body};
375 $c->request->body_parameters( $c->request->{_body}->param );
378 =head2 $self->prepare_connection($c)
380 Abstract method implemented in engines.
384 sub prepare_connection { }
386 =head2 $self->prepare_cookies($c)
388 Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
392 sub prepare_cookies {
393 my ( $self, $c ) = @_;
395 if ( my $header = $c->request->header('Cookie') ) {
396 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
400 =head2 $self->prepare_headers($c)
404 sub prepare_headers { }
406 =head2 $self->prepare_parameters($c)
408 sets up parameters from query and post parameters.
412 sub prepare_parameters {
413 my ( $self, $c ) = @_;
415 # We copy, no references
416 foreach my $name ( keys %{ $c->request->query_parameters } ) {
417 my $param = $c->request->query_parameters->{$name};
418 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
419 $c->request->parameters->{$name} = $param;
422 # Merge query and body parameters
423 foreach my $name ( keys %{ $c->request->body_parameters } ) {
424 my $param = $c->request->body_parameters->{$name};
425 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
426 if ( my $old_param = $c->request->parameters->{$name} ) {
427 if ( ref $old_param eq 'ARRAY' ) {
428 push @{ $c->request->parameters->{$name} },
429 ref $param eq 'ARRAY' ? @$param : $param;
431 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
433 else { $c->request->parameters->{$name} = $param }
437 =head2 $self->prepare_path($c)
439 abstract method, implemented by engines.
445 =head2 $self->prepare_request($c)
447 =head2 $self->prepare_query_parameters($c)
449 process the query string and extract query parameters.
453 sub prepare_query_parameters {
454 my ( $self, $c, $query_string ) = @_;
456 # Check for keywords (no = signs)
457 if ( index( $query_string, '=' ) < 0 ) {
458 $c->request->keywords( $self->unescape_uri($query_string) );
464 # replace semi-colons
465 $query_string =~ s/;/&/g;
467 my @params = split /&/, $query_string;
469 for my $item ( @params ) {
472 = map { $self->unescape_uri($_) }
475 $param = $self->unescape_uri($item) unless defined $param;
477 if ( exists $query{$param} ) {
478 if ( ref $query{$param} ) {
479 push @{ $query{$param} }, $value;
482 $query{$param} = [ $query{$param}, $value ];
486 $query{$param} = $value;
490 $c->request->query_parameters( \%query );
493 =head2 $self->prepare_read($c)
495 prepare to read from the engine.
500 my ( $self, $c ) = @_;
502 # Reset the read position
503 $self->read_position(0);
506 =head2 $self->prepare_request(@arguments)
508 Populate the context object from the request object.
512 sub prepare_request { }
514 =head2 $self->prepare_uploads($c)
518 sub prepare_uploads {
519 my ( $self, $c ) = @_;
521 return unless $c->request->{_body};
523 my $uploads = $c->request->{_body}->upload;
524 for my $name ( keys %$uploads ) {
525 my $files = $uploads->{$name};
526 $files = ref $files eq 'ARRAY' ? $files : [$files];
528 for my $upload (@$files) {
529 my $u = Catalyst::Request::Upload->new;
530 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
531 $u->type( $u->headers->content_type );
532 $u->tempname( $upload->{tempname} );
533 $u->size( $upload->{size} );
534 $u->filename( $upload->{filename} );
537 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
539 # support access to the filename as a normal param
540 my @filenames = map { $_->{filename} } @uploads;
541 # append, if there's already params with this name
542 if (exists $c->request->parameters->{$name}) {
543 if (ref $c->request->parameters->{$name} eq 'ARRAY') {
544 push @{ $c->request->parameters->{$name} }, @filenames;
547 $c->request->parameters->{$name} =
548 [ $c->request->parameters->{$name}, @filenames ];
552 $c->request->parameters->{$name} =
553 @filenames > 1 ? \@filenames : $filenames[0];
558 =head2 $self->prepare_write($c)
560 Abstract method. Implemented by the engines.
564 sub prepare_write { }
566 =head2 $self->read($c, [$maxlength])
571 my ( $self, $c, $maxlength ) = @_;
573 unless ( $self->{_prepared_read} ) {
574 $self->prepare_read($c);
575 $self->{_prepared_read} = 1;
578 my $remaining = $self->read_length - $self->read_position;
579 $maxlength ||= $CHUNKSIZE;
581 # Are we done reading?
582 if ( $remaining <= 0 ) {
583 $self->finalize_read($c);
587 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
588 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
590 $self->read_position( $self->read_position + $rc );
594 Catalyst::Exception->throw(
595 message => "Unknown error reading input: $!" );
599 =head2 $self->read_chunk($c, $buffer, $length)
601 Each engine inplements read_chunk as its preferred way of reading a chunk
608 =head2 $self->read_length
610 The length of input data to be read. This is obtained from the Content-Length
613 =head2 $self->read_position
615 The amount of input data that has already been read.
617 =head2 $self->run($c)
619 Start the engine. Implemented by the various engine classes.
625 =head2 $self->write($c, $buffer)
627 Writes the buffer to the client. Can only be called once for a request.
632 my ( $self, $c, $buffer ) = @_;
634 unless ( $self->{_prepared_write} ) {
635 $self->prepare_write($c);
636 $self->{_prepared_write} = 1;
639 print STDOUT $buffer;
642 =head2 $self->unescape_uri($uri)
644 Unescapes a given URI using the most efficient method available. Engines
645 can subclass to provide faster implementations.
653 # This function is ~12x faster than URI::Escape
654 return APR::Request::decode(@_);
657 my $e = URI::Escape::uri_unescape(@_);
663 =head2 $self->finalize_output
665 <obsolete>, see finalize_body
669 Sebastian Riedel, <sri@cpan.org>
671 Andy Grundman, <andy@hybridized.org>
675 This program is free software, you can redistribute it and/or modify it under
676 the same terms as Perl itself.