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->headers->content_type('text/html');
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;
124 for my $dump ( $c->dump_these ) {
126 my $name = $dump->[0];
127 my $value = encode_entities( Dumper $dump->[1] );
128 push @infos, sprintf <<"EOF", $name, $value;
131 <a href="#" onclick="toggleDump(dump_$i); return false">%s</a>
141 $infos = join "\n", @infos;
148 (en) Please come back later
149 (de) Bitte versuchen sie es spaeter nocheinmal
150 (nl) Gelieve te komen later terug
151 (no) Vennligst prov igjen senere
152 (fr) Veuillez revenir plus tard
153 (es) Vuelto por favor mas adelante
154 (pt) Voltado por favor mais tarde
155 (it) Ritornato prego piĆ¹ successivamente
160 $c->res->body( <<"" );
163 <title>$title</title>
164 <script language="JavaScript">
166 function toggleDump (dumpElement) {
167 if (dumpElement.style.display == "none") {
168 dumpElement.style.display = "";
171 dumpElement.style.display = "none";
176 <style type="text/css">
178 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
179 Tahoma, Arial, helvetica, sans-serif;
181 background-color: #eee;
185 :link, :link:hover, :visited, :visited:hover {
189 background-color: #ccc;
190 border: 1px solid #aaa;
193 -moz-border-radius: 10px;
196 background-color: #977;
197 border: 1px solid #755;
201 -moz-border-radius: 10px;
204 background-color: #797;
205 border: 1px solid #575;
209 -moz-border-radius: 10px;
212 background-color: #779;
213 border: 1px solid #557;
216 -moz-border-radius: 10px;
228 <div class="error">$error</div>
229 <div class="infos">$infos</div>
230 <div class="name">$name</div>
237 =item $self->finalize_headers($c)
241 sub finalize_headers { }
243 =item $self->finalize_read($c)
248 my ( $self, $c ) = @_;
250 undef $self->{_prepared_read};
253 =item $self->finalize_uploads($c)
257 sub finalize_uploads {
258 my ( $self, $c ) = @_;
260 if ( keys %{ $c->request->uploads } ) {
261 for my $key ( keys %{ $c->request->uploads } ) {
262 my $upload = $c->request->uploads->{$key};
263 unlink map { $_->tempname }
264 grep { -e $_->tempname }
265 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
270 =item $self->prepare_body($c)
275 my ( $self, $c ) = @_;
277 $self->read_length( $c->request->header('Content-Length') || 0 );
278 my $type = $c->request->header('Content-Type');
280 unless ( $c->request->{_body} ) {
281 $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
284 if ( $self->read_length > 0 ) {
285 while ( my $buffer = $self->read($c) ) {
286 $c->prepare_body_chunk($buffer);
291 =item $self->prepare_body_chunk($c)
295 sub prepare_body_chunk {
296 my ( $self, $c, $chunk ) = @_;
298 $c->request->{_body}->add($chunk);
301 =item $self->prepare_body_parameters($c)
305 sub prepare_body_parameters {
306 my ( $self, $c ) = @_;
307 $c->request->body_parameters( $c->request->{_body}->param );
310 =item $self->prepare_connection($c)
314 sub prepare_connection { }
316 =item $self->prepare_cookies($c)
320 sub prepare_cookies {
321 my ( $self, $c ) = @_;
323 if ( my $header = $c->request->header('Cookie') ) {
324 $c->req->cookies( { CGI::Cookie->parse($header) } );
328 =item $self->prepare_headers($c)
332 sub prepare_headers { }
334 =item $self->prepare_parameters($c)
338 sub prepare_parameters {
339 my ( $self, $c ) = @_;
341 # We copy, no references
342 while ( my ( $name, $param ) = each %{ $c->request->query_parameters } ) {
343 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
344 $c->request->parameters->{$name} = $param;
347 # Merge query and body parameters
348 while ( my ( $name, $param ) = each %{ $c->request->body_parameters } ) {
349 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
350 if ( my $old_param = $c->request->parameters->{$name} ) {
351 if ( ref $old_param eq 'ARRAY' ) {
352 push @{ $c->request->parameters->{$name} },
353 ref $param eq 'ARRAY' ? @$param : $param;
355 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
357 else { $c->request->parameters->{$name} = $param }
361 =item $self->prepare_path($c)
367 =item $self->prepare_request($c)
369 =item $self->prepare_query_parameters($c)
373 sub prepare_query_parameters {
374 my ( $self, $c, $query_string ) = @_;
376 # replace semi-colons
377 $query_string =~ s/;/&/g;
379 my $u = URI->new( '', 'http' );
380 $u->query($query_string);
381 for my $key ( $u->query_param ) {
382 my @vals = $u->query_param($key);
383 $c->request->query_parameters->{$key} = @vals > 1 ? [@vals] : $vals[0];
387 =item $self->prepare_read($c)
392 my ( $self, $c ) = @_;
394 # Reset the read position
395 $self->read_position(0);
398 =item $self->prepare_request(@arguments)
402 sub prepare_request { }
404 =item $self->prepare_uploads($c)
408 sub prepare_uploads {
409 my ( $self, $c ) = @_;
410 my $uploads = $c->request->{_body}->upload;
411 for my $name ( keys %$uploads ) {
412 my $files = $uploads->{$name};
413 $files = ref $files eq 'ARRAY' ? $files : [$files];
415 for my $upload (@$files) {
416 my $u = Catalyst::Request::Upload->new;
417 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
418 $u->type( $u->headers->content_type );
419 $u->tempname( $upload->{tempname} );
420 $u->size( $upload->{size} );
421 $u->filename( $upload->{filename} );
424 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
426 # support access to the filename as a normal param
427 my @filenames = map { $_->{filename} } @uploads;
428 $c->request->parameters->{$name} =
429 @filenames > 1 ? \@filenames : $filenames[0];
433 =item $self->prepare_write($c)
437 sub prepare_write { }
439 =item $self->read($c, [$maxlength])
444 my ( $self, $c, $maxlength ) = @_;
446 unless ( $self->{_prepared_read} ) {
447 $self->prepare_read($c);
448 $self->{_prepared_read} = 1;
451 my $remaining = $self->read_length - $self->read_position;
452 $maxlength ||= $CHUNKSIZE;
454 # Are we done reading?
455 if ( $remaining <= 0 ) {
456 $self->finalize_read($c);
460 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
461 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
463 $self->read_position( $self->read_position + $rc );
467 Catalyst::Exception->throw(
468 message => "Unknown error reading input: $!" );
472 =item $self->read_chunk($c, $buffer, $length)
474 Each engine inplements read_chunk as its preferred way of reading a chunk
481 =item $self->read_length
483 The length of input data to be read. This is obtained from the Content-Length
486 =item $self->read_position
488 The amount of input data that has already been read.
496 =item $self->write($c, $buffer)
501 my ( $self, $c, $buffer ) = @_;
503 unless ( $self->{_prepared_write} ) {
504 $self->prepare_write($c);
505 $self->{_prepared_write} = 1;
508 print STDOUT $buffer;
515 Sebastian Riedel, <sri@cpan.org>
517 Andy Grundman, <andy@hybridized.org>
521 This program is free software, you can redistribute it and/or modify it under
522 the same terms as Perl itself.