File Coverage

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


line stmt bran cond sub pod time code
1             # ABSTRACT: Response object for Dancer2
2              
3             $Dancer2::Core::Response::VERSION = '0.400000';
4             use Moo;
5 140     140   61585  
  140         6017  
  140         959  
6             use Encode;
7 140     140   45425 use Dancer2::Core::Types;
  140         24856  
  140         12724  
8 140     140   1299  
  140         321  
  140         1019  
9             use Dancer2 ();
10 140     140   1125416 use Dancer2::Core::HTTP;
  140         372  
  140         3019  
11 140     140   745  
  140         310  
  140         3557  
12             use HTTP::Headers::Fast;
13 140     140   779 use Scalar::Util qw(blessed);
  140         307  
  140         4827  
14 140     140   828 use Plack::Util;
  140         342  
  140         8355  
15 140     140   922 use Safe::Isa;
  140         382  
  140         3747  
16 140     140   770 use Sub::Quote ();
  140         286  
  140         18166  
17 140     140   937  
  140         377  
  140         11096  
18             use overload
19             '@{}' => sub { $_[0]->to_psgi },
20 28     28   59 '""' => sub { $_[0] };
21 140     140   922  
  140     1604   318  
  140         1823  
  1604         22242  
22             has headers => (
23             is => 'ro',
24             isa => InstanceOf['HTTP::Headers'],
25             lazy => 1,
26             coerce => sub {
27             my ($value) = @_;
28             # HTTP::Headers::Fast reports that it isa 'HTTP::Headers',
29             # but there is no actual inheritance.
30             $value->$_isa('HTTP::Headers')
31             ? $value
32             : HTTP::Headers::Fast->new(@{$value});
33             },
34             default => sub {
35             HTTP::Headers::Fast->new();
36             },
37             handles => [qw<header push_header>],
38             );
39              
40             my $self = shift;
41             my $headers = shift || $self->headers;
42 623     623 1 5682  
43 623   66     6725 my @hdrs;
44             $headers->scan( sub {
45 623         3363 my ( $k, $v ) = @_;
46             $v =~ s/\015\012[\040|\011]+/chr(32)/ge; # replace LWS with a single SP
47 1953     1953   28483 $v =~ s/\015|\012//g; # remove CR and LF since the char is invalid here
48 1953         3229 push @hdrs, $k => $v;
  0         0  
49 1953         6155 });
50 1953         4638  
51 623         4522 return \@hdrs;
52             }
53 623         5019  
54             # boolean to tell if the route passes or not
55             has has_passed => (
56             is => 'rw',
57             isa => Bool,
58             default => sub {0},
59             );
60              
61              
62             has serializer => (
63 2     2 1 44 is => 'ro',
64             isa => ConsumerOf ['Dancer2::Core::Role::Serializer'],
65             );
66              
67             has is_encoded => (
68             is => 'rw',
69             isa => Bool,
70             default => sub {0},
71             );
72              
73             has is_halted => (
74             is => 'rw',
75             isa => Bool,
76             default => sub {0},
77             );
78              
79             my ( $self, $content ) = @_;
80             $self->content( $content ) if @_ > 1;
81             $self->is_halted(1);
82             }
83 8     8 1 117  
84 8 100       66 has status => (
85 8         514 is => 'rw',
86             isa => Num,
87             default => sub {200},
88             lazy => 1,
89             coerce => sub { Dancer2::Core::HTTP->status(shift) },
90             );
91              
92             has content => (
93             is => 'rw',
94             isa => Str,
95             predicate => 'has_content',
96             clearer => 'clear_content',
97             );
98              
99             has server_tokens => (
100             is => 'ro',
101             isa => Bool,
102             default => sub {1},
103             );
104              
105             around content => sub {
106             my ( $orig, $self ) = ( shift, shift );
107              
108             # called as getter?
109             @_ or return $self->$orig;
110              
111             # No serializer defined; encode content
112             $self->serializer
113             or return $self->$orig( $self->encode_content(@_) );
114              
115             # serialize content
116             my $serialized = $self->serialize(@_);
117             $self->is_encoded(1); # All serializers return byte strings
118             return $self->$orig( defined $serialized ? $serialized : '' );
119             };
120              
121             has default_content_type => (
122             is => 'rw',
123             isa => Str,
124             default => sub {'text/html'},
125             );
126              
127             my ( $self, $content ) = @_;
128              
129             return $content if $self->is_encoded;
130              
131             # Apply default content type if none set.
132 533     533 1 1319 my $ct = $self->content_type ||
133             $self->content_type( $self->default_content_type );
134 533 100       7737  
135             return $content if $ct !~ /^text/;
136              
137 526   66     4550 # we don't want to encode an empty string, it will break the output
138             $content or return $content;
139              
140 526 50       7180 $self->content_type("$ct; charset=UTF-8")
141             if $ct !~ /charset/;
142              
143 526 100       3905 $self->is_encoded(1);
144             return Encode::encode( 'UTF-8', $content );
145 492 50       2718 }
146              
147             my ($self, $psgi_res) = @_;
148 492         7305  
149 492         15524 return Dancer2::Core::Response->new(
150             status => $psgi_res->status,
151             headers => $psgi_res->headers,
152             content => $psgi_res->body,
153 1     1 1 2824 );
154             }
155 1         4  
156             my ($self, $arrayref) = @_;
157              
158             return Dancer2::Core::Response->new(
159             status => $arrayref->[0],
160             headers => $arrayref->[1],
161             content => $arrayref->[2][0],
162             );
163 1     1 1 1747 }
164              
165 1         6 my ($self) = @_;
166              
167             $self->server_tokens
168             and $self->header( 'Server' => "Perl Dancer2 " . Dancer2->VERSION );
169              
170             my $headers = $self->headers;
171             my $status = $self->status;
172              
173 604     604 1 13311 Plack::Util::status_with_no_entity_body($status)
174             and return [ $status, $self->headers_to_array($headers), [] ];
175 604 100       4761  
176             my $content = $self->content;
177             # It is possible to have no content and/or no content type set
178 604         40749 # e.g. if all routes 'pass'. Set the default value for the content
179 604         12089 # (an empty string), allowing serializer hooks to be triggered
180             # as they may change the content..
181 604 100       15454 $content = $self->content('') if ! defined $content;
182              
183             if ( !$headers->header('Content-Length') &&
184 603         13576 !$headers->header('Transfer-Encoding') &&
185             defined( my $content_length = length $content ) ) {
186             $headers->push_header( 'Content-Length' => $content_length );
187             }
188              
189 603 100       6588 # More defaults
190             $self->content_type or $self->content_type($self->default_content_type);
191 603 100 66     2503 return [ $status, $self->headers_to_array($headers), [ $content ], ];
      66        
