File Coverage

blib/lib/Plack/App/Debugger.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Plack::App::Debugger;
2              
3             # ABSTRACT: The web service backend for the debugger
4              
5 1     1   27625 use strict;
  1         2  
  1         35  
6 1     1   4 use warnings;
  1         1  
  1         21  
7              
8 1     1   4 use Try::Tiny;
  1         1  
  1         50  
9 1     1   4 use Scalar::Util qw[ blessed ];
  1         1  
  1         68  
10              
11 1     1   496 use File::ShareDir;
  1         5179  
  1         46  
12 1     1   6 use File::Spec::Unix ();
  1         2  
  1         10  
13 1     1   1005 use JSON::XS ();
  0            
  0            
14              
15             use Plack::App::File;
16              
17             use Plack::Debugger;
18              
19             our $VERSION = '0.02';
20             our $AUTHORITY = 'cpan:STEVAN';
21              
22             use parent 'Plack::Component';
23              
24             use constant DEFAULT_BASE_URL => '/debugger';
25              
26             # Be *extremely* lax about our JSON, this
27             # might be overkill for simple cases, but
28             # for non-simple cases, it just makes sense.
29             our $JSON = JSON::XS
30             ->new
31             ->utf8
32             #->pretty(1)
33             #->canonical(1)
34             ->allow_blessed(1)
35             ->convert_blessed(1)
36             ->allow_nonref(1)
37             ->allow_unknown(1);
38              
39             sub new {
40             my $class = shift;
41             my %args = @_ == 1 && ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
42              
43             $args{'base_url'} ||= DEFAULT_BASE_URL;
44             $args{'static_url'} ||= '/static';
45             $args{'js_init_url'} ||= '/js/plack-debugger.js';
46             $args{'static_asset_dir'} ||= try { File::ShareDir::dist_dir('Plack-Debugger') } || 'share';
47              
48             die "You must pass a reference to a 'Plack::Debugger' instance"
49             unless blessed $args{'debugger'}
50             && $args{'debugger'}->isa('Plack::Debugger');
51              
52             die "Could not locate the static asssets needed for the Plack::Debugger at (" . $args{'static_asset_dir'} . ")"
53             unless -d $args{'static_asset_dir'};
54              
55             # ... private data
56             $args{'_static_app'} = Plack::App::File->new( root => $args{'static_asset_dir'} )->to_app;
57             $args{'_JSON'} = $JSON;
58              
59             $class->SUPER::new( %args );
60             }
61              
62             # accessors ...
63              
64             sub debugger { (shift)->{'debugger'} } # a reference to the Plack::Debugger
65             sub base_url { (shift)->{'base_url'} } # the base URL the debugger application will be mounted at
66             sub static_url { (shift)->{'static_url'} } # the URL root from where the debugger can load static resources
67             sub js_init_url { (shift)->{'js_init_url'} } # the JS application initializer URL
68             sub static_asset_dir { (shift)->{'static_asset_dir'} } # the directory that the static assets are served from (optional)
69              
70             # create an injector middleware for this debugger application
71              
72             sub make_injector_middleware {
73             my $self = shift;
74             my $middlware = Plack::Util::load_class('Plack::Middleware::Debugger::Injector');
75             my $js_url = File::Spec::Unix->canonpath(join "" => $self->base_url, $self->static_url, $self->js_init_url);
76             my $content = sub {
77             my $env = shift;
78             die "Unable to locate the debugger request-uid, cannot inject the debugger application"
79             unless exists $env->{'plack.debugger.request_uid'};
80             sprintf '' => (
81             $js_url,
82             $env->{'plack.debugger.request_uid'}
83             );
84             };
85             return sub { $middlware->new( content => $content )->wrap( @_ ) }
86             }
87              
88             # ...
89              
90             sub call {
91             my $self = shift;
92             my $env = shift;
93             my $r = Plack::Request->new( $env );
94              
95             my $static_url = $self->static_url;
96              
97             if ( $r->path_info =~ m!^$static_url! ) {
98             # clean off the path and
99             # serve the static resources
100             $r->env->{'PATH_INFO'} =~ s!^$static_url!!;
101             return $self->{'_static_app'}->( $r->env );
102             }
103             else {
104             # now handle the requests for results ...
105             $self->construct_debug_data_response( $r );
106             }
107             }
108              
109             sub construct_debug_data_response {
110             my ($self, $r) = @_;
111             my ($req, $err) = $self->validate_and_prepare_request( $r );
112             return $err if defined $err;
113             $self->_create_JSON_response( 200 => $self->fetch_debug_data_for_request( $req ) );
114             }
115              
116             sub validate_and_prepare_request {
117             my ($self, $r) = @_;
118              
119             # this only supports GET requests
120             return (undef, $self->_create_error_response( 405 => 'Method Not Allowed' ))
121             if $r->method ne 'GET';
122              
123             my ($request_uid, $get_subrequests, $get_specific_subrequest) = grep { $_ } split '/' => $r->path_info;
124              
125             # we need to have a request-id at a minimum
126             return (undef, $self->_create_error_response( 400 => 'Bad Request' ))
127             unless $request_uid;
128              
129             # some debugging help to make sure the UI is robust
130             return (undef, $self->_create_error_response( 500 => 'I AM THE CHAOS MONKEY, HEAR ME ROAR!!!!!' ))
131             if Plack::Debugger::DEBUG && (rand() <= $ENV{'PLACK_DEBUGGER_CHAOS_MONKEY_LEVEL'});
132              
133             # track the request uid
134             my $req = { request_uid => $request_uid };
135              
136             # if there is a specific subrequest uid
137             $req->{'subrequest_uid'} = $get_specific_subrequest
138             if $get_specific_subrequest;
139              
140             # or if they just want all subrequests
141             $req->{'all_subrequests'} = {}
142             if $get_subrequests && !$get_specific_subrequest;
143              
144             # handle any special headers
145             if ( my $epoch = $r->header('X-Plack-Debugger-SubRequests-Modified-Since') ) {
146             $req->{'all_subrequests'}->{'modified_since'} = $epoch;
147             }
148              
149             return ($req, undef);
150             }
151              
152             sub fetch_debug_data_for_request {
153             my ($self, $req) = @_;
154              
155             # if no subrequests requested, get the base request
156             if ( (not exists $req->{'subrequest_uid'}) && (not exists $req->{'all_subrequests'}) ) {
157             return $self->debugger->load_request_results( $req->{'request_uid'} )
158             }
159             # if no specific subrequest is requested, get all the subrequests for a specific request
160             elsif ( (not exists $req->{'subrequest_uid'}) && exists $req->{'all_subrequests'} ) {
161             if ( exists $req->{'all_subrequests'}->{'modified_since'} ) {
162             return $self->debugger->load_all_subrequest_results_modified_since(
163             $req->{'request_uid'},
164             $req->{'all_subrequests'}->{'modified_since'}
165             );
166             }
167             else {
168             return $self->debugger->load_all_subrequest_results( $req->{'request_uid'} )
169             }
170             }
171             # if a specific subrequest is requested, return that
172             elsif ( exists $req->{'subrequest_uid'} ) {
173             return $self->debugger->load_subrequest_results( $req->{'request_uid'}, $req->{'subrequest_uid'} )
174             }
175             # should never actually get here
176             else {
177             die 'Unknown request type';
178             }
179             }
180              
181             # ...
182              
183             sub _create_error_response {
184             my ($self, $status, $body) = @_;
185             return [ $status, [ 'Content-Type' => 'text/plain', 'Content-Length' => length $body ], [ $body ] ]
186             }
187              
188             sub _create_JSON_response {
189             my ($self, $status, $data) = @_;
190             my $json = $self->{'_JSON'}->encode( $data );
191             return [ $status, [ 'Content-Type' => 'application/json', 'Content-Length' => length $json ], [ $json ] ]
192             }
193              
194             1;
195              
196             __END__