File Coverage

blib/lib/Dancer/Renderer.pm
Criterion Covered Total %
statement 173 179 96.6
branch 35 40 87.5
condition 33 56 58.9
subroutine 37 38 97.3
pod 0 11 0.0
total 278 324 85.8


line stmt bran cond sub pod time code
1             package Dancer::Renderer;
2             our $AUTHORITY = 'cpan:SUKRIA';
3             # ABSTRACT: Rendering class for Dancer
4             $Dancer::Renderer::VERSION = '1.3514_04'; # TRIAL
5             $Dancer::Renderer::VERSION = '1.351404';
6 169     169   1442 use strict;
  169         285  
  169         4062  
7 169     169   715 use warnings;
  169         351  
  169         3181  
8 169     169   685 use Carp;
  169         290  
  169         7508  
9 169     169   993 use HTTP::Headers;
  169         324  
  169         4665  
10 169     169   60717 use HTTP::Date qw( str2time time2str );
  169         540097  
  169         9820  
11 169     169   2621 use Dancer::Route;
  169         322  
  169         3028  
12 169     169   737 use Dancer::HTTP;
  169         295  
  169         4657  
13 169     169   771 use Dancer::Cookie;
  169         310  
  169         2730  
14 169     169   684 use Dancer::Factory::Hook;
  169         311  
  169         2897  
15 169     169   747 use Dancer::Cookies;
  169         299  
  169         3301  
16 169     169   765 use Dancer::Request;
  169         329  
  169         3113  
17 169     169   854 use Dancer::Response;
  169         302  
  169         2769  
18 169     169   64669 use Dancer::Serializer;
  169         382  
  169         4643  
19 169     169   987 use Dancer::Config 'setting';
  169         326  
  169         6933  
20 169     169   908 use Dancer::FileUtils qw(path path_or_empty dirname read_file_content open_file);
  169         306  
  169         7918  
21 169     169   842 use Dancer::SharedData;
  169         304  
  169         2585  
22 169     169   723 use Dancer::Logger;
  169         324  
  169         3133  
23 169     169   812 use Dancer::MIME;
  169         360  
  169         5176  
24 169     169   802 use Dancer::Exception qw(:all);
  169         367  
  169         269442  