192             }
193              
194 576         31513 # sugar for accessing the content_type header, with mimetype care
195             my $self = shift;
196              
197             if ( scalar @_ > 0 ) {
198 603 100       13821 my $runner = Dancer2::runner();
199 603         26249 my $mimetype = $runner->mime_type->name_or_type(shift);
200             $self->header( 'Content-Type' => $mimetype );
201             return $mimetype;
202             }
203             else {
204 2238     2238 1 55038 return $self->header('Content-Type');
205             }
206 2238 100       4778 }
207 1107         3336  
208 1107         5285 has _forward => (
209 1107         18926 is => 'rw',
210 1107         64533 isa => HashRef,
211             );
212              
213 1131         16726 my ( $self, $uri, $params, $opts ) = @_;
214             $self->_forward( { to_url => $uri, params => $params, options => $opts } );
215             }
216              
217             my $self = shift;
218             $self->_forward;
219             }
220              
221             my ( $self, $destination, $status ) = @_;
222             $self->status( $status || 302 );
223 1     1 0 265  
224 1         26 # we want to stringify the $destination object (URI object)
225             $self->header( 'Location' => "$destination" );
226             }
227              
228 2     2 0 664 my $self = shift;
229 2         47  
230             my $error = Dancer2::Core::Error->new(
231             response => $self,
232             @_,
233 25     25 1 183 );
234 25   50     447  
235             $error->throw;
236             return $error;
237 25         955 }
238              
239             my ($self, $content) = @_;
240              
241 1     1 1 27 my $serializer = $self->serializer
242             or return;
243 1         20  
244             $content = $serializer->serialize($content)
245             or return;
246              
247             $self->content_type( $serializer->content_type );
248 1         5 return $content;
249 1         11 }
250              
251             1;
252              
253 77     77 1 149  
254             =pod
255 77 50       192  
256             =encoding UTF-8
257              
258 77 50       1265 =head1 NAME
259              
260             Dancer2::Core::Response - Response object for Dancer2
261 77         346  
262 77         162 =head1 VERSION
263              
264             version 0.400000
265              
266             =head1 ATTRIBUTES
267              
268             =head2 is_encoded
269              
270             Flag to tell if the content has already been encoded.
271              
272             =head2 is_halted
273              
274             Flag to tell whether or not the response should continue to be processed.
275              
276             =head2 status
277              
278             The HTTP status for the response.
279              
280             =head2 content
281              
282             The content for the response, stored as a string. If a reference is passed, the
283             response will try coerce it to a string via double quote interpolation.
284              
285             =head2 default_content_type
286              
287             Default mime type to use for the response Content-Type header
288             if nothing was specified
289              
290             =head2 headers
291              
292             The attribute that store the headers in a L<HTTP::Headers::Fast> object.
293              
294             That attribute coerces from ArrayRef and defaults to an empty L<HTTP::Headers::Fast>
295             instance.
296              
297             =head1 METHODS
298              
299             =head2 pass
300              
301             Set has_passed to true.
302              
303             =head2 serializer()
304              
305             Returns the optional serializer object used to deserialize request parameters
306              
307             =head2 halt
308              
309             Shortcut to halt the current response by setting the is_halted flag.
310              
311             =head2 encode_content
312              
313             Encodes the stored content according to the stored L<content_type>. If the content_type
314             is a text format C<^text>, then no encoding will take place.
315              
316             Internally, it uses the L<is_encoded> flag to make sure that content is not encoded twice.
317              
318             If it encodes the content, then it will return the encoded content. In all other
319             cases it returns C<false>.
320              
321             =head2 new_from_plack
322              
323             Creates a new response object from a L<Plack::Response> object.
324              
325             =head2 new_from_array
326              
327             Creates a new response object from a PSGI arrayref.
328              
329             =head2 to_psgi
330              
331             Converts the response object to a PSGI array.
332              
333             =head2 content_type($type)
334              
335             A little sugar for setting or accessing the content_type of the response, via the headers.
336              
337             =head2 redirect ($destination, $status)
338              
339             Sets a header in this response to give a redirect to $destination, and sets the
340             status to $status. If $status is omitted, or false, then it defaults to a status of
341             302.
342              
343             =head2 error( @args )
344              
345             $response->error( message => "oops" );
346              
347             Creates a L<Dancer2::Core::Error> object with the given I<@args> and I<throw()>
348             it against the response object. Returns the error object.
349              
350             =head2 serialize( $content )
351              
352             $response->serialize( $content );
353              
354             Serialize and return $content with the response's serializer.
355             set content-type accordingly.
356              
357             =head2 header($name)
358              
359             Return the value of the given header, if present. If the header has multiple
360             values, returns the list of values if called in list context, the first one
361             if in scalar context.
362              
363             =head2 push_header
364              
365             Add the header no matter if it already exists or not.
366              
367             $self->push_header( 'X-Wing' => '1' );
368              
369             It can also be called with multiple values to add many times the same header
370             with different values:
371              
372             $self->push_header( 'X-Wing' => 1, 2, 3 );
373              
374             =head2 headers_to_array($headers)
375              
376             Convert the C<$headers> to a PSGI ArrayRef.
377              
378             If no C<$headers> are provided, it will use the current response headers.
379              
380             =head1 AUTHOR
381              
382             Dancer Core Developers
383              
384             =head1 COPYRIGHT AND LICENSE
385              
386             This software is copyright (c) 2022 by Alexis Sukrieh.
387              
388             This is free software; you can redistribute it and/or modify it under
389             the same terms as the Perl 5 programming language system itself.
390              
391             =cut