| 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 |