use HTTP::Headers;
use Moose::Util::TypeConstraints;
use namespace::autoclean;
+use Scalar::Util 'blessed';
+use Catalyst::Response::Writer;
with 'MooseX::Emulate::Class::Accessor::Fast';
builder=>'_build_write_fh',
);
-sub _build_write_fh { shift ->_writer }
+sub _build_write_fh {
+ my $writer = $_[0]->_writer; # We need to get the finalize headers side effect...
+ my $requires_encoding = $_[0]->content_type =~ m/$Catalyst::DEFAULT_ENCODE_CONTENT_TYPE_MATCH/;
+ my %fields = (
+ _writer => $writer,
+ _encoding => $_[0]->encoding,
+ _requires_encoding => $requires_encoding,
+ );
+
+ return bless \%fields, 'Catalyst::Response::Writer';
+}
sub DEMOLISH {
my $self = shift;
clearer => '_clear_context',
);
+has encoding => (is=>'ro');
+
before [qw(status headers content_encoding content_length content_type header)] => sub {
my $self = shift;
$buffer = q[] unless defined $buffer;
+ $buffer = $self->_context->encoding->encode( $buffer, $self->_context->_encode_check )
+ if $self->_context->encoding && $self->content_type =~ /$Catalyst::DEFAULT_ENCODE_CONTENT_TYPE_MATCH/;
+
my $len = length($buffer);
$self->_writer->write($buffer);
sub from_psgi_response {
my ($self, $psgi_res) = @_;
+ if(blessed($psgi_res) && $psgi_res->can('as_psgi')) {
+ $psgi_res = $psgi_res->as_psgi;
+ }
if(ref $psgi_res eq 'ARRAY') {
my ($status, $headers, $body) = @$psgi_res;
$self->status($status);
it is recommended that you set the content length in the response headers
yourself, which will be respected and sent by Catalyst in the response.
+Please note that the object needs to implement C<getline>, not just
+C<read>.
+
+Starting from version 5.90060, when using an L<IO::Handle> object, you
+may want to use L<Plack::Middleware::XSendfile>, to delegate the
+actual serving to the frontend server. To do so, you need to pass to
+C<body> an IO object with a C<path> method. This can be achieved in
+two ways.
+
+Either using L<Plack::Util>:
+
+ my $fh = IO::File->new($file, 'r');
+ Plack::Util::set_io_path($fh, $file);
+
+Or using L<IO::File::WithPath>
+
+ my $fh = IO::File::WithPath->new($file, 'r');
+
+And then passing the filehandle to body and setting headers, if needed.
+
+ $c->response->body($fh);
+ $c->response->headers->content_type('text/plain');
+ $c->response->headers->content_length(-s $file);
+ $c->response->headers->last_modified((stat($file))[9]);
+
+L<Plack::Middleware::XSendfile> can be loaded in the application so:
+
+ __PACKAGE__->config(
+ psgi_middleware => [
+ 'XSendfile',
+ # other middlewares here...
+ ],
+ );
+
+B<Beware> that loading the middleware without configuring the
+webserver to set the request header C<X-Sendfile-Type> to a supported
+type (C<X-Accel-Redirect> for nginx, C<X-Sendfile> for Apache and
+Lighttpd), could lead to the disclosure of private paths to malicious
+clients setting that header.
+
+Nginx needs the additional X-Accel-Mapping header to be set in the
+webserver configuration, so the middleware will replace the absolute
+path of the IO object with the internal nginx path. This is also
+useful to prevent a buggy app to server random files from the
+filesystem, as it's an internal redirect.
+
+An nginx configuration for FastCGI could look so:
+
+ server {
+ server_name example.com;
+ root /my/app/root;
+ location /private/repo/ {
+ internal;
+ alias /my/app/repo/;
+ }
+ location /private/staging/ {
+ internal;
+ alias /my/app/staging/;
+ }
+ location @proxy {
+ include /etc/nginx/fastcgi_params;
+ fastcgi_param SCRIPT_NAME '';
+ fastcgi_param PATH_INFO $fastcgi_script_name;
+ fastcgi_param HTTP_X_SENDFILE_TYPE X-Accel-Redirect;
+ fastcgi_param HTTP_X_ACCEL_MAPPING /my/app=/private;
+ fastcgi_pass unix:/my/app/run/app.sock;
+ }
+ }
+
+In the example above, passing filehandles with a local path matching
+/my/app/staging or /my/app/repo will be served by nginx. Passing paths
+with other locations will lead to an internal server error.
+
+Setting the body to a filehandle without the C<path> method bypasses
+the middleware completely.
+
+For Apache and Lighttpd, the mapping doesn't apply and setting the
+X-Sendfile-Type is enough.
+
=head2 $res->has_body
Predicate which returns true when a body has been set.
thing and is not a standard behaviour. You may opt to use uri_for() or
uri_for_action() instead.
+B<Note:> If $url is an object that does ->as_string (such as L<URI>, which is
+what you get from ->uri_for) we automatically call that to stringify. This
+should ease the common case usage
+
+ return $c->res->redirect( $c->uri_for(...));
+
=cut
sub redirect {
my $location = shift;
my $status = shift || 302;
+ if(blessed($location) && $location->can('as_string')) {
+ $location = $location->as_string;
+ }
+
$self->location($location);
$self->status($status);
}
=head2 $res->write( $data )
-Writes $data to the output stream.
+Writes $data to the output stream. Calling this method will finalize your
+headers and send the headers and status code response to the client (so changing
+them afterwards is a waste... be sure to set your headers correctly first).
+
+You may call this as often as you want throughout your response cycle. You may
+even set a 'body' afterward. So for example you might write your HTTP headers
+and the HEAD section of your document and then set the body from a template
+driven from a database. In some cases this can seem to the client as if you had
+a faster overall response (but note that unless your server support chunked
+body your content is likely to get queued anyway (L<Starman> and most other
+http 1.1 webservers support this).
+
+If there is an encoding set, we encode each line of the response (the default
+encoding is UTF-8).
=head2 $res->write_fh
-Returns a PSGI $writer object that has two methods, write and close. You can
-close over this object for asynchronous and nonblocking applications. For
-example (assuming you are using a supporting server, like L<Twiggy>
+Returns an instance of L<Catalyst::Response::Writer>, which is a lightweight
+decorator over the PSGI C<$writer> object (see L<PSGI.pod\Delayed-Response-and-Streaming-Body>).
+
+In addition to proxying the C<write> and C<close> method from the underlying PSGI
+writer, this proxy object knows any application wide encoding, and provides a method
+C<write_encoded> that will properly encode your written lines based upon your
+encoding settings. By default in L<Catalyst> responses are UTF-8 encoded and this
+is the encoding used if you respond via C<write_encoded>. If you want to handle
+encoding yourself, you can use the C<write> method directly.
+
+Encoding only applies to content types for which it matters. Currently the following
+content types are assumed to need encoding: text (including HTML), xml and javascript.
+
+We provide access to this object so that you can properly close over it for use in
+asynchronous and nonblocking applications. For example (assuming you are using a supporting
+server, like L<Twiggy>:
package AsyncExample::Controller::Root;
});
}
+Like the 'write' method, calling this will finalize headers. Unlike 'write' when you
+can this it is assumed you are taking control of the response so the body is never
+finalized (there isn't one anyway) and you need to call the close method.
+
=head2 $res->print( @data )
Prints @data to the output stream, separated by $,. This lets you pass
Properly supports streaming and delayed response and / or async IO if running
under an expected event loop.
+If passed an object, will expect that object to do a method C<as_psgi>.
+
Example:
package MyApp::Web::Controller::Test;