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;
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( <<"" );
161 <title>$title</title>
162 <script language="JavaScript">
164 function toggleDump (dumpElement) {
165 if (dumpElement.style.display == "none") {
166 dumpElement.style.display = "";
169 dumpElement.style.display = "none";
174 <style type="text/css">
176 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
177 Tahoma, Arial, helvetica, sans-serif;
179 background-color: #eee;
183 :link, :link:hover, :visited, :visited:hover {
187 background-color: #ccc;
188 border: 1px solid #aaa;
191 -moz-border-radius: 10px;
194 background-color: #977;
195 border: 1px solid #755;
199 -moz-border-radius: 10px;
202 background-color: #797;
203 border: 1px solid #575;
207 -moz-border-radius: 10px;
210 background-color: #779;
211 border: 1px solid #557;
214 -moz-border-radius: 10px;
226 <div class="error">$error</div>
227 <div class="infos">$infos</div>
228 <div class="name">$name</div>
235 =item $self->finalize_headers($c)
239 sub finalize_headers { }
241 =item $self->finalize_read($c)
246 my ( $self, $c ) = @_;
248 undef $self->{_prepared_read};
251 =item $self->finalize_uploads($c)
255 sub finalize_uploads {
256 my ( $self, $c ) = @_;
258 if ( keys %{ $c->request->uploads } ) {
259 for my $key ( keys %{ $c->request->uploads } ) {
260 my $upload = $c->request->uploads->{$key};
261 unlink map { $_->tempname }
262 grep { -e $_->tempname }
263 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
268 =item $self->prepare_body($c)
273 my ( $self, $c ) = @_;
275 $self->read_length( $c->request->header('Content-Length') || 0 );
276 my $type = $c->request->header('Content-Type');
278 unless ( $c->request->{_body} ) {
279 $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
282 if ( $self->read_length > 0 ) {
283 while ( my $buffer = $self->read($c) ) {
284 $c->prepare_body_chunk($buffer);
289 =item $self->prepare_body_chunk($c)
293 sub prepare_body_chunk {
294 my ( $self, $c, $chunk ) = @_;
296 $c->request->{_body}->add($chunk);
299 =item $self->prepare_body_parameters($c)
303 sub prepare_body_parameters {
304 my ( $self, $c ) = @_;
305 $c->request->body_parameters( $c->request->{_body}->param );
308 =item $self->prepare_connection($c)
312 sub prepare_connection { }
314 =item $self->prepare_cookies($c)
318 sub prepare_cookies {
319 my ( $self, $c ) = @_;
321 if ( my $header = $c->request->header('Cookie') ) {
322 $c->req->cookies( { CGI::Cookie->parse($header) } );
326 =item $self->prepare_headers($c)
330 sub prepare_headers { }
332 =item $self->prepare_parameters($c)
336 sub prepare_parameters {
337 my ( $self, $c ) = @_;
339 # We copy, no references
340 while ( my ( $name, $param ) = each %{ $c->request->query_parameters } ) {
341 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
342 $c->request->parameters->{$name} = $param;
345 # Merge query and body parameters
346 while ( my ( $name, $param ) = each %{ $c->request->body_parameters } ) {
347 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
348 if ( my $old_param = $c->request->parameters->{$name} ) {
349 if ( ref $old_param eq 'ARRAY' ) {
350 push @{ $c->request->parameters->{$name} },
351 ref $param eq 'ARRAY' ? @$param : $param;
353 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
355 else { $c->request->parameters->{$name} = $param }
359 =item $self->prepare_path($c)
365 =item $self->prepare_request($c)
367 =item $self->prepare_query_parameters($c)
371 sub prepare_query_parameters {
372 my ( $self, $c, $query_string ) = @_;
374 # replace semi-colons
375 $query_string =~ s/;/&/g;
377 my $u = URI->new( '', 'http' );
378 $u->query($query_string);
379 for my $key ( $u->query_param ) {
380 my @vals = $u->query_param($key);
381 $c->request->query_parameters->{$key} = @vals > 1 ? [@vals] : $vals[0];
385 =item $self->prepare_read($c)
390 my ( $self, $c ) = @_;
392 # Reset the read position
393 $self->read_position(0);
396 =item $self->prepare_request(@arguments)
400 sub prepare_request { }
402 =item $self->prepare_uploads($c)
406 sub prepare_uploads {
407 my ( $self, $c ) = @_;
408 my $uploads = $c->request->{_body}->upload;
409 for my $name ( keys %$uploads ) {
410 my $files = $uploads->{$name};
411 $files = ref $files eq 'ARRAY' ? $files : [$files];
413 for my $upload (@$files) {
414 my $u = Catalyst::Request::Upload->new;
415 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
416 $u->type( $u->headers->content_type );
417 $u->tempname( $upload->{tempname} );
418 $u->size( $upload->{size} );
419 $u->filename( $upload->{filename} );
422 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
424 # support access to the filename as a normal param
425 my @filenames = map { $_->{filename} } @uploads;
426 $c->request->parameters->{$name} =
427 @filenames > 1 ? \@filenames : $filenames[0];
431 =item $self->prepare_write($c)
435 sub prepare_write { }
437 =item $self->read($c, [$maxlength])
442 my ( $self, $c, $maxlength ) = @_;
444 unless ( $self->{_prepared_read} ) {
445 $self->prepare_read($c);
446 $self->{_prepared_read} = 1;
449 my $remaining = $self->read_length - $self->read_position;
450 $maxlength ||= $CHUNKSIZE;
452 # Are we done reading?
453 if ( $remaining <= 0 ) {
454 $self->finalize_read($c);
458 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
459 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
461 $self->read_position( $self->read_position + $rc );
465 Catalyst::Exception->throw(
466 message => "Unknown error reading input: $!" );
470 =item $self->read_chunk($c, $buffer, $length)
472 Each engine inplements read_chunk as its preferred way of reading a chunk
479 =item $self->read_length
481 The length of input data to be read. This is obtained from the Content-Length
484 =item $self->read_position
486 The amount of input data that has already been read.
494 =item $self->write($c, $buffer)
499 my ( $self, $c, $buffer ) = @_;
501 unless ( $self->{_prepared_write} ) {
502 $self->prepare_write($c);
503 $self->{_prepared_write} = 1;
506 print STDOUT $buffer;
513 Sebastian Riedel, <sri@cpan.org>
515 Andy Grundman, <andy@hybridized.org>
519 This program is free software, you can redistribute it and/or modify it under
520 the same terms as Perl itself.