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.3520';
5 168     168   1183 use strict;
  168         423  
  168         5016  
6 168     168   909 use warnings;
  168         393  
  168         4299  
7 168     168   899 use Carp;
  168         407  
  168         9674  
8 168     168   1292 use HTTP::Headers;
  168         434  
  168         7128  
9 168     168   77243 use HTTP::Date qw( str2time time2str );
  168         670244  
  168         12123  
10 168     168   3189 use Dancer::Route;
  168         402  
  168         3840  
11 168     168   899 use Dancer::HTTP;
  168         373  
  168         5913  
12 168     168   1011 use Dancer::Cookie;
  168         426  
  168         3568  
13 168     168   904 use Dancer::Factory::Hook;
  168         381  
  168         4271  
14 168     168   919 use Dancer::Cookies;
  168         439  
  168         3807  
15 168     168   1003 use Dancer::Request;
  168         409  
  168         3818  
16 168     168   980 use Dancer::Response;
  168         356  
  168         3662  
17 168     168   77127 use Dancer::Serializer;
  168         503  
  168         5805  
18 168     168   1273 use Dancer::Config 'setting';
  168         462  
  168         8613  
19 168     168   1118 use Dancer::FileUtils qw(path path_or_empty dirname read_file_content open_file);
  168         402  
  168         9997  
20 168     168   1025 use Dancer::SharedData;
  168         413  
  168         3213  
21 168     168   857 use Dancer::Logger;
  168         395  
  168         3630  
22 168     168   1255 use Dancer::MIME;
  168         469  
  168         5241  
23 168     168   1033 use Dancer::Exception qw(:all);
  168         521  
  168         337540  
24              
25             Dancer::Factory::Hook->instance->install_hooks(
26             qw/before after before_serializer after_serializer before_file_render after_file_render/
27             );
28              
29 517     517 0 1244 sub render_file { get_file_response() }
30              
31             sub render_action {
32 512     512 0 1149 my $class = shift;
33 512         1426 my $resp = $class->get_action_response();
34 467 100       1719 return (defined $resp)
35             ? response_with_headers()
36             : undef;
37             }
38              
39             sub render_error {
40 32     32 0 107 my ($class, $error_code) = @_;
41              
42 32         129 my $app = Dancer::App->current;
43 32         129 my $static_file = path($app->setting('public'), "$error_code.html");
44 32         178 my $response = Dancer::Renderer->get_file_response_for_path(
45             $static_file => $error_code);
46 32 100       137 return $response if $response;
47              
48 30         244 return Dancer::Response->new(
49             status => $error_code,
50             headers => ['Content-Type' => 'text/html'],
51             content => Dancer::Renderer->html_page(
52             "Error $error_code" => "

Unable to process your query

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

<% title %>

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