- lots of UTF8 changes. Again we think this is now more correct but please test.
- Allow $c->res->redirect($url) to accept $url as an object that does ->as_string
which I think will ease a common case (and common bug) and added documentation.
- - UTF-8 is now the default encoding (there used to be none...). You can disable
+ - !!! UTF-8 is now the default encoding (there used to be none...). You can disable
this if you need to with MyApp->config(encoding => undef) if it causes you trouble.
- Calling $c->res->write($data) now encodes $data based on the configured encoding
(UTF-8 is default).
+ - $c->res->writer_fh now returns Catalyst::Response::Writer which is a decorator
+ over the PSGI writer and provides and additional methd 'write_encoded' that just
+ does the right thing for encoding your responses. This is probably the method
+ you want to use.
5.90077 - 2014-11-18
- We store the PSGI $env in Catalyst::Engine for backcompat reasons. Changed
lazy => 1,
);
sub _build_response_constructor_args {
- my $self = shift;
- { _log => $self->log };
+ return +{
+ _log => $_[0]->log,
+ encoding => $_[0]->encoding,
+ };
}
has namespace => (is => 'rw');
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/^text|xml$|javascript$/;
+ 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;
=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;
--- /dev/null
+package Catalyst::Response::Writer;
+
+sub write { shift->{_writer}->write(@_) }
+sub close { shift->{_writer}->close }
+
+sub write_encoded {
+ my ($self, $line) = @_;
+ if((my $enc = $self->{_encoding}) && $self->{_requires_encoding}) {
+ # Not going to worry about CHECK arg since Unicode always croaks I think - jnap
+ $line = $enc->encode($line);
+ }
+
+ $self->write($line);
+}
+
+=head1 TITLE
+
+Catalyst::Response::Writer - Proxy over the PSGI Writer
+
+=head1 SYNOPSIS
+
+ sub myaction : Path {
+ my ($self, $c) = @_;
+ my $w = $c->response->writer_fh;
+
+ $w->write("hello world");
+ $w->close;
+ }
+
+=head1 DESCRIPTION
+
+This wraps the PSGI writer (see L<PSGI.pod\Delayed-Response-and-Streaming-Body>)
+for more. We wrap this object so we can provide some additional methods that
+make sense from inside L<Catalyst>
+
+=head1 METHODS
+
+This class does the following methods
+
+=head2 write
+
+=head2 close
+
+These delegate to the underlying L<PSGI> writer object
+
+=head2 write_encoded
+
+If the application defines a response encoding (default is UTF8) and the
+content type is a type that needs to be encoded (text types like HTML or XML and
+Javascript) we first encode the line you want to write. This is probably the
+thing you want to always do. If you use the L<\write> method directly you will
+need to handle your own encoding.
+
+=head1 AUTHORS
+
+Catalyst Contributors, see Catalyst.pm
+
+=head1 COPYRIGHT
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
$c->response->content_type('text/html');
my $writer = $c->res->write_fh;
-
- $writer->write(Encode::encode_utf8('<p>This is stream_write_fh action ♥</p>'));
+ $writer->write_encoded('<p>This is stream_write_fh action ♥</p>');
$writer->close;
}
+ # Stream a file with utf8 chars directly, you don't need to decode
sub stream_body_fh :Local {
my ($self, $c) = @_;
-
my $path = File::Spec->catfile('t', 'utf8.txt');
open(my $fh, '<', $path) || die "trouble: $!";
$c->response->content_type('text/html');
$c->response->body($fh);
}
+ # If you pull the file contents into a var, NOW you need to specify the
+ # IO encoding on the FH. Ultimately Plack at the end wants bytes...
+ sub stream_body_fh2 :Local {
+ my ($self, $c) = @_;
+ my $path = File::Spec->catfile('t', 'utf8.txt');
+ open(my $fh, '<:encoding(UTF-8)', $path) || die "trouble: $!";
+ my $contents = do { local $/; <$fh> };
+
+ $c->response->content_type('text/html');
+ $c->response->body($contents);
+ }
+
+
package MyApp;
use Catalyst;
is $res->content_charset, 'UTF-8';
}
+{
+ my $res = request "/root/stream_body_fh2";
+
+ is $res->code, 200, 'OK';
+ is decode_utf8($res->content), "<p>This is stream_body_fh action ♥</p>\n", 'correct body';
+ is $res->content_length, 41, 'correct length';
+ is $res->content_charset, 'UTF-8';
+}
+
done_testing;