1 package Catalyst::Engine;
4 use base 'Class::Accessor::Fast';
12 # input position and length
13 __PACKAGE__->mk_accessors(qw/read_position read_length/);
16 use overload '""' => sub { return ref shift }, fallback => 1;
18 # Amount of data to read from input on each pass
19 our $CHUNKSIZE = 4096;
23 Catalyst::Engine - The Catalyst Engine
34 =head2 $self->finalize_body($c)
36 Finalize body. Prints the response output.
41 my ( $self, $c ) = @_;
42 my $body = $c->response->body;
43 if ( ref $body && ( $body->can('read') || ref($body) eq 'GLOB' ) ) {
44 while ( !eof $body ) {
45 read $body, my ($buffer), $CHUNKSIZE;
46 last unless $self->write( $c, $buffer );
51 $self->write( $c, $body );
55 =head2 $self->finalize_cookies($c)
57 Create CGI::Cookies from $c->res->cookies, and set them as response headers.
61 sub finalize_cookies {
62 my ( $self, $c ) = @_;
65 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
67 my $cookie = CGI::Cookie->new(
69 -value => $cookie->{value},
70 -expires => $cookie->{expires},
71 -domain => $cookie->{domain},
72 -path => $cookie->{path},
73 -secure => $cookie->{secure} || 0
76 push @cookies, $cookie->as_string;
79 for my $cookie (@cookies) {
80 $c->res->headers->push_header( 'Set-Cookie' => $cookie );
84 =head2 $self->finalize_error($c)
86 Output an apropriate error message, called if there's an error in $c
87 after the dispatch has finished. Will output debug messages if Catalyst
88 is in debug mode, or a `please come back later` message otherwise.
93 my ( $self, $c ) = @_;
95 $c->res->content_type('text/html; charset=utf-8');
96 my $name = $c->config->{name} || join(' ', split('::', ref $c));
98 my ( $title, $error, $infos );
102 local $Data::Dumper::Terse = 1;
103 $error = join '', map {
104 '<p><code class="error">'
105 . encode_entities($_)
108 $error ||= 'No output';
109 $error = qq{<pre wrap="">$error</pre>};
110 $title = $name = "$name on Catalyst $Catalyst::VERSION";
111 $name = "<h1>$name</h1>";
113 # Don't show context in the dump
114 delete $c->req->{_context};
115 delete $c->res->{_context};
117 # Don't show body parser in the dump
118 delete $c->req->{_body};
120 # Don't show response header state in dump
121 delete $c->res->{_finalized_headers};
123 my $req = _fixup_debug_info($c->req);
124 my $res = _fixup_debug_info($c->res);
125 my $stash = _fixup_debug_info($c->stash);
129 for my $dump ( $c->dump_these ) {
130 my $name = $dump->[0];
131 my $value = encode_entities( Dumper $dump->[1] );
132 push @infos, sprintf <<"EOF", $name, $value;
133 <h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
135 <pre wrap="">%s</pre>
140 $infos = join "\n", @infos;
147 (en) Please come back later
148 (fr) SVP veuillez revenir plus tard
149 (de) Bitte versuchen sie es spaeter nocheinmal
150 (at) Konnten's bitt'schoen spaeter nochmal reinschauen
151 (no) Vennligst prov igjen senere
152 (dk) Venligst prov igen senere
153 (pl) Prosze sprobowac pozniej
158 $c->res->body( <<"" );
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">
163 <meta http-equiv="Content-Language" content="en" />
164 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
165 <title>$title</title>
166 <script type="text/javascript">
168 function toggleDump (dumpElement) {
169 var e = document.getElementById( dumpElement );
170 if (e.style.display == "none") {
171 e.style.display = "";
174 e.style.display = "none";
179 <style type="text/css">
181 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
182 Tahoma, Arial, helvetica, sans-serif;
184 background-color: #eee;
188 :link, :link:hover, :visited, :visited:hover {
193 background-color: #ccc;
194 border: 1px solid #aaa;
199 background-color: #cce;
200 border: 1px solid #755;
206 background-color: #eee;
207 border: 1px solid #575;
213 background-color: #cce;
214 border: 1px solid #557;
223 div.name h1, div.error p {
231 text-decoration: underline;
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 */
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+ */
250 <div class="error">$error</div>
251 <div class="infos">$infos</div>
252 <div class="name">$name</div>
259 $c->res->{body} .= ( ' ' x 512 );
262 $c->res->status(500);
265 =head2 $self->finalize_headers($c)
267 Abstract method, allows engines to write headers to response
271 sub finalize_headers { }
273 =head2 $self->finalize_read($c)
278 my ( $self, $c ) = @_;
280 undef $self->{_prepared_read};
283 =head2 $self->finalize_uploads($c)
285 Clean up after uploads, deleting temp files.
289 sub finalize_uploads {
290 my ( $self, $c ) = @_;
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);
302 =head2 $self->prepare_body($c)
304 sets up the L<Catalyst::Request> object body using L<HTTP::Body>
309 my ( $self, $c ) = @_;
311 $self->read_length( $c->request->header('Content-Length') || 0 );
312 my $type = $c->request->header('Content-Type');
314 unless ( $c->request->{_body} ) {
315 $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
316 $c->request->{_body}->{tmpdir} = $c->config->{uploadtmp}
317 if exists $c->config->{uploadtmp};
320 if ( $self->read_length > 0 ) {
321 while ( my $buffer = $self->read($c) ) {
322 $c->prepare_body_chunk($buffer);
325 # paranoia against wrong Content-Length header
326 my $remaining = $self->read_length - $self->read_position;
327 if ( $remaining > 0 ) {
328 $self->finalize_read($c);
329 Catalyst::Exception->throw(
330 "Wrong Content-Length value: " . $self->read_length );
335 =head2 $self->prepare_body_chunk($c)
337 Add a chunk to the request body.
341 sub prepare_body_chunk {
342 my ( $self, $c, $chunk ) = @_;
344 $c->request->{_body}->add($chunk);
347 =head2 $self->prepare_body_parameters($c)
349 Sets up parameters from body.
353 sub prepare_body_parameters {
354 my ( $self, $c ) = @_;
355 $c->request->body_parameters( $c->request->{_body}->param );
358 =head2 $self->prepare_connection($c)
360 Abstract method implemented in engines.
364 sub prepare_connection { }
366 =head2 $self->prepare_cookies($c)
368 Parse cookies from header. Sets a L<CGI::Cookie> object.
372 sub prepare_cookies {
373 my ( $self, $c ) = @_;
375 if ( my $header = $c->request->header('Cookie') ) {
376 $c->req->cookies( { CGI::Cookie->parse($header) } );
380 =head2 $self->prepare_headers($c)
384 sub prepare_headers { }
386 =head2 $self->prepare_parameters($c)
388 sets up parameters from query and post parameters.
392 sub prepare_parameters {
393 my ( $self, $c ) = @_;
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;
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;
409 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
411 else { $c->request->parameters->{$name} = $param }
415 =head2 $self->prepare_path($c)
417 abstract method, implemented by engines.
423 =head2 $self->prepare_request($c)
425 =head2 $self->prepare_query_parameters($c)
427 process the query string and extract query parameters.
431 sub prepare_query_parameters {
432 my ( $self, $c, $query_string ) = @_;
434 # replace semi-colons
435 $query_string =~ s/;/&/g;
437 my $u = URI->new( '', 'http' );
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];
445 =head2 $self->prepare_read($c)
447 prepare to read from the engine.
452 my ( $self, $c ) = @_;
454 # Reset the read position
455 $self->read_position(0);
458 =head2 $self->prepare_request(@arguments)
460 Populate the context object from the request object.
464 sub prepare_request { }
466 =head2 $self->prepare_uploads($c)
470 sub 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];
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} );
486 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
488 # support access to the filename as a normal param
489 my @filenames = map { $_->{filename} } @uploads;
490 $c->request->parameters->{$name} =
491 @filenames > 1 ? \@filenames : $filenames[0];
495 =head2 $self->prepare_write($c)
497 Abstract method. Implemented by the engines.
501 sub prepare_write { }
503 =head2 $self->read($c, [$maxlength])
508 my ( $self, $c, $maxlength ) = @_;
510 unless ( $self->{_prepared_read} ) {
511 $self->prepare_read($c);
512 $self->{_prepared_read} = 1;
515 my $remaining = $self->read_length - $self->read_position;
516 $maxlength ||= $CHUNKSIZE;
518 # Are we done reading?
519 if ( $remaining <= 0 ) {
520 $self->finalize_read($c);
524 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
525 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
527 $self->read_position( $self->read_position + $rc );
531 Catalyst::Exception->throw(
532 message => "Unknown error reading input: $!" );
536 =head2 $self->read_chunk($c, $buffer, $length)
538 Each engine inplements read_chunk as its preferred way of reading a chunk
545 =head2 $self->read_length
547 The length of input data to be read. This is obtained from the Content-Length
550 =head2 $self->read_position
552 The amount of input data that has already been read.
554 =head2 $self->run($c)
556 Start the engine. Implemented by the various engine classes.
562 =head2 $self->write($c, $buffer)
564 Writes the buffer to the client. Can only be called once for a request.
569 my ( $self, $c, $buffer ) = @_;
571 unless ( $self->{_prepared_write} ) {
572 $self->prepare_write($c);
573 $self->{_prepared_write} = 1;
576 print STDOUT $buffer;
579 sub _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;
586 =head2 $self->finalize_output
588 <obsolete>, see finalize_body
592 Sebastian Riedel, <sri@cpan.org>
594 Andy Grundman, <andy@hybridized.org>
598 This program is free software, you can redistribute it and/or modify it under
599 the same terms as Perl itself.