25              
26             Dancer::Factory::Hook->instance->install_hooks(
27             qw/before after before_serializer after_serializer before_file_render after_file_render/
28             );
29              
30 520     520 0 1256 sub render_file { get_file_response() }
31              
32             sub render_action {
33 515     515 0 1043 my $class = shift;
34 515         1376 my $resp = $class->get_action_response();
35 470 100       1647 return (defined $resp)
36             ? response_with_headers()
37             : undef;
38             }
39              
40             sub render_error {
41 32     32 0 78 my ($class, $error_code) = @_;
42              
43 32         122 my $app = Dancer::App->current;
44 32         102 my $static_file = path($app->setting('public'), "$error_code.html");
45 32         118 my $response = Dancer::Renderer->get_file_response_for_path(
46             $static_file => $error_code);
47 32 100       96 return $response if $response;
48              
49 30         200 return Dancer::Response->new(
50             status => $error_code,
51             headers => ['Content-Type' => 'text/html'],
52             content => Dancer::Renderer->html_page(
53             "Error $error_code" => "

Unable to process your query

"
54             . "The page you requested is not available"
55             )
56             );
57             }
58              
59             # Takes a response object and add default headers
60             sub response_with_headers {
61 451     451 0 1070 my $response = Dancer::SharedData->response();
62              
63 451 50       1118 if (Dancer::Config::setting('server_tokens')) {
64 451   33     1139 $response->{headers} ||= HTTP::Headers->new;
65 451   50     5045 my $powered_by = "Perl Dancer " . ( Dancer->VERSION || '' );
66 451         1957 $response->header('X-Powered-By' => $powered_by);
67 451         23380 $response->header('Server' => $powered_by);
68             }
69              
70 451         16425 return $response;
71             }
72              
73             sub html_page {
74 39     39 0 114 my ($class, $title, $content, $style) = @_;
75 39   100     164 $style ||= 'style';
76              
77 39         112 my $template = $class->templates->{'default'};
78 39         343 my $ts = Dancer::Template::Simple->new;
79              
80 39         226 return $ts->render(
81             \$template,
82             { title => $title,
83             style => $style,
84             version => $Dancer::VERSION,
85             content => $content
86             }
87             );
88             }
89              
90             sub get_action_response {
91 539     539 0 816 my $class = shift;
92 539   100     1658 my $depth = shift || 1;
93              
94             # save the request before the filters are ran
95 539         1428 my $request = Dancer::SharedData->request;
96 539         1582 my ($method, $path) = ($request->method, $request->path_info);
97              
98             # look for a matching route handler, for the given request
99 539         1425 my $handler =
100             Dancer::App->find_route_through_apps(Dancer::SharedData->request);
101              
102 539 100 66     2082 my $app = ($handler && $handler->app) ? $handler->app : Dancer::App->current();
103              
104             # run the before filters, before "running" the route handler
105 539         1719 Dancer::Factory::Hook->instance->execute_hooks('before', $handler);
106              
107             # recurse if something has changed
108 533         1756 my $MAX_RECURSIVE_LOOP = 10;
109 533 100 66     1521 if ( ($path ne Dancer::SharedData->request->path_info)
110             || ($method ne Dancer::SharedData->request->method))
111             {
112 26 100       55 if ($depth > $MAX_RECURSIVE_LOOP) {
113 2         8 raise core_renderer => "infinite loop detected, "
114             . "check your route/filters for "
115             . $method . ' '
116             . $path;
117             }
118 24         91 return $class->get_action_response($depth + 1);
119             }
120              
121             # redirect immediately - skip route execution
122 507         1563 my $response = Dancer::SharedData->response();
123 507 50 33     1943 if (defined $response && (my $status = $response->status)) {
124 507 100 66     1976 if ($status == 302 || $status == 301) {
125 6         28 $class->serialize_response_if_needed();
126 6         31 Dancer::Factory::Hook->instance->execute_hooks('after', $response);
127 6         21 return $response;
128             }
129             }
130              
131             # execute the action
132 501 100       1169 if ($handler) {
133             # a response may exist, produced by a before filter
134 482 50 33     1790 return $class->serialize_response_if_needed() if defined $response && $response->exists;
135             # else, get the route handler's response
136 482         1651 Dancer::App->current($handler->{app});
137             try {
138 482     482   14292 $handler->run($request);
139 445         1376 $class->serialize_response_if_needed();
140             } continuation {
141 9     9   19 my ($continuation) = @_;
142             # If we have a Route continuation, run the after hook, then
143             # propagate the continuation
144 9         31 my $resp = Dancer::SharedData->response();
145 9         28 Dancer::Factory::Hook->instance->execute_hooks('after', $resp);
146 9         24 $continuation->rethrow();
147 482         3878 };
148 445         6732 my $resp = Dancer::SharedData->response();
149 445         1354 Dancer::Factory::Hook->instance->execute_hooks('after', $resp);
150 445         2516 return $resp;
151             }
152             else {
153 19         50 return undef; # 404
154             }
155             }
156              
157             sub render_autopage {
158 19 100   19 0 61 return unless Dancer::setting('auto_page');
159              
160 8         21 my $request = Dancer::SharedData->request;
161 8         17 my $path = $request->path_info;
162              
163             # See if we find a matching view for this request, if so, render it
164 8         11 my $viewpath = $path;
165 8         34 $viewpath =~ s{^/}{};
166 8   100     20 my $view = Dancer::engine('template')->view($viewpath) || '';
167              
168 8 100 66     69 if ($view && -f $view) {
169             # A view exists for the path requested, go ahead and render it:
170 5         13 return _autopage_response($viewpath);
171             }
172              
173             # Try appending "index" and looking again
174 3   100     9 $view = Dancer::engine('template')->view(
175             Dancer::FileUtils::path($viewpath, 'index')
176             )|| '';
177 3         19 Dancer::error("Looking for $viewpath/index - got $view");
178 3 100 66     33 if ($view && -f $view) {
179 2         9 return _autopage_response(
180             Dancer::FileUtils::path($viewpath, 'index')
181             );
182             }
183              
184 1         7 return;
185             }
186             sub _autopage_response {
187 7     7   12 my $viewpath = shift;
188 7         23 my $response = Dancer::Response->new;
189 7         18 $response->status(200);
190 7         16 $response->content(
191             Dancer::template($viewpath)
192             );
193 7         21 $response->header( 'Content-Type' => 'text/html' );
194 7         291 return $response;
195             }
196              
197             sub serialize_response_if_needed {
198 451     451 0 1009 my $response = Dancer::SharedData->response();
199              
200 451 100 66     1310 if (Dancer::App->current->setting('serializer') && $response->content()){
201 30         118 Dancer::Factory::Hook->execute_hooks('before_serializer', $response);
202             try {
203 30     30   847 Dancer::Serializer->process_response($response);
204             } continuation {
205 0     0   0 my ($continuation) = @_;
206             # If we have a Route continuation, run the after hook, then
207             # propagate the continuation
208 0         0 Dancer::Factory::Hook->execute_hooks('after_serializer', $response);
209 0         0 $continuation->rethrow();
210 30         216 };
211 30         508 Dancer::Factory::Hook->execute_hooks('after_serializer', $response);
212             }
213 451         2419 return $response;
214             }
215              
216             sub get_file_response {
217 533     533 0 1388 my $request = Dancer::SharedData->request;
218 533         1360 my $path_info = $request->path_info;
219              
220             # requests that have \0 in path are forbidden
221 533 100       2164 if ( $path_info =~ /\0/ ) {
222 1         5 _bad_request();
223 1         18 return 1;
224             }
225              
226 532         1396 my $app = Dancer::App->current;
227             # TODO: this should be later removed with a check whether the file exists
228             # and then returning a 404
229 532 50       1245 my $public = defined $app->setting('public') ?
230             $app->setting('public') :
231             '';
232              
233 532         1891 my $static_file = path( $public, $path_info );
234              
235 532 100 50     2140 return if ( !$static_file
      66        
236             || index( $static_file, ( path($public) || '' ) ) != 0 );
237              
238 530         1675 return Dancer::Renderer->get_file_response_for_path( $static_file, undef,
239             $request->content_type );
240             }
241              
242             sub get_file_response_for_path {
243 564     564 0 1344 my ($class, $static_file, $status, $mime) = @_;
244              
245 564 100       13184 if ( -f $static_file ) {
246 16         83 Dancer::Factory::Hook->execute_hooks( 'before_file_render',
247             $static_file );
248              
249 16   33     57 my $response = Dancer::SharedData->response() || Dancer::Response->new();
250              
251             # handle If-Modified-Since
252 16         200 my $last_modified = (stat $static_file)[9];
253 16         88 my $since = str2time(Dancer::SharedData->request->env->{HTTP_IF_MODIFIED_SINCE});
254 16 50 33     138 if( defined $since && $since >= $last_modified ) {
255 0         0 $response->status( 304 );
256 0         0 $response->content( '' );
257 0         0 return $response;
258             }
259              
260 16         53 my $fh = open_file( '<', $static_file );
261 16         70 binmode $fh;
262 16 100       43 $response->status($status) if ($status);
263 16         47 $response->header( 'Last-Modified' => time2str( $last_modified ) );
264 16   66     767 $response->header('Content-Type' => (($mime && _get_full_mime_type($mime)) ||
265             Dancer::SharedData->request->content_type ||
266             _get_mime_type($static_file)));
267 16         614 $response->content($fh);
268              
269 16         98 Dancer::Factory::Hook->execute_hooks( 'after_file_render', $response );
270              
271 16         111 return $response;
272             }
273 548         4402 return;
274             }
275              
276             # private
277             sub _get_full_mime_type {
278 1     1   6 my $mime = Dancer::MIME->instance();
279 1         4 return $mime->name_or_type(shift @_);
280             }
281              
282             sub _get_mime_type {
283 14     14   24 my $file = shift;
284 14         79 my $mime = Dancer::MIME->instance();
285 14         54 return $mime->for_file($file);
286             }
287              
288             sub _bad_request{
289 1   33 1   2 my $response = Dancer::SharedData->response() || Dancer::Response->new();
290 1         4 $response->status(400);
291 1         2 $response->content('Bad Request');
292             }
293              
294             # set of builtin templates needed by Dancer when rendering HTML pages
295             sub templates {
296 39   50 39 0 130 my $charset = setting('charset') || 'UTF-8';
297 39         222 { default =>
298             '
299             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
300            
301            
302             <% title %>
303            
304             305             . '" />
306            
307            
308            

<% title %>

309            
310             <% content %>
311            
312            
313             Powered by Dancer <% version %>
314            
315            
316             ',
317             };
318             }
319              
320              
321             1;
322              
323             __END__