File Coverage

blib/lib/Dancer2/Core/Response.pm
Criterion Covered Total %
statement 94 95 98.9
branch 23 26 88.4
condition 9 14 64.2
subroutine 27 27 100.0
pod 11 13 84.6
total 164 175 93.7


line stmt bran cond sub pod time code
1             # ABSTRACT: Response object for Dancer2
2              
3             package Dancer2::Core::Response;
4             $Dancer2::Core::Response::VERSION = '2.0.1';
5 151     151   430670 use Moo;
  151         21346  
  151         1596  
6              
7 151     151   76488 use Encode;
  151         36507  
  151         20276  
8 151     151   2621 use Dancer2::Core::Types;
  151         389  
  151         1476  
9              
10 151     151   2310516 use Dancer2 ();
  151         470  
  151         4898  
11 151     151   891 use Dancer2::Core::HTTP;
  151         375  
  151         5275  
12              
13 151     151   950 use HTTP::Headers::Fast;
  151         365  
  151         6953  
14 151     151   988 use Scalar::Util qw(blessed);
  151         384  
  151         12609  
15 151     151   1118 use Plack::Util;
  151         352  
  151         5146  
16 151     151   926 use Safe::Isa;
  151         358  
  151         27959  
17 151     151   1217 use Sub::Quote ();
  151         410  
  151         16112  
18              
19             use overload
20 28     28   77 '@{}' => sub { $_[0]->to_psgi },
21 151     151   1113 '""' => sub { $_[0] };
  151     1735   345  
  151         2311  
  1735         29627  
