Filename | /usr/share/perl5/Plack/Middleware/Debug.pm |
Statements | Executed 0 statements in 0s |
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package Plack::Middleware::Debug; | ||||
2 | use 5.008_001; | ||||
3 | use strict; | ||||
4 | use warnings; | ||||
5 | use parent qw(Plack::Middleware); | ||||
6 | our $VERSION = '0.16'; | ||||
7 | |||||
8 | use Encode; | ||||
9 | use File::ShareDir; | ||||
10 | use Plack::App::File; | ||||
11 | use Plack::Builder; | ||||
12 | use Plack::Util::Accessor qw(panels renderer files); | ||||
13 | use Plack::Util; | ||||
14 | use Plack::Middleware::Debug::Panel; | ||||
15 | use Text::MicroTemplate; | ||||
16 | use Try::Tiny; | ||||
17 | |||||
18 | sub TEMPLATE { | ||||
19 | <<'EOTMPL' } | ||||
20 | % my $stash = $_[0]; | ||||
21 | <script type="text/javascript" charset="utf-8"> | ||||
22 | // When jQuery is sourced, it's going to overwrite whatever might be in the | ||||
23 | // '$' variable, so store a reference of it in a temporary variable... | ||||
24 | var _$ = window.$; | ||||
25 | if (typeof jQuery == 'undefined') { | ||||
26 | var jquery_url = '<%= $stash->{BASE_URL} %>/debug_toolbar/jquery.js'; | ||||
27 | document.write(unescape('%3Cscript src="' + jquery_url + '" type="text/javascript"%3E%3C/script%3E')); | ||||
28 | } | ||||
29 | </script> | ||||
30 | <script type="text/javascript" src="<%= $stash->{BASE_URL} %>/debug_toolbar/toolbar.min.js"></script> | ||||
31 | <script type="text/javascript" charset="utf-8"> | ||||
32 | // Now that jQuery is done loading, put the '$' variable back to what it was... | ||||
33 | var $ = _$; | ||||
34 | </script> | ||||
35 | <style type="text/css"> | ||||
36 | @import url(<%= $stash->{BASE_URL} %>/debug_toolbar/toolbar.min.css); | ||||
37 | </style> | ||||
38 | <div id="plDebug"> | ||||
39 | <div style="display:none;" id="plDebugToolbar"> | ||||
40 | <ul id="plDebugPanelList"> | ||||
41 | % if ($stash->{panels}) { | ||||
42 | <li><a id="plHideToolBarButton" href="#" title="Hide Toolbar">Hide »</a></li> | ||||
43 | % } else { | ||||
44 | <li id="plDebugButton">DEBUG</li> | ||||
45 | % } | ||||
46 | % for my $panel (reverse @{$stash->{panels}}) { | ||||
47 | <li> | ||||
48 | % if ($panel->content) { | ||||
49 | <a href="<%= $panel->url %>" title="<%= $panel->title %>" class="<%= $panel->dom_id %>"> | ||||
50 | % } else { | ||||
51 | <div class="contentless"> | ||||
52 | % } | ||||
53 | <%= $panel->nav_title %> | ||||
54 | % if ($panel->nav_subtitle) { | ||||
55 | <br><small><%= $panel->nav_subtitle %></small> | ||||
56 | % } | ||||
57 | % if ($panel->content) { | ||||
58 | </a> | ||||
59 | % } else { | ||||
60 | </div> | ||||
61 | % } | ||||
62 | </li> | ||||
63 | % } # end for | ||||
64 | </ul> | ||||
65 | </div> | ||||
66 | <div style="display:none;" id="plDebugToolbarHandle"> | ||||
67 | <a title="Show Toolbar" id="plShowToolBarButton" href="#">«</a> | ||||
68 | </div> | ||||
69 | % for my $panel (reverse @{$stash->{panels}}) { | ||||
70 | % if ($panel->content) { | ||||
71 | <div id="<%= $panel->dom_id %>" class="panelContent"> | ||||
72 | <div class="plDebugPanelTitle"> | ||||
73 | <a href="" class="plDebugClose">Close</a> | ||||
74 | <h3><%= $panel->title %></h3> | ||||
75 | </div> | ||||
76 | <div class="plDebugPanelContent"> | ||||
77 | <div class="scroll"> | ||||
78 | % my $content = ref $panel->content eq 'CODE' ? $panel->content->() : $panel->content; | ||||
79 | % $content = Encode::encode('latin1', $content, Encode::FB_XMLCREF); | ||||
80 | <%= Text::MicroTemplate::encoded_string($content) %> | ||||
81 | </div> | ||||
82 | </div> | ||||
83 | </div> | ||||
84 | % } | ||||
85 | % } # end for | ||||
86 | <div id="plDebugWindow" class="panelContent"></div> | ||||
87 | </div> | ||||
88 | EOTMPL | ||||
89 | |||||
90 | sub default_panels { | ||||
91 | [qw(Environment Response Timer Memory Session DBITrace)]; | ||||
92 | } | ||||
93 | |||||
94 | sub prepare_app { | ||||
95 | my $self = shift; | ||||
96 | my $root = try { File::ShareDir::dist_dir('Plack-Middleware-Debug') } || 'share'; | ||||
97 | |||||
98 | my $builder = Plack::Builder->new; | ||||
99 | |||||
100 | for my $spec (@{ $self->panels || $self->default_panels }) { | ||||
101 | my ($package, %args); | ||||
102 | if (ref $spec eq 'ARRAY') { | ||||
103 | # For the backward compatiblity | ||||
104 | # [ 'PanelName', key1 => $value1, ... ] | ||||
105 | $package = shift @$spec; | ||||
106 | $builder->add_middleware("Debug::$package", @$spec); | ||||
107 | } else { | ||||
108 | # $spec could be a code ref (middleware) or a string | ||||
109 | # copy so that we do not change default_panels | ||||
110 | my $spec_copy = $spec; | ||||
111 | $spec_copy = "Debug::$spec_copy" unless ref $spec_copy; | ||||
112 | $builder->add_middleware($spec_copy); | ||||
113 | } | ||||
114 | } | ||||
115 | |||||
116 | $self->app( $builder->to_app($self->app) ); | ||||
117 | |||||
118 | $self->renderer( | ||||
119 | Text::MicroTemplate->new( | ||||
120 | template => $self->TEMPLATE, | ||||
121 | tag_start => '<%', | ||||
122 | tag_end => '%>', | ||||
123 | line_start => '%', | ||||
124 | )->build | ||||
125 | ); | ||||
126 | |||||
127 | $self->files(Plack::App::File->new(root => $root)); | ||||
128 | } | ||||
129 | |||||
130 | sub call { | ||||
131 | my ($self, $env) = @_; | ||||
132 | if ($env->{PATH_INFO} =~ m!^/debug_toolbar!) { | ||||
133 | return $self->files->call($env); | ||||
134 | } | ||||
135 | |||||
136 | $env->{'plack.debug.panels'} = []; | ||||
137 | |||||
138 | my $res = $self->app->($env); | ||||
139 | $self->response_cb($res, sub { | ||||
140 | my $res = shift; | ||||
141 | my $headers = Plack::Util::headers($res->[1]); | ||||
142 | my $panels = delete $env->{'plack.debug.panels'}; | ||||
143 | if ( ! Plack::Util::status_with_no_entity_body($res->[0]) | ||||
144 | && ($headers->get('Content-Type') || '') =~ m!^(?:text/html|application/xhtml\+xml)!) { | ||||
145 | |||||
146 | my $vars = { | ||||
147 | panels => [ grep !$_->disabled, @$panels ], | ||||
148 | BASE_URL => $env->{SCRIPT_NAME}, | ||||
149 | }; | ||||
150 | |||||
151 | my $content = $self->renderer->($vars); | ||||
152 | return sub { | ||||
153 | my $chunk = shift; | ||||
154 | return unless defined $chunk; | ||||
155 | 3 | 11µs | $chunk =~ s!(?=</body>)!$content!i; # spent 11µs making 3 calls to Text::MicroTemplate::EncodedString::__ANON__, avg 4µs/call | ||
156 | return $chunk; | ||||
157 | }; | ||||
158 | } | ||||
159 | }); | ||||
160 | } | ||||
161 | |||||
162 | 1; | ||||
163 | __END__ | ||||
164 | |||||
165 | =head1 NAME | ||||
166 | |||||
167 | Plack::Middleware::Debug - display information about the current request/response | ||||
168 | |||||
169 | =head1 SYNOPSIS | ||||
170 | |||||
171 | enable "Debug"; | ||||
172 | |||||
173 | =head1 DESCRIPTION | ||||
174 | |||||
175 | The debug middleware offers a configurable set of panels that displays | ||||
176 | information about the current request and response. The information is | ||||
177 | generated only for responses with a status of 200 (C<OK>) and a | ||||
178 | C<Content-Type> that contains C<text/html> or C<application/xhtml+xml> | ||||
179 | and is embedded in the HTML that is sent back to the browser. Also the | ||||
180 | code is injected directly before the C<< </body> >> tag so if there is | ||||
181 | no such tag, the information will not be injected. | ||||
182 | |||||
183 | To enable the middleware, just use L<Plack::Builder> as usual in your C<.psgi> | ||||
184 | file: | ||||
185 | |||||
186 | use Plack::Builder; | ||||
187 | |||||
188 | builder { | ||||
189 | enable 'Debug', panels => [ qw(DBITrace Memory Timer) ]; | ||||
190 | $app; | ||||
191 | }; | ||||
192 | |||||
193 | The C<Debug> middleware takes an optional C<panels> argument whose value is | ||||
194 | expected to be a reference to an array of panel specifications. If given, | ||||
195 | only those panels will be enabled. If you don't pass a C<panels> | ||||
196 | argument, the default list of panels - C<Environment>, C<Response>, | ||||
197 | C<Timer>, C<Memory>, C<Session> and C<DBITrace> - will be enabled, each with | ||||
198 | their default settings, and automatically disabled if their targer modules or | ||||
199 | middleware components are not loaded. | ||||
200 | |||||
201 | Each panel specification can take one of three forms: | ||||
202 | |||||
203 | =over 4 | ||||
204 | |||||
205 | =item A string | ||||
206 | |||||
207 | This is interpreted as the base name of a panel in the | ||||
208 | C<Plack::Middeware::Debug::> namespace. The panel class is loaded and a panel | ||||
209 | object is created with its default settings. | ||||
210 | |||||
211 | =item An array reference | ||||
212 | |||||
213 | If you need to pass arguments to the panel object as it is created, | ||||
214 | you may use this form (But see below). | ||||
215 | |||||
216 | The first element of the array reference has to be the panel base | ||||
217 | name. The remaining elements are key/value pairs to be passed to the | ||||
218 | panel. | ||||
219 | |||||
220 | For example: | ||||
221 | |||||
222 | builder { | ||||
223 | enable 'Debug', panels => | ||||
224 | [ qw(Environment Response Timer Memory), | ||||
225 | [ 'DBITrace', level => 2 ] | ||||
226 | ]; | ||||
227 | $app; | ||||
228 | }; | ||||
229 | |||||
230 | Because each panel is a middleware component, you can write this way | ||||
231 | as well: | ||||
232 | |||||
233 | builder { | ||||
234 | enable 'Debug'; # load defaults | ||||
235 | enable 'Debug::DBITrace', level => 2; | ||||
236 | $app; | ||||
237 | }; | ||||
238 | |||||
239 | Note that the C<<enable 'Debug'>> line should come before other Debug | ||||
240 | panels because of the order middleware components are executed. | ||||
241 | |||||
242 | =item Custom middleware | ||||
243 | |||||
244 | You can also pass a Panel middleware component. This might be useful | ||||
245 | if you have custom debug panels in your framework or web application. | ||||
246 | |||||
247 | =back | ||||
248 | |||||
249 | =head1 HOW TO WRITE YOUR OWN DEBUG PANEL | ||||
250 | |||||
251 | The C<Debug> middleware is designed to be easily extensible. You might | ||||
252 | want to write a custom debug panel for your framework or for your web | ||||
253 | application. Each debug panel is also a Plack middleware copmonent and | ||||
254 | is easy to write one. | ||||
255 | |||||
256 | Let's look at the anatomy of the C<Timer> debug panel. Here is the code from | ||||
257 | that panel: | ||||
258 | |||||
259 | package Plack::Middleware::Debug::Timer; | ||||
260 | use Time::HiRes; | ||||
261 | |||||
262 | use parent qw(Plack::Middleware::Debug::Base); | ||||
263 | |||||
264 | sub run { | ||||
265 | my($self, $env, $panel) = @_; | ||||
266 | |||||
267 | my $start = [ Time::HiRes::gettimeofday ]; | ||||
268 | |||||
269 | return sub { | ||||
270 | my $res = shift; | ||||
271 | |||||
272 | my $end = [ Time::HiRes::gettimeofday ]; | ||||
273 | my $elapsed = sprintf '%.6f s', Time::HiRes::tv_interval $start, $end; | ||||
274 | |||||
275 | $panel->nav_subtitle($elapsed); | ||||
276 | $panel->content( | ||||
277 | $self->render_list_pairs( | ||||
278 | [ Start => $self->format_time($start), | ||||
279 | End => $self->format_time($end), | ||||
280 | Elapsed => $elapsed ], | ||||
281 | ), | ||||
282 | ); | ||||
283 | }; | ||||
284 | } | ||||
285 | |||||
286 | sub format_time { ... } | ||||
287 | |||||
288 | To write a new debug panel, place it in the C<Plack::Middleware::Debug::> | ||||
289 | namespace. In our example, the C<Timer> panel lives in the | ||||
290 | C<Plack::Middleware::Debug::Timer> package. | ||||
291 | |||||
292 | The only thing your panel should do is to subclass | ||||
293 | L<Plack::Middleware::Debug::Base>. This does most of the things a | ||||
294 | middleware component should do as a Plack middleware, so you only need | ||||
295 | to override C<run> method to profile and create the panel content. | ||||
296 | |||||
297 | sub run { | ||||
298 | my($self, $env, $panel) = @_; | ||||
299 | |||||
300 | # Do something before the application runs | ||||
301 | |||||
302 | return sub { | ||||
303 | my $res = shift; | ||||
304 | |||||
305 | # Do something after the application returns | ||||
306 | |||||
307 | }; | ||||
308 | } | ||||
309 | |||||
310 | You can create as many lexical variables as you need and reference | ||||
311 | that in the returned callback as a closure, and update the content of | ||||
312 | of the C<$panel> which is Plack::Middleware::Debug::Panel object. | ||||
313 | |||||
314 | In our C<Timer> example we want to list three key/value pairs: the | ||||
315 | start time, the end time and the elapsed time. We use the | ||||
316 | C<render_list_pairs()> method to place the pairs in the order we | ||||
317 | want. There is also a C<render_hash()> and C<render_lines()> method, | ||||
318 | to render a hash keys and values, as well as just text lines (e.g. log | ||||
319 | messages). | ||||
320 | |||||
321 | =head1 BUGS AND LIMITATIONS | ||||
322 | |||||
323 | Please report any bugs or feature requests through the web interface at | ||||
324 | L<http://rt.cpan.org>. | ||||
325 | |||||
326 | =head1 INSTALLATION | ||||
327 | |||||
328 | See perlmodinstall for information and options on installing Perl modules. | ||||
329 | |||||
330 | =head1 AVAILABILITY | ||||
331 | |||||
332 | The latest version of this module is available from the Comprehensive Perl | ||||
333 | Archive Network (CPAN). Visit L<http://www.perl.com/CPAN/> to find a CPAN site | ||||
334 | near you. Or see L<http://search.cpan.org/dist/Plack-Middleware-Debug/>. | ||||
335 | |||||
336 | The development version lives at | ||||
337 | L<http://github.com/miyagawa/plack-middleware-debug/>. Instead of sending | ||||
338 | patches, please fork this project using the standard git and github | ||||
339 | infrastructure. | ||||
340 | |||||
341 | =head1 AUTHORS | ||||
342 | |||||
343 | Marcel Grunauer, C<< <marcel@cpan.org> >> | ||||
344 | |||||
345 | Tatsuhiko Miyagawa, C<< <miyagawa@bulknews.net> >> | ||||
346 | |||||
347 | =head1 COPYRIGHT AND LICENSE | ||||
348 | |||||
349 | Copyright 2009 by Marcel GrE<uuml>nauer | ||||
350 | |||||
351 | This library is free software; you can redistribute it and/or modify | ||||
352 | it under the same terms as Perl itself. | ||||
353 | |||||
354 | =head1 SEE ALSO | ||||
355 | |||||
356 | The debug middleware is heavily influenced (that is, adapted from) the Django | ||||
357 | Debug Toolbar - see L<http://github.com/robhudson/django-debug-toolbar>. | ||||
358 | |||||
359 | =cut |