The latter part of that doc doesn't apply in master, yet
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
index 84450c4..8f88cef 100644 (file)
@@ -11,11 +11,26 @@ use HTTP::Body;
 use HTTP::Headers;
 use URI::QueryParam;
 use Moose::Util::TypeConstraints;
+use Plack::Loader;
+use Catalyst::EngineLoader;
+use Encode ();
+use utf8;
 
 use namespace::clean -except => 'meta';
 
 has env => (is => 'ro', writer => '_set_env', clearer => '_clear_env');
 
+my $WARN_ABOUT_ENV = 0;
+around env => sub {
+  my ($orig, $self, @args) = @_;
+  if(@args) {
+    warn "env as a writer is deprecated, you probably need to upgrade Catalyst::Engine::PSGI"
+      unless $WARN_ABOUT_ENV++;
+    return $self->_set_env(@args);
+  }
+  return $self->$orig;
+};
+
 # input position and length
 has read_length => (is => 'rw');
 has read_position => (is => 'rw');
@@ -23,15 +38,21 @@ has read_position => (is => 'rw');
 has _prepared_write => (is => 'rw');
 
 has _response_cb => (
-    is     => 'ro',
-    isa    => 'CodeRef',
-    writer => '_set_response_cb',
+    is      => 'ro',
+    isa     => 'CodeRef',
+    writer  => '_set_response_cb',
+    clearer => '_clear_response_cb',
+    predicate => '_has_response_cb',
 );
 
+subtype 'Catalyst::Engine::Types::Writer',
+    as duck_type([qw(write close)]);
+
 has _writer => (
-    is     => 'ro',
-    isa    => duck_type([qw(write close)]),
-    writer => '_set_writer',
+    is      => 'ro',
+    isa     => 'Catalyst::Engine::Types::Writer',
+    writer  => '_set_writer',
+    clearer => '_clear_writer',
 );
 
 # Amount of data to read from input on each pass
@@ -74,6 +95,8 @@ sub finalize_body {
     }
 
     $self->_writer->close;
+    $self->_clear_writer;
+    $self->_clear_env;
 
     return;
 }
@@ -108,6 +131,11 @@ sub finalize_cookies {
                 -httponly => $val->{httponly} || 0,
             )
         );
+        if (!defined $cookie) {
+            $c->log->warn("undef passed in '$name' cookie value - not setting cookie")
+                if $c->debug;
+            next;
+        }
 
         push @cookies, $cookie->as_string;
     }
