File Coverage

blib/lib/Plack/Middleware/StackTrace/RethrowFriendly.pm
Criterion Covered Total %
statement 57 70 81.4
branch 13 18 72.2
condition 6 8 75.0
subroutine 16 18 88.8
pod 1 5 20.0
total 93 119 78.1


line stmt bran cond sub pod time code
1             package Plack::Middleware::StackTrace::RethrowFriendly;
2 7     7   3257 use strict;
  7         10  
  7         205  
3 7     7   22 use warnings;
  7         9  
  7         155  
4              
5             # core
6 7     7   29 use Scalar::Util 'refaddr';
  7         9  
  7         667  
7 7     7   3262 use MIME::Base64 qw(encode_base64);
  7         3895  
  7         436  
8              
9             # cpan
10 7     7   3425 use parent qw(Plack::Middleware::StackTrace);
  7         1677  
  7         34  
11 7     7   190443 use Try::Tiny;
  7         12  
  7         5506  
12              
13             our $VERSION = "0.01";
14              
15             sub call {
16 4     4 1 51036 my($self, $env) = @_;
17              
18 4         9 my $trace = [];
19 4         8 my $last_key = '';
20             local $SIG{__DIE__} = sub {
21             # If we get the same keys, the exception may be rethrown and
22             # we keep the original stacktrace.
23 4     4   634 my $key = _make_key($_[0]);
24 4 50       21 if ($key ne $last_key) {
25 4         7 $last_key = $key;
26 4         8 $trace = [];
27             }
28              
29 4         29 push @$trace, $Plack::Middleware::StackTrace::StackTraceClass->new(
30             indent => 1,
31             message => munge_error($_[0], [ caller ]),
32             ignore_package => __PACKAGE__,
33             );
34              
35 4         1774 die @_;
36 4         31 };
37              
38 4         8 my $caught;
39             my $res = try {
40 4     4   123 $self->app->($env);
41             } catch {
42 1     1   8 $caught = $_;
43 1         4 _error('text/plain', $caught, 'no_trace');
44 4         27 };
45              
46 4 100 66     88 if (scalar @$trace && $self->should_show_trace($caught, $last_key, $res)) {
47 2         24 my $text = $trace->[0]->as_string;
48 2 50       1887 my $html = @$trace > 1
49             ? _multi_trace_html(@$trace) : $trace->[0]->as_html;
50 2         11563 $env->{'plack.stacktrace.text'} = $text;
51 2         8 $env->{'plack.stacktrace.html'} = $html;
52 2 50       24 $env->{'psgi.errors'}->print($text) unless $self->no_print_errors;
53 2 100 100     37 $res = ($env->{HTTP_ACCEPT} || '*/*') =~ /html/
54             ? _error('text/html', $html)
55             : _error('text/plain', $text);
56             }
57             # break $trace here since $SIG{__DIE__} holds the ref to it, and
58             # $trace has refs to Standalone.pm's args ($conn etc.) and
59             # prevents garbage collection to be happening.
60 4         39 undef $trace;
61 4         102 undef $last_key;
62              
63 4         55 return $res;
64             }
65              
66             sub should_show_trace {
67 4     4 0 13 my ($self, $err, $key, $res) = @_;
68 4 100       10 if ($err) {
69 1         2 return _make_key($err) eq $key;
70             } else {
71 3   66     15 return $self->force && ref $res eq 'ARRAY' && $res->[0] == 500;
72             }
73             }
74              
75 1     1 0 3 sub no_trace_error { Plack::Middleware::StackTrace::no_trace_error(@_) }
76 4     4 0 24 sub munge_error { Plack::Middleware::StackTrace::munge_error(@_) }
77 3     3 0 11 sub utf8_safe { Plack::Middleware::StackTrace::utf8_safe(@_) }
78              
79             sub _make_key {
80 5     5   8 my ($val) = @_;
81 5 50       22 if (!defined($val)) {
    50          
82 0         0 return 'undef';
83             } elsif (ref($val)) {
84 0         0 return 'ref:' . refaddr($val);
85             } else {
86 5         18 return "str:$val";
87             }
88             }
89              
90             sub _error {
91 3     3   7 my ($type, $content, $no_trace) = @_;
92 3         12 $content = utf8_safe($content);
93 3 100       25 $content = no_trace_error($content) if $no_trace;
94 3         25 return [ 500, [ 'Content-Type' => "$type; charset=utf-8" ], [ $content ] ];
95             }
96              
97             sub _multi_trace_html {
98 0     0     my (@trace) = @_;
99              
100 0           require Devel::StackTrace::AsHTML;
101 0           my $msg = Devel::StackTrace::AsHTML::encode_html(
102             $trace[0]->frame(0)->as_string(1),
103             );
104              
105 0           my @data_uris = map { _trace_as_data_uri($_) } @trace;
  0            
106 0           my @links = map {
107 0           sprintf
108             '#%s',
109             $data_uris[$_],
110             $_;
111             } (0..$#data_uris);
112              
113 0           my $css = << '----';
114             body {
115             height: 100%;
116             margin: 0;
117             padding: 0;
118             }
119             div#links {
120             z-index: 100;
121             position: absolute;
122             top: 0;
123             height: 30px;
124             }
125             #content {
126             z-index: 50;
127             position: absolute;
128             top: 0;
129             height: 100%;
130             width: 100%;
131             margin: 0;
132             padding: 30px 0 4px 0;
133             box-sizing: border-box;
134             }
135             iframe#trace {
136             border: none;
137             padding: 0;
138             margin: 0;
139             height: 100%;
140             width: 100%;
141             }
142             a.selected {
143             color: #000;
144             font-weight: bold;
145             text-decoration: none;
146             }
147             ----
148              
149 0           sprintf << '----', $msg, $css, join(' ', @links), $data_uris[0];
150            
151            
152            
153             Error: %s
154            
157            
173            
174            
175            
176             Throws: %s
177            
178            
179            
180            
181            
182            
183             ----
184             }
185              
186             sub _trace_as_data_uri ($) {
187 0     0     my $html = $_[0]->as_html;
188 0           return 'data:text/html;charset=utf-8;base64,'.encode_base64($html);
189             }
190              
191             1;
192             __END__