File Coverage

blib/lib/Kelp/Response.pm
Criterion Covered Total %
statement 100 106 94.3
branch 28 32 87.5
condition 23 26 88.4
subroutine 26 28 92.8
pod 18 21 85.7
total 195 213 91.5


line stmt bran cond sub pod time code
1              
2             use Kelp::Base 'Plack::Response';
3 22     22   564  
  22         40  
  22         189  
4             use Encode;
5 22     22   1360 use Carp;
  22         48  
  22         1372  
6 22     22   131 use Try::Tiny;
  22         40  
  22         1009  
7 22     22   123 use Scalar::Util;
  22         67  
  22         988  
8 22     22   136  
  22         53  
  22         29532  
9             attr -app => sub { croak "app is required" };
10             attr rendered => 0;
11             attr partial => 0;
12              
13             my ( $class, %args ) = @_;
14             my $self = $class->SUPER::new();
15 203     203 1 560 $self->{$_} = $args{$_} for keys %args;
16 203         609 return $self;
17 203         2351 }
18 203         709  
19             $_[0]->content_type( $_[1] );
20             return $_[0];
21             }
22 193     193 1 488  
23 193         2586 $_[0]->set_content_type( 'text/plain; charset=' . $_[0]->app->charset );
24             }
25              
26             $_[0]->set_content_type( 'text/html; charset=' . $_[0]->app->charset );
27 5     5 1 20 }
28              
29             $_[0]->set_content_type('application/json');
30             }
31 156     156 1 388  
32             $_[0]->set_content_type('application/xml');
33             }
34              
35 29     29 1 77 my $self = shift;
36             my $arr = $self->SUPER::finalize(@_);
37             pop @$arr if $self->partial;
38             return $arr;
39 1     1 1 3 }
40              
41             my $self = shift;
42             $self->SUPER::header(@_);
43 200     200 1 287 return $self;
44 200         488 }
45 200 100       15987  
46 200         1245 my $self = shift;
47             $self->set_header( 'Cache-Control' => 'no-cache, no-store, must-revalidate' );
48             $self->set_header( 'Pragma' => 'no-cache' );
49             $self->set_header( 'Expires' => '0' );
50 1     1 1 3 return $self;
51 1         6 }
52 1         60  
53             my $self = shift;
54             $self->SUPER::code(@_);
55             return $self;
56 0     0 1 0 }
57 0         0  
58 0         0 my $self = shift;
59 0         0 my $body = shift // '';
60 0         0  
61             # Set code 200 if the code has not been set
62             $self->set_code(200) unless $self->code;
63              
64 203     203 1 1064 my $is_json = $self->content_type eq 'application/json';
65 203         562 my $guess_json = !$self->content_type && ref($body);
66 203         1285 my $guess_html = !$self->content_type && !ref($body);
67              
68             # If the content has been determined as JSON, then encode it
69             if ( $is_json || $guess_json ) {
70 192     192 1 296 die "No JSON decoder" unless $self->app->can('json');
71 192   100     382 die "Data must be a reference" unless ref($body);
72             my $json = $self->app->json;
73             $body = $json->encode($body);
74 192 100       457 $body = encode($self->app->charset, $body) unless $json->get_utf8;
75             $self->json if $guess_json;
76 192         730 $self->body( $body );
77 192   100     4373 } else {
78 192   100     2739 $self->html if $guess_html;
79             $self->body( encode( $self->app->charset, $body ) );
80             }
81 192 100 100     2789  
82 31 50       78 $self->rendered(1);
83 31 100       94 return $self;
84 27         52 }
85 27         262  
86 24 50       83 my $self = shift;
87 24 100       77 my $body = shift // '';
88 24         53  
89             # Set code 200 if the code has not been set
90 161 100       478 $self->set_code(200) unless $self->code;
91 161         337  
92             if ( !$self->content_type ) {
93             die "Content-type must be explicitly set for binaries";
94 185         8871 }
95 185         579  
96             $self->body($body);
97             $self->rendered(1);
98             return $self;
99 2     2 1 4 }
100 2   50     5  
101             my ( $self, $code, $error ) = @_;
102              
103 2 50       6 $code //= 500;
104             $error //= "Internal Server Error";
105 2 100       8  
106 1         38 $self->set_code($code);
107              
108             # Look for a template and if not found, then show a generic text
109 1         24 try {
110 1         7 local $SIG{__DIE__}; # Silence StackTrace
111 1         2 my $filename = "error/$code";
112             $self->template(
113             $filename, {
114             app => $self->app,
115 32     32 1 54 error => $error
116             }
117 32   100     86 );
118 32   100     75 }
119             catch {
120 32         65 $self->render("$code - $error");
121             };
122              
123             return $self;
124 32     32   1130 }
125 32         84  
126 32         82 my ( $self, $exception ) = @_;
127              
128             my $code = $exception->code;
129             my $body = $exception->body;
130              
131             $self->set_code($code);
132             if ( defined $body ) {
133             my $is_html = $self->content_type =~ m{^text/html};
134 14     14   5979 my $guess_html = !$self->content_type && !ref($body);
135 32         200  
136             if ( $is_html || $guess_html ) {
137 32         374 $self->render_error($code, $body);
138             }
139             else {
140             $self->render($body);
141 12     12 0 30 }
142              
143 12         38 }
144 12         38 elsif ( $self->content_type ) {
145             $self->content_type('');
146 12         44 }
147 12 100       33 }
    100          