@@ -148,6 +176,14 @@ sub finalize_error {
 
     $c->res->content_type('text/html; charset=utf-8');
     my $name = ref($c)->config->{name} || join(' ', split('::', ref $c));
+    
+    # Prevent Catalyst::Plugin::Unicode::Encoding from running.
+    # This is a little nasty, but it's the best way to be clean whether or
+    # not the user has an encoding plugin.
+
+    if ($c->can('encoding')) {
+      $c->{encoding} = '';
+    }
 
     my ( $title, $error, $infos );
     if ( $c->debug ) {
@@ -296,10 +332,12 @@ sub finalize_error {
 </body>
 </html>
 
-
-    # Trick IE
+    # Trick IE. Old versions of IE would display their own error page instead
+    # of ours if we'd give it less than 512 bytes.
     $c->res->{body} .= ( ' ' x 512 );
 
+    $c->res->{body} = Encode::encode("UTF-8", $c->res->{body});
+
     # Return 500
     $c->res->status(500);
 }
@@ -313,10 +351,20 @@ Abstract method, allows engines to write headers to response
 sub finalize_headers {
     my ($self, $ctx) = @_;
 
+    # This is a less-than-pretty hack to avoid breaking the old
+    # Catalyst::Engine::PSGI. 5.9 Catalyst::Engine sets a response_cb and
+    # expects us to pass headers to it here, whereas Catalyst::Enngine::PSGI
+    # just pulls the headers out of $ctx->response in its run method and never
+    # sets response_cb. So take the lack of a response_cb as a sign that we
+    # don't need to set the headers.
+
+    return unless $self->_has_response_cb;
+
     my @headers;
     $ctx->response->headers->scan(sub { push @headers, @_ });
 
     $self->_set_writer($self->_response_cb->([ $ctx->response->status, \@headers ]));
+    $self->_clear_response_cb;
 
     return;
 }
@@ -336,6 +384,8 @@ Clean up after uploads, deleting temp files.
 sub finalize_uploads {
     my ( $self, $c ) = @_;
 
+    # N.B. This code is theoretically entirely unneeded due to ->cleanup(1)
+    #      on the HTTP::Body object.
     my $request = $c->request;
     foreach my $key (keys %{ $request->uploads }) {
         my $upload = $request->uploads->{$key};
@@ -360,6 +410,7 @@ sub prepare_body {
         unless ( $request->_body ) {
             my $type = $request->header('Content-Type');
             $request->_body(HTTP::Body->new( $type, $length ));
+            $request->_body->cleanup(1); # Make extra sure!
             $request->_body->tmpdir( $appclass->config->{uploadtmp} )
               if exists $appclass->config->{uploadtmp};
         }
@@ -427,7 +478,7 @@ sub prepare_connection {
     $request->protocol( $env->{SERVER_PROTOCOL} );
     $request->remote_user( $env->{REMOTE_USER} );
     $request->method( $env->{REQUEST_METHOD} );
-    $request->secure( $env->{'psgi.url_scheme'} eq 'https' );
+    $request->secure( $env->{'psgi.url_scheme'} eq 'https' ? 1 : 0 );
 
     return;
 }
@@ -511,10 +562,24 @@ sub prepare_path {
     my $base_path = $env->{SCRIPT_NAME} || "/";
 
     # set the request URI
-    my $req_uri = $env->{REQUEST_URI};
-    $req_uri =~ s/\?.*$//;
-    my $path = $self->unescape_uri($req_uri);
-    $path =~ s{^/+}{};
+    my $path;
+    if (!$ctx->config->{use_request_uri_for_path}) {
+        my $path_info = $env->{PATH_INFO};
+        if ( exists $env->{REDIRECT_URL} ) {
+            $base_path = $env->{REDIRECT_URL};
+            $base_path =~ s/\Q$path_info\E$//;
+        }
+        $path = $base_path . $path_info;
+        $path =~ s{^/+}{};
+        $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
+        $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
+    }
+    else {
+        my $req_uri = $env->{REQUEST_URI};
+        $req_uri =~ s/\?.*$//;
+        $path = $req_uri;
+        $path =~ s{^/+}{};
+    }
 
     # Using URI directly is way too slow, so we construct the URLs manually
     my $uri_class = "URI::$scheme";
@@ -526,14 +591,10 @@ sub prepare_path {
         $host .= ":$port";
     }
 
-    # Escape the path
-    $path =~ s/([^$URI::uric])/$URI::Escape::escapes{$1}/go;
-    $path =~ s/\?/%3F/g; # STUPID STUPID SPECIAL CASE
-
     my $query = $env->{QUERY_STRING} ? '?' . $env->{QUERY_STRING} : '';
     my $uri   = $scheme . '://' . $host . '/' . $path . $query;
 
-    $ctx->request->uri( bless \$uri, $uri_class );
+    $ctx->request->uri( (bless \$uri, $uri_class)->canonical );
 
     # set the base URI
     # base must end in a slash
@@ -617,7 +678,7 @@ sub prepare_read {
 
 =head2 $self->prepare_request(@arguments)
 
-Populate the context object from the request object.
+Sets up the PSGI environment in the Engine.
 
 =cut
 
@@ -646,7 +707,7 @@ sub prepare_uploads {
             my $u = Catalyst::Request::Upload->new
               (
                size => $upload->{size},
-               type => $headers->content_type,
+               type => scalar $headers->content_type,
                headers => $headers,
                tempname => $upload->{tempname},
                filename => $upload->{filename},
@@ -704,7 +765,7 @@ sub read {
     my $rc = $self->read_chunk( $c, my $buffer, $readlen );
     if ( defined $rc ) {
         if (0 == $rc) { # Nothing more to read even though Content-Length
-                        # said there should be. FIXME - Warn in the log here?
+                        # said there should be.
             $self->finalize_read;
             return;
         }
@@ -725,7 +786,10 @@ there is no more data to be read.
 
 =cut
 
-sub read_chunk { }
+sub read_chunk {
+    my ($self, $ctx) = (shift, shift);
+    return $self->env->{'psgi.input'}->read(@_);
+}
 
 =head2 $self->read_length
 
@@ -736,14 +800,51 @@ header.
 
 The amount of input data that has already been read.
 
-=head2 $self->run($c)
+=head2 $self->run($app, $server)
 
-Start the engine. Implemented by the various engine classes.
+Start the engine. Builds a PSGI application and calls the
+run method on the server passed in, which then causes the
+engine to loop, handling requests..
 
 =cut
 
 sub run {
-    my ($self, $app) = @_;
+    my ($self, $app, $psgi, @args) = @_;
+    # @args left here rather than just a $options, $server for back compat with the
+    # old style scripts which send a few args, then a hashref
+
+    # They should never actually be used in the normal case as the Plack engine is
+    # passed in got all the 'standard' args via the loader in the script already.
+
+    # FIXME - we should stash the options in an attribute so that custom args
+    # like Gitalist's --git_dir are possible to get from the app without stupid tricks.
+    my $server = pop @args if (scalar @args && blessed $args[-1]);
+    my $options = pop @args if (scalar @args && ref($args[-1]) eq 'HASH');
+    # Back compat hack for applications with old (non Catalyst::Script) scripts to work in FCGI.
+    if (scalar @args && !ref($args[0])) {
+        if (my $listen = shift @args) {
+            $options->{listen} ||= [$listen];
+        }
+    }
+    if (! $server ) {
+        $server = Catalyst::EngineLoader->new(application_name => ref($self))->auto(%$options);
+        # We're not being called from a script, so auto detect what backend to
+        # run on.  This should never happen, as mod_perl never calls ->run,
+        # instead the $app->handle method is called per request.
+        $app->log->warn("Not supplied a Plack engine, falling back to engine auto-loader (are your scripts ancient?)")
+    }
+    $app->run_options($options);
+    $server->run($psgi, $options);
+}
+
+=head2 build_psgi_app ($app, @args)
+
+Builds and returns a PSGI application closure. (Raw, not wrapped in middleware)
+
+=cut
+
+sub build_psgi_app {
+    my ($self, $app, @args) = @_;
 
     return sub {
         my ($env) = @_;
@@ -770,7 +871,7 @@ sub write {
         $self->_prepared_write(1);
     }
 
-    return 0 if !defined $buffer;
+    $buffer = q[] unless defined $buffer;
 
     my $len = length($buffer);
     $self->_writer->write($buffer);
@@ -799,15 +900,15 @@ sub unescape_uri {
 
 =head2 $self->env
 
-Hash containing enviroment variables including many special variables inserted
+Hash containing environment variables including many special variables inserted
 by WWW server - like SERVER_*, REMOTE_*, HTTP_* ...
 
-Before accesing enviroment variables consider whether the same information is
+Before accessing environment variables consider whether the same information is
 not directly available via Catalyst objects $c->request, $c->engine ...
 
-BEWARE: If you really need to access some enviroment variable from your Catalyst
+BEWARE: If you really need to access some environment variable from your Catalyst
 application you should use $c->engine->env->{VARNAME} instead of $ENV{VARNAME},
-as in some enviroments the %ENV hash does not contain what you would expect.
+as in some environments the %ENV hash does not contain what you would expect.
 
 =head1 AUTHORS