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
35 =item $self->finalize_output
37 <obsolete>, see finalize_body
39 =item $self->finalize_body($c)
41 Finalize body. Prints the response output.
46 my ( $self, $c ) = @_;
47 if ( ref $c->response->body && $c->response->body->can('read') ) {
48 while ( !$c->response->body->eof() ) {
49 $c->response->body->read( my $buffer, $CHUNKSIZE );
50 $self->write( $c, $buffer );
52 $c->response->body->close();
55 $self->write( $c, $c->response->body );
59 =item $self->finalize_cookies($c)
63 sub finalize_cookies {
64 my ( $self, $c ) = @_;
67 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
69 my $cookie = CGI::Cookie->new(
71 -value => $cookie->{value},
72 -expires => $cookie->{expires},
73 -domain => $cookie->{domain},
74 -path => $cookie->{path},
75 -secure => $cookie->{secure} || 0
78 push @cookies, $cookie->as_string;
82 $c->res->headers->push_header( 'Set-Cookie' => join ',', @cookies );
86 =item $self->finalize_error($c)
91 my ( $self, $c ) = @_;
93 $c->res->content_type('text/html; charset=utf-8');
94 my $name = $c->config->{name} || 'Catalyst Application';
96 my ( $title, $error, $infos );
100 local $Data::Dumper::Terse = 1;
102 map { '<code class="error">' . encode_entities($_) . '</code>' }
104 $error ||= 'No output';
105 $title = $name = "$name on Catalyst $Catalyst::VERSION";
107 # Don't show context in the dump
108 delete $c->req->{_context};
109 delete $c->res->{_context};
111 # Don't show body parser in the dump
112 delete $c->req->{_body};
114 # Don't show response header state in dump
115 delete $c->res->{_finalized_headers};
117 my $req = encode_entities Dumper $c->req;
118 my $res = encode_entities Dumper $c->res;
119 my $stash = encode_entities Dumper $c->stash;
123 for my $dump ( $c->dump_these ) {
124 my $name = $dump->[0];
125 my $value = encode_entities( Dumper $dump->[1] );
126 push @infos, sprintf <<"EOF", $name, $value;
129 <a href="#" onclick="toggleDump('dump_$i'); return false">%s</a>
139 $infos = join "\n", @infos;
146 (en) Please come back later
147 (de) Bitte versuchen sie es spaeter nocheinmal
148 (nl) Gelieve te komen later terug
149 (no) Vennligst prov igjen senere
150 (fr) Veuillez revenir plus tard
151 (es) Vuelto por favor mas adelante
152 (pt) Voltado por favor mais tarde
153 (it) Ritornato prego piĆ¹ successivamente
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 {
192 background-color: #ccc;
193 border: 1px solid #aaa;
196 -moz-border-radius: 10px;
199 background-color: #977;
200 border: 1px solid #755;
204 -moz-border-radius: 10px;
207 background-color: #797;
208 border: 1px solid #575;
212 -moz-border-radius: 10px;
215 background-color: #779;
216 border: 1px solid #557;
219 -moz-border-radius: 10px;
231 <div class="error">$error</div>
232 <div class="infos">$infos</div>
233 <div class="name">$name</div>
240 =item $self->finalize_headers($c)
244 sub finalize_headers { }
246 =item $self->finalize_read($c)
251 my ( $self, $c ) = @_;
253 undef $self->{_prepared_read};
256 =item $self->finalize_uploads($c)
260 sub finalize_uploads {
261 my ( $self, $c ) = @_;
263 if ( keys %{ $c->request->uploads } ) {
264 for my $key ( keys %{ $c->request->uploads } ) {
265 my $upload = $c->request->uploads->{$key};
266 unlink map { $_->tempname }
267 grep { -e $_->tempname }
268 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
273 =item $self->prepare_body($c)
278 my ( $self, $c ) = @_;
280 $self->read_length( $c->request->header('Content-Length') || 0 );
281 my $type = $c->request->header('Content-Type');
283 unless ( $c->request->{_body} ) {
284 $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
287 if ( $self->read_length > 0 ) {
288 while ( my $buffer = $self->read($c) ) {
289 $c->prepare_body_chunk($buffer);
294 =item $self->prepare_body_chunk($c)
298 sub prepare_body_chunk {
299 my ( $self, $c, $chunk ) = @_;
301 $c->request->{_body}->add($chunk);
304 =item $self->prepare_body_parameters($c)
308 sub prepare_body_parameters {
309 my ( $self, $c ) = @_;
310 $c->request->body_parameters( $c->request->{_body}->param );
313 =item $self->prepare_connection($c)
317 sub prepare_connection { }
319 =item $self->prepare_cookies($c)
323 sub prepare_cookies {
324 my ( $self, $c ) = @_;
326 if ( my $header = $c->request->header('Cookie') ) {
327 $c->req->cookies( { CGI::Cookie->parse($header) } );
331 =item $self->prepare_headers($c)
335 sub prepare_headers { }
337 =item $self->prepare_parameters($c)
341 sub prepare_parameters {
342 my ( $self, $c ) = @_;
344 # We copy, no references
345 while ( my ( $name, $param ) = each %{ $c->request->query_parameters } ) {
346 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
347 $c->request->parameters->{$name} = $param;
350 # Merge query and body parameters
351 while ( my ( $name, $param ) = each %{ $c->request->body_parameters } ) {
352 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
353 if ( my $old_param = $c->request->parameters->{$name} ) {
354 if ( ref $old_param eq 'ARRAY' ) {
355 push @{ $c->request->parameters->{$name} },
356 ref $param eq 'ARRAY' ? @$param : $param;
358 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
360 else { $c->request->parameters->{$name} = $param }
364 =item $self->prepare_path($c)
370 =item $self->prepare_request($c)
372 =item $self->prepare_query_parameters($c)
376 sub prepare_query_parameters {
377 my ( $self, $c, $query_string ) = @_;
379 # replace semi-colons
380 $query_string =~ s/;/&/g;
382 my $u = URI->new( '', 'http' );
383 $u->query($query_string);
384 for my $key ( $u->query_param ) {
385 my @vals = $u->query_param($key);
386 $c->request->query_parameters->{$key} = @vals > 1 ? [@vals] : $vals[0];
390 =item $self->prepare_read($c)
395 my ( $self, $c ) = @_;
397 # Reset the read position
398 $self->read_position(0);
401 =item $self->prepare_request(@arguments)
405 sub prepare_request { }
407 =item $self->prepare_uploads($c)
411 sub prepare_uploads {
412 my ( $self, $c ) = @_;
413 my $uploads = $c->request->{_body}->upload;
414 for my $name ( keys %$uploads ) {
415 my $files = $uploads->{$name};
416 $files = ref $files eq 'ARRAY' ? $files : [$files];
418 for my $upload (@$files) {
419 my $u = Catalyst::Request::Upload->new;
420 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
421 $u->type( $u->headers->content_type );
422 $u->tempname( $upload->{tempname} );
423 $u->size( $upload->{size} );
424 $u->filename( $upload->{filename} );
427 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
429 # support access to the filename as a normal param
430 my @filenames = map { $_->{filename} } @uploads;
431 $c->request->parameters->{$name} =
432 @filenames > 1 ? \@filenames : $filenames[0];
436 =item $self->prepare_write($c)
440 sub prepare_write { }
442 =item $self->read($c, [$maxlength])
447 my ( $self, $c, $maxlength ) = @_;
449 unless ( $self->{_prepared_read} ) {
450 $self->prepare_read($c);
451 $self->{_prepared_read} = 1;
454 my $remaining = $self->read_length - $self->read_position;
455 $maxlength ||= $CHUNKSIZE;
457 # Are we done reading?
458 if ( $remaining <= 0 ) {
459 $self->finalize_read($c);
463 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
464 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
466 $self->read_position( $self->read_position + $rc );
470 Catalyst::Exception->throw(
471 message => "Unknown error reading input: $!" );
475 =item $self->read_chunk($c, $buffer, $length)
477 Each engine inplements read_chunk as its preferred way of reading a chunk
484 =item $self->read_length
486 The length of input data to be read. This is obtained from the Content-Length
489 =item $self->read_position
491 The amount of input data that has already been read.
499 =item $self->write($c, $buffer)
504 my ( $self, $c, $buffer ) = @_;
506 unless ( $self->{_prepared_write} ) {
507 $self->prepare_write($c);
508 $self->{_prepared_write} = 1;
511 print STDOUT $buffer;
518 Sebastian Riedel, <sri@cpan.org>
520 Andy Grundman, <andy@hybridized.org>
524 This program is free software, you can redistribute it and/or modify it under
525 the same terms as Perl itself.