File Coverage

blib/lib/Kelp/Response.pm
Criterion Covered Total %
statement 110 117 94.0
branch 26 30 86.6
condition 22 29 75.8
subroutine 31 33 93.9
pod 19 22 86.3
total 208 231 90.0


line stmt bran cond sub pod time code
1             package Kelp::Response;
2              
3 28     28   22231 use Kelp::Base 'Plack::Response';
  28         67  
  28         226  
4              
5 28     28   352 use Carp;
  28         68  
  28         2112  
6 28     28   338 use Try::Tiny;
  28         77  
  28         2617  
7 28     28   189 use Scalar::Util qw(blessed);
  28         65  
  28         1735  
8 28     28   2767 use HTTP::Status qw(status_message);
  28         26015  
  28         2330  
9 28     28   555 use Kelp::Util;
  28         64  
  28         56874  
10              
11             our @CARP_NOT = qw(Kelp);
12              
13             attr -app => sub { croak "app is required" };
14             attr charset => undef;
15             attr rendered => 0;
16             attr partial => 0;
17              
18             sub new
19             {
20 253     253 1 1067 my ($class, %args) = @_;
21 253         1521 my $self = $class->SUPER::new();
22 253         4068 $self->{$_} = $args{$_} for keys %args;
23 253         1254 return $self;
24             }
25              
26             sub set_content_type
27             {
28 246     246 1 618 my ($self, $type, $charset) = @_;
29 246         794 $self->content_type($type);
30 246 100       5331 $self->charset($charset) if $charset;
31 246         455 return $self;
32             }
33              
34             sub charset_encode
35             {
36 231     231 1 534 my ($self, $string) = @_;
37 231   100     629 return Kelp::Util::charset_encode(
38             Kelp::Util::effective_charset($self->charset) // $self->app->charset,
39             $string,
40             );
41             }
42              
43             sub _apply_charset
44             {
45 253     253   561 my ($self) = @_;
46 253         726 my $charset = $self->charset;
47 253 100       728 return unless $charset;
48              
49 232         614 my $ct = $self->content_type;
50              
51 232 50       7324 croak 'Cannot apply charset to response without content_type'
52             unless $ct;
53              
54             # content_type is actually an array, getting it in scalar context only
55             # yields the actual type without charset. It will be split after setting it
56             # like this
57 232         958 $self->content_type("$ct; charset=$charset");
58             }
59              
60             sub text
61             {
62 31     31 1 56 my $self = shift;
63 31   66     95 $self->set_content_type('text/plain', $self->charset || $self->app->charset);
64 31         117 return $self;
65             }
66              
67             sub html
68             {
69 177     177 1 6349 my $self = shift;
70 177   100     607 $self->set_content_type('text/html', $self->charset || $self->app->charset);
71 177         349 return $self;
72             }
73              
74             sub json
75             {
76 33     33 1 66 my $self = shift;
77 33   66     158 $self->set_content_type('application/json', $self->charset || $self->app->charset);
78 33         99 return $self;
79             }
80              
81             sub xml
82             {
83 2     2 1 7 my $self = shift;
84 2   66     11 $self->set_content_type('application/xml', $self->charset || $self->app->charset);
85 2         8 return $self;
86             }
87              
88             sub finalize
89             {
90 253     253 1 453 my $self = shift;
91              
92 253         781 $self->_apply_charset;
93              
94 253         6160 my $arr = $self->SUPER::finalize(@_);
95 253 100       29020 pop @$arr if $self->partial;
96 253         2749 return $arr;
97             }
98              
99             sub set_header
100             {
101 1     1 1 2 my $self = shift;
102 1         13 $self->SUPER::header(@_);
103 1         109 return $self;
104             }
105              
106             sub no_cache
107             {
108 0     0 1 0 my $self = shift;
109 0         0 $self->set_header('Cache-Control' => 'no-cache, no-store, must-revalidate');
110 0         0 $self->set_header('Pragma' => 'no-cache');
111 0         0 $self->set_header('Expires' => '0');
112 0         0 return $self;
113             }
114              
115             sub set_code
116             {
117 234     234 1 2028 my $self = shift;
118 234         879 $self->SUPER::code(@_);
119 234         2027 return $self;
120             }
121              
122             sub render
123             {
124 236     236 1 672 my ($self, $body) = @_;
125              
126 236 100       739 my $method = ref $body ? '_render_ref' : '_render_nonref';
127 236         842 $body = $self->$method($body);
128              
129             # Set code 200 if the code has not been set
130 231 100       1077 $self->set_code(200) unless $self->code;
131              
132 231         1229 $self->body($self->charset_encode($body));
133 231         9068 $self->rendered(1);
134 231         825 return $self;
135             }
136              
137             # override to change how references are serialized
138             sub _render_ref
139             {
140 27     27   99 my ($self, $body) = @_;
141 27         150 my $ct = $self->content_type;
142              
143 27 50 66     1208 if (!$ct || $ct =~ m{^application/json}i) {
144 27 100       149 $self->json if !$ct;
145 27         91 return $self->app->get_encoder(json => 'internal')->encode($body);
146             }
147             else {
148 0         0 croak "Don't know how to handle reference for $ct in response (forgot to serialize?)";
149             }
150             }
151              
152             # override to change how non-references are handled
153             sub _render_nonref
154             {
155 209     209   494 my ($self, $body) = @_;
156 209 100       804 $self->html if !$self->content_type;
157              
158 209         1441 return $body;
159             }
160              
161             sub render_binary
162             {
163 2     2 1 8 my ($self, $body) = @_;
164 2   50     8 $body //= '';
165              
166             # Set code 200 if the code has not been set
167 2 50       12 $self->set_code(200) unless $self->code;
168              
169 2 100       9 if (!$self->content_type) {
170 1         288 croak "Content-type must be explicitly set for binaries";
171             }
172              
173 1         43 $self->body($body);
174 1         8 $self->rendered(1);
175 1         4 return $self;
176             }
177              
178             sub render_error
179             {
180 65     65 1 160 my ($self, $code, $error) = @_;
181              
182 65   100     216 $code //= 500;
183 65   50     262 $error //= status_message($code) // 'Error';
      66        
184              
185 65         462 $self->set_code($code);
186              
187             # Look for a template and if not found, then show a generic text
188             try {
189 65     65   2550 local $SIG{__DIE__}; # Silence StackTrace
190 65         334 $self->template(
191             "error/$code", {
192             error => $error
193             }
194             );
195             }
196             catch {
197 23     23   3920 $self->text->render("$code - $error");
198 65         559 };
199              
200 65         1119 return $self;
201             }
202              
203             sub render_exception
204             {
205 10     10 0 29 my ($self, $exception) = @_;
206              
207             # If the error is 500, do the same thing normal errors do: provide more
208             # info on non-production
209 10 100       19 return $self->render_500($exception->body)
210             if $exception->code == 500;
211              
212 8         13 return $self->render_error($exception->code);
213             }
214              
215             sub render_401
216             {
217 0     0 0 0 $_[0]->render_error(401);
218             }
219              
220             sub render_403
221             {
222 2     2 0 13 $_[0]->render_error(403);
223             }
224              
225             sub render_404
226             {
227 15     15 1 71 $_[0]->render_error(404);
228             }
229              
230             sub render_500
231             {
232 36     36 1 199 my ($self, $error) = @_;
233              
234             # Do not leak information on production!
235 36 100       99 if ($self->app->is_production) {
236 14         1346 return $self->render_error;
237             }
238              
239             # if render_500 gets blessed object as error, stringify it
240 22 100       175 $error = "$error" if blessed $error;
241 22   100     111 $error //= 'No error, something is wrong';
242              
243             # at this point, error will never be in HTML, since the exception body
244             # would have to be HTML itself. Try to nest it inside a template. NOTE: We
245             # don't currently handle ref errors here which aren't objects
246 22         74 return $self->render_error(500, $error);
247             }
248              
249             sub redirect
250             {
251 7     7 1 13 my $self = shift;
252 7         40 $self->rendered(1);
253 7         48 $self->SUPER::redirect(@_);
254             }
255              
256             sub redirect_to
257             {
258 7     7 1 22 my ($self, $where, $args, $code) = @_;
259 7         1859 my $url = $self->app->url_for($where, %$args);
260 7         30 $self->redirect($url, $code);
261             }
262              
263             sub template
264             {
265 71     71 1 164 my ($self, $template, $vars, @rest) = @_;
266              
267             # Do we have a template module loaded?
268 71 50       216 croak "No template module loaded"
269             unless $self->app->can('template');
270              
271             # run template in current controller context
272 71         170 my $output = $self->app->context->run_method('template', $template, $vars, @rest);
273 48         1869 $self->render($output);
274             }
275              
276             1;
277              
278             __END__
279              
280             =head1 NAME
281              
282             Kelp::Response - Format an HTTP response
283              
284             =head1 SYNOPSIS
285              
286             Examples of how to use this module make a lot more sense when shown inside
287             route definitions. Note that in the below examples C<$self-E<gt>res>
288             is an instance of C<Kelp::Response>:
289              
290             # Render simple text
291             sub text {
292             my $self = shift;
293             $self->res->text->render("It works!");
294             }
295              
296             # Render advanced HTML
297             sub html {
298             my $self = shift;
299             $self->res->html->render("<h1>It works!</h1>");
300             }
301              
302             # Render a mysterious JSON structure
303             sub json {
304             my $self = shift;
305             $self->res->json->render({ why => 'no' });
306             }
307              
308             # Render the stock 404
309             sub missing {
310             my $self = shift;
311             $self->res->render_404;
312             }
313              
314             # Render a template
315             sub view {
316             my $self = shift;
317             $self->res->template('view.tt', { name => 'Rick James' });
318             }
319              
320             =head1 DESCRIPTION
321              
322             The L<PSGI> specification requires that each route returns an array with status
323             code, headers and body. L<Plack::Response> already provides many useful methods
324             that deal with that. This module extends C<Plack::Response> to add the tools we
325             need to write graceful PSGI compliant responses. Some methods return C<$self>,
326             which makes them easy to chain.
327              
328             =head1 ATTRIBUTES
329              
330             =head2 app
331              
332             A reference to the Kelp application. This will always be the real application,
333             not the reblessed controller.
334              
335             =head2 charset
336              
337             The charset to be used in response. Will be glued to C<Content-Type> header
338             just before the response is finalized.
339              
340             NOTE: charset will be glued regardless of it having any sense with a given
341             C<Content-Type>, and will override any charset set explicitly through
342             L</set_content_type> - use with caution.
343              
344             =head2 rendered
345              
346             Tells if the response has been rendered. This attribute is used internally and
347             unless you know what you're doing, we recommend that you do not use it.
348              
349             =head2 partial
350              
351             Sets partial response. If this attribute is set to a true value, it will cause
352             C<finalize> to return the HTTP status code and headers, but not the body. This is
353             convenient if you intend to stream your content. In the following example, we
354             set C<partial> to 1 and use C<finalize> to get a C<writer> object for streaming.
355              
356             sub stream {
357             my $self = shift;
358             return sub {
359             my $responder = shift;
360              
361             # Stream JSON
362             $self->res->set_code(200)->json->partial(1);
363              
364             # finalize will now return only the status code and headers
365             my $writer = $responder->($self->res->finalize);
366              
367             # Stream JSON body using the writer object
368             for (1 .. 30) {
369             $writer->write(qq|{"id":$_}\n|);
370             sleep 1;
371             }
372              
373             # Close the writer
374             $writer->close;
375             };
376             }
377              
378             For more information on how to stream, see the
379             L<PSGI/Delayed-Response-and-Streaming-Body> docs.
380              
381             =head1 METHODS
382              
383             =head2 render
384              
385             This method tries to act smart, without being a control freak. It will fill out
386             the blanks, unless they were previously filled out by someone else. Here is what
387             is does:
388              
389             =over
390              
391             =item
392              
393             If the response code was not previously set, this method will set it to 200.
394              
395             =item
396              
397             If no content-type is previously set, C<render> will set is based on the type
398             of the data rendered. If it's a reference, then the content-type will be set to
399             C<application/json>, otherwise it will be set to C<text/html>.
400              
401             # Will set the content-type to json
402             $res->render({ numbers => [1, 2, 3] });
403              
404             =item
405              
406             Last, the data will be encoded with the charset from L</charset> or the one
407             specified by the app - see L<Kelp/charset>. Any string you pass here should not
408             already be encoded, unless your application has its charset set to undef.
409              
410             =back
411              
412             =head2 set_content_type
413              
414             Sets the content type of the response and returns C<$self>.
415              
416             # Inside a route definition
417             $self->res->set_content_type('image/png');
418              
419             An optional second argument can be passed, which will be used for C<charset>
420             part of C<Content-Type> (will set L</charset> field).
421              
422             =head2 text, html, json, xml
423              
424             These methods are shortcuts for C<set_content_type> with the corresponding type.
425             All of them set the content-type header and return C<$self> so they can be
426             chained.
427              
428             $self->res->text->render("word");
429             $self->res->html->render("<p>word</p>");
430             $self->res->json->render({ word => \1 });
431              
432             NOTE: These methods will also call L</charset> and set it to application's
433             charset (unless it was previously set).
434              
435             =head2 set_header
436              
437             Sets response headers. This is a wrapper around L<Plack::Response/header>, which
438             returns C<$self> to allow for chaining.
439              
440             $self->res->set_header('X-Something' => 'Value')->text->render("Hello");
441              
442             =head2 no_cache
443              
444             A convenience method that sets several response headers instructing most
445             browsers to not cache the response.
446              
447             $self->res->no_cache->json->render({ epoch => time });
448              
449             The above response will contain headers that disable caching.
450              
451             =head2 set_code
452              
453             Set the response code.
454              
455             $self->res->set_code(401)->render("Access denied");
456              
457             =head2 render_binary
458              
459             Render binary data such as byte streams, files, images, etc. You must
460             explicitly set the content_type before that. Will not encode the content into
461             any charset.
462              
463             use Kelp::Less;
464              
465             get '/image/:name' => sub {
466             my $content = Path::Tiny::path("$name.jpg")->slurp_raw;
467             res->set_content_type('image/jpeg')->render_binary($content);
468              
469             # the same, but probably more effective way (PSGI-server dependent)
470             open my $handle, "<:raw", "$name.png"
471             or die "cannot open $name: $!";
472             res->set_content_type('image/png')->render_binary($handle);
473             };
474              
475             =head2 render_error
476              
477             $self->render_error($code, $error)
478              
479             Renders the specified return code and an error message. This sub will first look
480             for this error template C<error/$code>, before displaying a plain page with the
481             error text.
482              
483             $self->res->render_error(510, "Not Extended");
484              
485             The above code will look for a template named C<views/errors/510.tt>, and if not
486             found it will render this message:
487              
488             510 - Not Extended
489              
490             A return code of 510 will also be set.
491              
492             If a standard error message is to be used, it may be skipped - will be pulled
493             from L<HTTP::Status>.
494              
495             =head2 render_404
496              
497             A convenience method that sets code 404 and returns "File Not Found".
498              
499             sub some_route {
500             if ( not $self->req->param('ok') ) {
501             return $self->res->render_404;
502             }
503             }
504              
505             If your application's tone is overly friendly or humorous, you will want to create a
506             custom 404 page. The best way to do this is to design your own C<404.tt> template and
507             put it in the C<views/error>.
508              
509             =head2 render_500
510              
511             $self->render_500($optional_error)
512              
513             Renders the 500 error page. Designing your own 500 page is possible by adding file
514             C<500.tt> in C<views/error>.
515              
516             Keep in mind C<$optional_error> will not show in C<deployment> mode, and
517             instead stock error message will be displayed.
518              
519             =head2 redirect_to
520              
521             Redirects the client to a named route or to a given url. In case the route is passed by
522             name, a hash reference with the needed arguments can be passed after the route's name.
523             As a third optional argument, you can enter the desired response code:
524              
525             $self->redirect_to( '/example' );
526             $self->redirect_to( 'catalogue' );
527             $self->redirect_to( 'catalogue', { id => 243 });
528             $self->redirect_to( 'other', {}, 303 );
529              
530             This method attempts to build the Kelp route by name, so if you want to just
531             redirect to an url it's better to use L<Plack::Response/redirect>.
532              
533             =head2 template
534              
535             This method renders a template. The template should be previously configured by
536             you and included via a module. See L<Kelp::Module::Template> for a template
537             module.
538              
539             sub some_route {
540             my $self = shift;
541             $self->res->template('home.tt', { login => 'user' });
542             }
543              
544             =head2 charset_encode
545              
546             Shortcut method, which encodes a string using the L</charset> or L<Kelp/charset>.
547