use Time::HiRes qw/gettimeofday tv_interval/;
use URI;
use Scalar::Util qw/weaken/;
+use Hash::Util qw/lock_hash/;
+use HTTP::Headers::ReadOnly;
use attributes;
__PACKAGE__->mk_accessors(
=cut
-sub finalize_cookies { my $c = shift; $c->engine->finalize_cookies( $c, @_ ) }
+sub finalize_cookies {
+ my $c = shift;
+ $c->engine->finalize_cookies( $c, @_ );
+ lock_hash( %$_ ) for $c->res->cookies, values %{ $c->res->cookies };
+}
=item $c->finalize_error
$c->engine->finalize_headers( $c, @_ );
+ bless $c->response->headers, "HTTP::Headers::ReadOnly";
+
# Done
$c->response->{_finalized_headers} = 1;
}
will need to manually set the C<Content-Length> header to the length of
your output data, if known.
+Also note that any headers created after the write can no longer be added, and
+this includes cookies.
+
=cut
sub write {
--- /dev/null
+#!/usr/bin/perl
+
+package HTTP::Headers::ReadOnly;
+use base qw/HTTP::Headers/;
+
+use strict;
+use warnings;
+
+use Carp qw/croak/;
+use Class::Inspector;
+
+sub _jerk_it {
+ croak "Can't modify headers after headers have been sent to the client";
+}
+
+sub _header {
+ my ( $self, $field, $val, $op ) = @_;
+ shift;
+ _jerk_it if $val;
+
+ $self->SUPER::_header(@_);
+}
+
+BEGIN {
+ for ( @{ Class::Inspector->functions( "HTTP::Headers" ) }) {
+ no strict 'refs';
+ *$_ = \&_jerk_it if /remove|clear/;
+
+ }
+}
+
+__PACKAGE__;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+HTTP::Headers::ReadOnly - Immutable HTTP::headers
+
+=head1 SYNOPSIS
+
+ my $headers = HTTP::Headers->new(...);
+
+ bless $headers, "HTTP::Headers::ReadOnly";
+
+ $headers->content_type( "foo" ); # dies
+
+=head1 DESCRIPTION
+
+This class blocks write access to a L<HTTP::Headers> object.
+
+It is used to raise errors in L<Catalyst> if the header object is modified
+after C<finalize_headers>.
+
+=cut
+
+
--- /dev/null
+use Test::More tests => 6;
+use strict;
+use warnings;
+
+{
+
+ package MyApp;
+ use Catalyst qw/-Engine=Test/;
+ use Test::Exception;
+
+ sub stream_it : Local {
+ my ( $self, $c ) = @_;
+
+ lives_ok { $c->res->headers->content_encoding("moose") }
+ "can set header";
+ lives_ok { $c->res->headers->remove_header("moose") }
+ "can remove header";
+ lives_ok { $c->res->cookies->{yadda} = { value => "ping" } }
+ "can make cookie";
+ $c->write("foo");
+ throws_ok { $c->res->headers->content_encoding("moose") }
+ qr/can't modify/i, "can't set header after write";
+ throws_ok { $c->res->headers->remove_header("moose") }
+ qr/can't modify/i, "can't remove header after write";
+ throws_ok { $c->res->cookies->{yadda} = { value => "ping" } }
+ qr/read-only/i, "can't make cookie after write";
+ }
+
+ __PACKAGE__->setup;
+}
+
+use Catalyst::Test qw/MyApp/;
+
+get "/stream_it";
+