1 package Catalyst::Engine;
4 with 'MooseX::Emulate::Class::Accessor::Fast';
6 use CGI::Simple::Cookie;
7 use Data::Dump qw/dump/;
8 use Errno 'EWOULDBLOCK';
16 use namespace::clean -except => 'meta';
18 has env => (is => 'rw');
20 # input position and length
21 has read_length => (is => 'rw');
22 has read_position => (is => 'rw');
24 has _prepared_write => (is => 'rw');
26 # Amount of data to read from input on each pass
27 our $CHUNKSIZE = 64 * 1024;
31 Catalyst::Engine - The Catalyst Engine
42 =head2 $self->finalize_body($c)
44 Finalize body. Prints the response output.
49 my ( $self, $c ) = @_;
50 my $body = $c->response->body;
51 no warnings 'uninitialized';
52 if ( blessed($body) && $body->can('read') or ref($body) eq 'GLOB' ) {
55 $got = read $body, my ($buffer), $CHUNKSIZE;
56 $got = 0 unless $self->write( $c, $buffer );
62 $self->write( $c, $body );
66 =head2 $self->finalize_cookies($c)
68 Create CGI::Simple::Cookie objects from $c->res->cookies, and set them as
73 sub finalize_cookies {
74 my ( $self, $c ) = @_;
77 my $response = $c->response;
79 foreach my $name (keys %{ $response->cookies }) {
81 my $val = $response->cookies->{$name};
86 : CGI::Simple::Cookie->new(
88 -value => $val->{value},
89 -expires => $val->{expires},
90 -domain => $val->{domain},
91 -path => $val->{path},
92 -secure => $val->{secure} || 0,
93 -httponly => $val->{httponly} || 0,
97 push @cookies, $cookie->as_string;
100 for my $cookie (@cookies) {
101 $response->headers->push_header( 'Set-Cookie' => $cookie );
105 =head2 $self->finalize_error($c)
107 Output an appropriate error message. Called if there's an error in $c
108 after the dispatch has finished. Will output debug messages if Catalyst
109 is in debug mode, or a `please come back later` message otherwise.
113 sub _dump_error_page_element {
114 my ($self, $i, $element) = @_;
115 my ($name, $val) = @{ $element };
117 # This is fugly, but the metaclass is _HUGE_ and demands waaay too much
118 # scrolling. Suggestions for more pleasant ways to do this welcome.
119 local $val->{'__MOP__'} = "Stringified: "
120 . $val->{'__MOP__'} if ref $val eq 'HASH' && exists $val->{'__MOP__'};
122 my $text = encode_entities( dump( $val ));
123 sprintf <<"EOF", $name, $text;
124 <h2><a href="#" onclick="toggleDump('dump_$i'); return false">%s</a></h2>
126 <pre wrap="">%s</pre>
132 my ( $self, $c ) = @_;
134 $c->res->content_type('text/html; charset=utf-8');
135 my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
137 # Prevent Catalyst::Plugin::Unicode::Encoding from running.
138 # This is a little nasty, but it's the best way to be clean whether or
139 # not the user has an encoding plugin.
141 if ($c->can('encoding')) {
145 my ( $title, $error, $infos );
149 $error = join '', map {
150 '<p><code class="error">'
151 . encode_entities($_)
154 $error ||= 'No output';
155 $error = qq{<pre wrap="">$error</pre>};
156 $title = $name = "$name on Catalyst $Catalyst::VERSION";
157 $name = "<h1>$name</h1>";
159 # Don't show context in the dump
160 $c->req->_clear_context;
161 $c->res->_clear_context;
163 # Don't show body parser in the dump
164 $c->req->_clear_body;
168 for my $dump ( $c->dump_these ) {
169 push @infos, $self->_dump_error_page_element($i, $dump);
172 $infos = join "\n", @infos;
179 (es) Por favor inténtelo de nuevo más tarde
180 (en) Please come back later
181 (fr) SVP veuillez revenir plus tard
182 (de) Bitte versuchen sie es spaeter nocheinmal
183 (at) Konnten's bitt'schoen spaeter nochmal reinschauen
184 (no) Vennligst prov igjen senere
185 (dk) Venligst prov igen senere
186 (pl) Prosze sprobowac pozniej
187 (pt) Por favor volte mais tarde
188 (ru) Попробуйте еще раз позже
189 (ua) Спробуйте ще раз пізніше
194 $c->res->body( <<"" );
195 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
196 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
197 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
199 <meta http-equiv="Content-Language" content="en" />
200 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
201 <title>$title</title>
202 <script type="text/javascript">
204 function toggleDump (dumpElement) {
205 var e = document.getElementById( dumpElement );
206 if (e.style.display == "none") {
207 e.style.display = "";
210 e.style.display = "none";
215 <style type="text/css">
217 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
218 Tahoma, Arial, helvetica, sans-serif;
220 background-color: #eee;
224 :link, :link:hover, :visited, :visited:hover {
229 background-color: #ccc;
230 border: 1px solid #aaa;
235 background-color: #cce;
236 border: 1px solid #755;
242 background-color: #eee;
243 border: 1px solid #575;
249 background-color: #cce;
250 border: 1px solid #557;
259 div.name h1, div.error p {
267 text-decoration: underline;
273 /* from http://users.tkk.fi/~tkarvine/linux/doc/pre-wrap/pre-wrap-css3-mozilla-opera-ie.html */
274 /* Browser specific (not valid) styles to make preformatted text wrap */
276 white-space: pre-wrap; /* css-3 */
277 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
278 white-space: -pre-wrap; /* Opera 4-6 */
279 white-space: -o-pre-wrap; /* Opera 7 */
280 word-wrap: break-word; /* Internet Explorer 5.5+ */
286 <div class="error">$error</div>
287 <div class="infos">$infos</div>
288 <div class="name">$name</div>
293 # Trick IE. Old versions of IE would display their own error page instead
294 # of ours if we'd give it less than 512 bytes.
295 $c->res->{body} .= ( ' ' x 512 );
297 $c->res->{body} = Encode::encode("UTF-8", $c->res->{body});
300 $c->res->status(500);
303 =head2 $self->finalize_headers($c)
305 Abstract method, allows engines to write headers to response
309 sub finalize_headers { }
311 =head2 $self->finalize_read($c)
315 sub finalize_read { }
317 =head2 $self->finalize_uploads($c)
319 Clean up after uploads, deleting temp files.
323 sub finalize_uploads {
324 my ( $self, $c ) = @_;
326 # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
327 # on the HTTP::Body object.
328 my $request = $c->request;
329 foreach my $key (keys %{ $request->uploads }) {
330 my $upload = $request->uploads->{$key};
331 unlink grep { -e $_ } map { $_->tempname }
332 (ref $upload eq 'ARRAY' ? @{$upload} : ($upload));
337 =head2 $self->prepare_body($c)
339 sets up the L<Catalyst::Request> object body using L<HTTP::Body>
344 my ( $self, $c ) = @_;
346 my $appclass = ref($c) || $c;
347 if ( my $length = $self->read_length ) {
348 my $request = $c->request;
349 unless ( $request->_body ) {
350 my $type = $request->header('Content-Type');
351 $request->_body(HTTP::Body->new( $type, $length ));
352 $request->_body->cleanup(1); # Make extra sure!
353 $request->_body->tmpdir( $appclass->config->{uploadtmp} )
354 if exists $appclass->config->{uploadtmp};
357 # Check for definedness as you could read '0'
358 while ( defined ( my $buffer = $self->read($c) ) ) {
359 $c->prepare_body_chunk($buffer);
362 # paranoia against wrong Content-Length header
363 my $remaining = $length - $self->read_position;
364 if ( $remaining > 0 ) {
365 $self->finalize_read($c);
366 Catalyst::Exception->throw(
367 "Wrong Content-Length value: $length" );
371 # Defined but will cause all body code to be skipped
372 $c->request->_body(0);
376 =head2 $self->prepare_body_chunk($c)
378 Add a chunk to the request body.
382 sub prepare_body_chunk {
383 my ( $self, $c, $chunk ) = @_;
385 $c->request->_body->add($chunk);
388 =head2 $self->prepare_body_parameters($c)
390 Sets up parameters from body.
394 sub prepare_body_parameters {
395 my ( $self, $c ) = @_;
397 return unless $c->request->_body;
399 $c->request->body_parameters( $c->request->_body->param );
402 =head2 $self->prepare_connection($c)
404 Abstract method implemented in engines.
408 sub prepare_connection { }
410 =head2 $self->prepare_cookies($c)
412 Parse cookies from header. Sets a L<CGI::Simple::Cookie> object.
416 sub prepare_cookies {
417 my ( $self, $c ) = @_;
419 if ( my $header = $c->request->header('Cookie') ) {
420 $c->req->cookies( { CGI::Simple::Cookie->parse($header) } );
424 =head2 $self->prepare_headers($c)
428 sub prepare_headers { }
430 =head2 $self->prepare_parameters($c)
432 sets up parameters from query and post parameters.
436 sub prepare_parameters {
437 my ( $self, $c ) = @_;
439 my $request = $c->request;
440 my $parameters = $request->parameters;
441 my $body_parameters = $request->body_parameters;
442 my $query_parameters = $request->query_parameters;
443 # We copy, no references
444 foreach my $name (keys %$query_parameters) {
445 my $param = $query_parameters->{$name};
446 $parameters->{$name} = ref $param eq 'ARRAY' ? [ @$param ] : $param;
449 # Merge query and body parameters
450 foreach my $name (keys %$body_parameters) {
451 my $param = $body_parameters->{$name};
452 my @values = ref $param eq 'ARRAY' ? @$param : ($param);
453 if ( my $existing = $parameters->{$name} ) {
454 unshift(@values, (ref $existing eq 'ARRAY' ? @$existing : $existing));
456 $parameters->{$name} = @values > 1 ? \@values : $values[0];
460 =head2 $self->prepare_path($c)
462 abstract method, implemented by engines.
468 =head2 $self->prepare_request($c)
470 =head2 $self->prepare_query_parameters($c)
472 process the query string and extract query parameters.
476 sub prepare_query_parameters {
477 my ( $self, $c, $query_string ) = @_;
479 # Check for keywords (no = signs)
480 # (yes, index() is faster than a regex :))
481 if ( index( $query_string, '=' ) < 0 ) {
482 $c->request->query_keywords( $self->unescape_uri($query_string) );
488 # replace semi-colons
489 $query_string =~ s/;/&/g;
491 my @params = grep { length $_ } split /&/, $query_string;
493 for my $item ( @params ) {
496 = map { $self->unescape_uri($_) }
497 split( /=/, $item, 2 );
499 $param = $self->unescape_uri($item) unless defined $param;
501 if ( exists $query{$param} ) {
502 if ( ref $query{$param} ) {
503 push @{ $query{$param} }, $value;
506 $query{$param} = [ $query{$param}, $value ];
510 $query{$param} = $value;
514 $c->request->query_parameters( \%query );
517 =head2 $self->prepare_read($c)
519 prepare to read from the engine.
524 my ( $self, $c ) = @_;
526 # Initialize the read position
527 $self->read_position(0);
529 # Initialize the amount of data we think we need to read
530 $self->read_length( $c->request->header('Content-Length') || 0 );
533 =head2 $self->prepare_request(@arguments)
535 Populate the context object from the request object.
539 sub prepare_request { }
541 =head2 $self->prepare_uploads($c)
545 sub prepare_uploads {
546 my ( $self, $c ) = @_;
548 my $request = $c->request;
549 return unless $request->_body;
551 my $uploads = $request->_body->upload;
552 my $parameters = $request->parameters;
553 foreach my $name (keys %$uploads) {
554 my $files = $uploads->{$name};
556 for my $upload (ref $files eq 'ARRAY' ? @$files : ($files)) {
557 my $headers = HTTP::Headers->new( %{ $upload->{headers} } );
558 my $u = Catalyst::Request::Upload->new
560 size => $upload->{size},
561 type => scalar $headers->content_type,
563 tempname => $upload->{tempname},
564 filename => $upload->{filename},
568 $request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
570 # support access to the filename as a normal param
571 my @filenames = map { $_->{filename} } @uploads;
572 # append, if there's already params with this name
573 if (exists $parameters->{$name}) {
574 if (ref $parameters->{$name} eq 'ARRAY') {
575 push @{ $parameters->{$name} }, @filenames;
578 $parameters->{$name} = [ $parameters->{$name}, @filenames ];
582 $parameters->{$name} = @filenames > 1 ? \@filenames : $filenames[0];
587 =head2 $self->prepare_write($c)
589 Abstract method. Implemented by the engines.
593 sub prepare_write { }
595 =head2 $self->read($c, [$maxlength])
597 Reads from the input stream by calling C<< $self->read_chunk >>.
599 Maintains the read_length and read_position counters as data is read.
604 my ( $self, $c, $maxlength ) = @_;
606 my $remaining = $self->read_length - $self->read_position;
607 $maxlength ||= $CHUNKSIZE;
609 # Are we done reading?
610 if ( $remaining <= 0 ) {
611 $self->finalize_read($c);
615 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
616 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
618 if (0 == $rc) { # Nothing more to read even though Content-Length
619 # said there should be. FIXME - Warn in the log here?
620 $self->finalize_read;
623 $self->read_position( $self->read_position + $rc );
627 Catalyst::Exception->throw(
628 message => "Unknown error reading input: $!" );
632 =head2 $self->read_chunk($c, $buffer, $length)
634 Each engine implements read_chunk as its preferred way of reading a chunk
635 of data. Returns the number of bytes read. A return of 0 indicates that
636 there is no more data to be read.
642 =head2 $self->read_length
644 The length of input data to be read. This is obtained from the Content-Length
647 =head2 $self->read_position
649 The amount of input data that has already been read.
651 =head2 $self->run($c)
653 Start the engine. Implemented by the various engine classes.
659 =head2 $self->write($c, $buffer)
661 Writes the buffer to the client.
666 my ( $self, $c, $buffer ) = @_;
668 unless ( $self->_prepared_write ) {
669 $self->prepare_write($c);
670 $self->_prepared_write(1);
673 return 0 if !defined $buffer;
675 my $len = length($buffer);
676 my $wrote = syswrite STDOUT, $buffer;
678 if ( !defined $wrote && $! == EWOULDBLOCK ) {
679 # Unable to write on the first try, will retry in the loop below
683 if ( defined $wrote && $wrote < $len ) {
684 # We didn't write the whole buffer
686 my $ret = syswrite STDOUT, $buffer, $CHUNKSIZE, $wrote;
687 if ( defined $ret ) {
691 next if $! == EWOULDBLOCK;
695 last if $wrote >= $len;
702 =head2 $self->unescape_uri($uri)
704 Unescapes a given URI using the most efficient method available. Engines such
705 as Apache may implement this using Apache's C-based modules, for example.
710 my ( $self, $str ) = @_;
712 $str =~ s/(?:%([0-9A-Fa-f]{2})|\+)/defined $1 ? chr(hex($1)) : ' '/eg;
717 =head2 $self->finalize_output
719 <obsolete>, see finalize_body
723 Hash containing environment variables including many special variables inserted
724 by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
726 Before accessing environment variables consider whether the same information is
727 not directly available via Catalyst objects $c->request, $c->engine ...
729 BEWARE: If you really need to access some environment variable from your Catalyst
730 application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
731 as in some enviroments the %ENV hash does not contain what you would expect.
735 Catalyst Contributors, see Catalyst.pm
739 This library is free software. You can redistribute it and/or modify it under
740 the same terms as Perl itself.