22              
23             has headers => (
24             is => 'ro',
25             isa => InstanceOf['HTTP::Headers'],
26             lazy => 1,
27             coerce => sub {
28             my ($value) = @_;
29             # HTTP::Headers::Fast reports that it isa 'HTTP::Headers',
30             # but there is no actual inheritance.
31             $value->$_isa('HTTP::Headers')
32             ? $value
33             : HTTP::Headers::Fast->new(@{$value});
34             },
35             default => sub {
36             HTTP::Headers::Fast->new();
37             },
38             handles => [qw
],
39             );
40              
41             sub headers_to_array {
42 666     666 1 7604 my $self = shift;
43 666   66     2872 my $headers = shift || $self->headers;
44              
45 666         1573 my @hdrs;
46             $headers->scan( sub {
47 2077     2077   40529 my ( $k, $v ) = @_;
48 2077         4711 $v =~ s/\015\012[\040|\011]+/chr(32)/ge; # replace LWS with a single SP
  0         0  
49 2077         5877 $v =~ s/\015|\012//g; # remove CR and LF since the char is invalid here
50 2077         6813 push @hdrs, $k => $v;
51 666         6685 });
52              
53 666         7820 return \@hdrs;
54             }
55              
56             # boolean to tell if the route passes or not
57             has has_passed => (
58             is => 'rw',
59             isa => Bool,
60             default => sub {0},
61             );
62              
63 2     2 1 103 sub pass { shift->has_passed(1) }
64              
65             has serializer => (
66             is => 'ro',
67             isa => ConsumerOf ['Dancer2::Core::Role::Serializer'],
68             );
69              
70             has is_encoded => (
71             is => 'rw',
72             isa => Bool,
73             default => sub {0},
74             );
75              
76             has is_halted => (
77             is => 'rw',
78             isa => Bool,
79             default => sub {0},
80             );
81              
82             sub halt {
83 10     10 1 310 my ( $self, $content ) = @_;
84 10 100       65 $self->content( $content ) if @_ > 1;
85 10         345 $self->is_halted(1);
86             }
87              
88             has status => (
89             is => 'rw',
90             isa => Num,
91             default => sub {200},
92             lazy => 1,
93             coerce => sub { Dancer2::Core::HTTP->status(shift) },
94             );
95              
96             has content => (
97             is => 'rw',
98             isa => Str,
99             predicate => 'has_content',
100             clearer => 'clear_content',
101             );
102              
103             has server_tokens => (
104             is => 'ro',
105             isa => Bool,
106             default => sub {1},
107             );
108              
109             around content => sub {
110             my ( $orig, $self ) = ( shift, shift );
111              
112             # called as getter?
113             @_ or return $self->$orig;
114              
115             # No serializer defined; encode content
116             $self->serializer
117             or return $self->$orig( $self->encode_content(@_) );
118              
119             # serialize content
120             my $serialized = $self->serialize(@_);
121             $self->is_encoded(1); # All serializers return byte strings
122             return $self->$orig( defined $serialized ? $serialized : '' );
123             };
124              
125             has default_content_type => (
126             is => 'rw',
127             isa => Str,
128             default => sub {'text/html'},
129             );
130              
131             sub encode_content {
132 575     575 1 1674 my ( $self, $content ) = @_;
133              
134 575 100       12757 return $content if $self->is_encoded;
135              
136             # Apply default content type if none set.
137 566   66     6317 my $ct = $self->content_type ||
138             $self->content_type( $self->default_content_type );
139              
140 566 50       10801 return $content if $ct !~ /^text/;
141              
142             # we don't want to encode an empty string, it will break the output
143 566 100       2674 $content or return $content;
144              
145 531 50       3750 $self->content_type("$ct; charset=UTF-8")
146             if $ct !~ /charset/;
147              
148 531         12431 $self->is_encoded(1);
149 531         23499 return Encode::encode( 'UTF-8', $content );
150             }
151              
152             sub new_from_plack {
153 1     1 1 5240 my ($self, $psgi_res) = @_;
154              
155 1         6 return Dancer2::Core::Response->new(
156             status => $psgi_res->status,
157             headers => $psgi_res->headers,
158             content => $psgi_res->body,
159             );
160             }
161              
162             sub new_from_array {
163 1     1 1 506439 my ($self, $arrayref) = @_;
164              
165 1         23 return Dancer2::Core::Response->new(
166             status => $arrayref->[0],
167             headers => $arrayref->[1],
168             content => $arrayref->[2][0],
169             );
170             }
171              
172             sub to_psgi {
173 644     644 1 19532 my ($self) = @_;
174              
175 644 100       7751 $self->server_tokens
176             and $self->header( 'Server' => "Perl Dancer2 " . Dancer2->VERSION );
177              
178 644         63519 my $headers = $self->headers;
179 644         18083 my $status = $self->status;
180              
181 644 100       24063 Plack::Util::status_with_no_entity_body($status)
182             and return [ $status, $self->headers_to_array($headers), [] ];
183              
184 643         21878 my $content = $self->content;
185             # It is possible to have no content and/or no content type set
186             # e.g. if all routes 'pass'. Set the default value for the content
187             # (an empty string), allowing serializer hooks to be triggered
188             # as they may change the content..
189 643 100       6940 $content = $self->content('') if ! defined $content;
190              
191 643 100 66     4033 if ( !$headers->header('Content-Length') &&
      66        
192             !$headers->header('Transfer-Encoding') &&
193             defined( my $content_length = length $content ) ) {
194 616         40915 $headers->push_header( 'Content-Length' => $content_length );
195             }
196              
197             # More defaults
198 643 100       23396 $self->content_type or $self->content_type($self->default_content_type);
199 643         39097 return [ $status, $self->headers_to_array($headers), [ $content ], ];
200             }
201              
202             # sugar for accessing the content_type header, with mimetype care
203             sub content_type {
204 2398     2398 1 82011 my $self = shift;
205              
206 2398 100       6268 if ( scalar @_ > 0 ) {
207 1187         4756 my $runner = Dancer2::runner();
208 1187         8338 my $mimetype = $runner->mime_type->name_or_type(shift);
209 1187         27936 $self->header( 'Content-Type' => $mimetype );
210 1187         96692 return $mimetype;
211             }
212             else {
213 1211         28856 return $self->header('Content-Type');
214             }
215             }
216              
217             has _forward => (
218             is => 'rw',
219             isa => HashRef,
220             );
221              
222             sub forward {
223 1     1 0 226 my ( $self, $uri, $params, $opts ) = @_;
224 1         25 $self->_forward( { to_url => $uri, params => $params, options => $opts } );
225             }
226              
227             sub is_forwarded {
228 2     2 0 693 my $self = shift;
229 2         38 $self->_forward;
230             }
231              
232             sub redirect {
233 26     26 1 331 my ( $self, $destination, $status ) = @_;
234 26   50     780 $self->status( $status || 302 );
235              
236             # we want to stringify the $destination object (URI object)
237 26         1874 $self->header( 'Location' => "$destination" );
238             }
239              
240             sub error {
241 1     1 1 36 my $self = shift;
242              
243 1         32 my $error = Dancer2::Core::Error->new(
244             response => $self,
245             @_,
246             );
247              
248 1         7 $error->throw;
249 1         17 return $error;
250             }
251              
252             sub serialize {
253 78     78 1 192 my ($self, $content) = @_;
254              
255 78 50       357 my $serializer = $self->serializer
256             or return;
257              
258 78 100       1786 $content = $serializer->serialize($content)
259             or return;
260              
261 77         490 $self->content_type( $serializer->content_type );
262 77         237 return $content;
263             }
264              
265             1;
266              
267             __END__