148 8         28  
149 8   66     266 $_[0]->render_error( 404, "File Not Found" );
150             }
151 8 100 66     132  
152 4         14 my ( $self, $error ) = @_;
153              
154             if ( !defined $error || $self->app->mode eq 'deployment' ) {
155 4         14 return $self->render_error;
156             }
157              
158             # if render_500 gets blessed object as error stringify it
159             $error = ref $error if Scalar::Util::blessed $error;
160 2         55  
161             return $self->set_code(500)->render($error);
162             }
163              
164             $_[0]->render_error( 401, "Unauthorized" );
165 10     10 1 23 }
166              
167             $_[0]->render_error( 403, "Forbidden" );
168             }
169 37     37 1 2106  
170             my $self = shift;
171 37 100 100     133 $self->rendered(1);
172 12         27 $self->SUPER::redirect(@_);
173             }
174              
175             my ( $self, $where, $args, $code ) = @_;
176 25 100       66 my $url = $self->app->url_for($where, %$args);
177             $self->redirect( $url, $code );
178 25         49 }
179              
180             my ( $self, $template, $vars, @rest ) = @_;
181              
182 0     0 0 0 # Add the app object for convenience
183             $vars->{app} = $self->app;
184              
185             # Do we have a template module loaded?
186 2     2 0 4 die "No template module loaded"
187             unless $self->app->can('template');
188              
189             my $output = $self->app->template( $template, $vars, @rest );
190 7     7 1 8 $self->render($output);
191 7         16 }
192 7         27  
193             1;
194              
195              
196 7     7 1 15 =pod
197 7         15  
198 7         18 =head1 NAME
199              
200             Kelp::Response - Format an HTTP response
201              
202 36     36 1 68 =head1 SYNOPSIS
203              
204             Examples of how to use this module make a lot more sense when shown inside
205 36         72 route definitions. Note that in the below examples C<$self-E<gt>res>
206             is an instance of C<Kelp::Response>:
207              
208 36 50       74 # Render simple text
209             sub text {
210             my $self = shift;
211 36         131 $self->res->text->render("It works!");
212 22         61 }
213              
214             # Render advanced HTML
215             sub html {
216             my $self = shift;
217             $self->res->html->render("<h1>It works!</h1>");
218             }
219              
220             # Render a mysterious JSON structure
221             sub json {
222             my $self = shift;
223             $self->res->json->render({ why => 'no' });
224             }
225              
226             # Render the stock 404
227             sub missing {
228             my $self = shift;
229             $self->res->render_404;
230             }
231              
232             # Render a template
233             sub view {
234             my $self = shift;
235             $self->res->template('view.tt', { name => 'Rick James' } );
236             }
237              
238             =head1 DESCRIPTION
239              
240             The L<PSGI> specification requires that each route returns an array with status
241             code, headers and body. C<Plack::Response> already provides many useful methods
242             that deal with that. This module extends C<Plack::Response> to add the tools we
243             need to write graceful PSGI compliant responses. Some methods return C<$self>,
244             which makes them easy to chain.
245              
246             =head1 ATTRIBUTES
247              
248             =head2 rendered
249              
250             Tells if the response has been rendered. This attribute is used internally and
251             unless you know what you're doing, we recommend that you do not use it.
252              
253             =head2 partial
254              
255             Sets partial response. If this attribute is set to a true value, it will cause
256             C<finalize> to return the HTTP status code and headers, but not the body. This is
257             convenient if you intend to stream your content. In the following example, we
258             set C<partial> to 1 and use C<finalize> to get a C<writer> object for streaming.
259              
260             sub stream {
261             my $self = shift;
262             return sub {
263             my $responder = shift;
264              
265             # Stream JSON
266             $self->res->set_code(200)->json->partial(1);
267              
268             # finalize will now return only the status code and headers
269             my $writer = $responder->( $self->res->finalize );
270              
271             # Stream JSON body using the writer object
272             for ( 1 .. 30 ) {
273             $writer->write(qq|{"id":$_}\n|);
274             sleep 1;
275             }
276              
277             # Close the writer
278             $writer->close;
279             };
280             }
281              
282             For more information on how to stream, see the
283             L<PSGI/Delayed-Response-and-Streaming-Body> docs.
284              
285             =head1 METHODS
286              
287             =head2 render
288              
289             This method tries to act smart, without being a control freak. It will fill out
290             the blanks, unless they were previously filled out by someone else. Here is what
291             is does:
292              
293             =over
294              
295             =item
296              
297             If the response code was not previously set, this method will set it to 200.
298              
299             =item
300              
301             If no content-type is previously set, C<render> will set is based on the type of
302             the data rendered. If it's a reference, then the content-type will be set to
303             C<application/json>, otherwise it will be set to C<text/html>.
304              
305             # Will set the content-type to json
306             $res->render( { numbers => [ 1, 2, 3 ] } );
307              
308             =item
309              
310             Last, the data will be encoded with the charset specified by the app.
311              
312             =back
313              
314             =head2 set_content_type
315              
316             Sets the content type of the response and returns C<$self>.
317              
318             # Inside a route definition
319             $self->res->set_content_type('image/png');
320              
321             =head2 text, html, json, xml
322              
323             These methods are shortcuts for C<set_content_type> with the corresponding type.
324             All of them set the content-type header and return C<$self> so they can be
325             chained.
326              
327             $self->res->text->render("word");
328             $self->res->html->render("<p>word</p>");
329             $self->res->json->render({ word => \1 });
330              
331             =head2 set_header
332              
333             Sets response headers. This is a wrapper around L<Plack::Response/header>, which
334             returns C<$self> to allow for chaining.
335              
336             $self->res->set_header('X-Something' => 'Value')->text->render("Hello");
337              
338             =head2 no_cache
339              
340             A convenience method that sets several response headers instructing most
341             browsers to not cache the response.
342              
343             $self->res->no_cache->json->render({ epoch => time });
344              
345             The above response will contain headers that disable caching.
346              
347             =head2 set_code
348              
349             Set the response code.
350              
351             $self->res->set_code(401)->render("Access denied");
352              
353             =head2 render_binary
354              
355             Render binary files, such as images, etc. You must explicitly set the content_type
356             before that.
357              
358             use Kelp::Less;
359              
360             get '/image/:name' => sub {
361             my $content = Path::Tiny::path("$name.jpg")->slurp_raw;
362             res->set_content_type('image/jpeg')->render_binary( $content );
363              
364             # the same, but probably more effective way (PSGI-server dependent)
365             open( my $handle, "<:raw", "$name.png" )
366             or die("cannot open $name: $!");
367             res->set_content_type('image/png')->render_binary( $handle );
368             };
369              
370             =head2 render_error
371              
372             C<render_error( $code, $error )>
373              
374             Renders the specified return code and an error message. This sub will first look
375             for this error template C<error/$code>, before displaying a plain page with the
376             error text.
377              
378             $self->res->render_error(510, "Not Extended");
379              
380             The above code will look for a template named C<views/errors/510.tt>, and if not
381             found it will render this message:
382              
383             510 - Not Extended
384              
385             A return code of 510 will also be set.
386              
387             =head2 render_404
388              
389             A convenience method that sets code 404 and returns "File Not Found".
390              
391             sub some_route {
392             if ( not $self->req->param('ok') ) {
393             return $self->res->render_404;
394             }
395             }
396              
397             If your application's tone is overly friendly or humorous, you will want to create a
398             custom 404 page. The best way to do this is to design your own 404.tt template and
399             put it in the views/error folder.
400              
401             =head2 render_500
402              
403             C<render_500($optional_error)>
404              
405             Renders the stock "500 - Server Error" message.
406             Designing your own 500 page is also possible. All you need to do is add file 500.tt in
407             views/error. Keep in mind that it will only show in C<deployment>. In any other mode,
408             this method will display the optional error, or the stock error message.
409              
410             =head2 redirect_to
411              
412             Redirects the client to a named route or to a given url. In case the route is passed by
413             name, a hash reference with the needed arguments can be passed after the route's name.
414             As a third optional argument, you can enter the desired response code:
415              
416             $self->redirect_to( '/example' );
417             $self->redirect_to( 'catalogue' );
418             $self->redirect_to( 'catalogue', { id => 243 });
419             $self->redirect_to( 'other', {}, 303 );
420              
421             =head2 template
422              
423             This method renders a template. The template should be previously configured by
424             you and included via a module. See L<Kelp::Module::Template> for a template
425             module.
426              
427             sub some_route {
428             my $self = shift;
429             $self->res->template('home.tt', { login => 'user' });
430             }
431              
432             =cut