File Coverage

lib/Mojolicious/Plugin/PlackMiddleware.pm
Criterion Covered Total %
statement 159 163 97.5
branch 24 32 75.0
condition 10 17 58.8
subroutine 28 29 96.5
pod 5 5 100.0
total 226 246 91.8


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::PlackMiddleware;
2 6     6   811850 use strict;
  6         27  
  6         201  
3 6     6   35 use warnings;
  6         16  
  6         181  
4 6     6   606 use Mojo::Base 'Mojolicious::Plugin';
  6         199742  
  6         44  
5 6     6   5151 use Plack::Util;
  6         12438  
  6         196  
6 6     6   555 use Mojo::Message::Request;
  6         106880  
  6         91  
7 6     6   814 use Mojo::Message::Response;
  6         7258  
  6         66  
8             our $VERSION = '0.39';
9 6     6   349 use Scalar::Util 'weaken';
  6         15  
  6         4872  
10            
11             ### ---
12             ### register
13             ### ---
14             sub register {
15 17     17 1 217079 my ($self, $app, $mws) = @_;
16            
17             my $plack_app = sub {
18 24     24   1166 my $env = shift;
19 24         75 my $c = $env->{'mojo.c'};
20 24         83 my $tx = $c->tx;
21            
22 24         152 $tx->req(psgi_env_to_mojo_req($env));
23            
24 24 100       251 if ($env->{'mojo.routed'}) {
25 2         13 my $stash = $c->stash;
26 2         22 for my $key (grep {$_ =~ qr{^mojo\.}} keys %{$stash}) {
  15         91  
  2         12  
27 10         29 delete $stash->{$key};
28             }
29 2         6 delete $stash->{'status'};
30 2         12 my $sever = $tx->res->headers->header('server');
31 2         61 $tx->res(Mojo::Message::Response->new);
32 2         42 $tx->res->headers->header('server', $sever);
33 2         136 $c->match(Mojolicious::Routes::Match->new->root($c->app->routes));
34 2         63 $env->{'mojo.inside_app'}->();
35             } else {
36 22         144 $env->{'mojo.inside_app'}->();
37 22         138333 $env->{'mojo.routed'} = 1;
38             }
39            
40 24         3779 return mojo_res_to_psgi_res($tx->res);
41 17         154 };
42            
43 17         83 my @mws = reverse @$mws;
44 17         120 while (scalar @mws) {
45 20 100       273 my $args = (ref $mws[0] eq 'HASH') ? shift @mws : undef;
46 20 100       84 my $cond = (ref $mws[0] eq 'CODE') ? shift @mws : undef;
47 20         99 my $e = _load_class(shift @mws, 'Plack::Middleware');
48             $plack_app = Mojolicious::Plugin::PlackMiddleware::_Cond->wrap(
49             $plack_app,
50             condition => $cond,
51 20     20   1516 builder => sub {$e->wrap($_[0], %$args)},
52 20         330 );
53             }
54            
55             $app->hook('around_dispatch' => sub {
56 23     23   280542 my ($next, $c) = @_;
57            
58 23 50       152 return $next->() if ($c->tx->req->error);
59            
60 23         497 my $plack_env = mojo_req_to_psgi_env($c->req);
61 23         8730 $plack_env->{'mojo.c'} = $c;
62 23         183 $plack_env->{'mojo.inside_app'} = $next;
63             $plack_env->{'psgi.errors'} =
64             Mojolicious::Plugin::PlackMiddleware::_EH->new(sub {
65 0         0 $c->app->log->debug(shift);
66 23         295 });
67            
68 23         275 $c->tx->res(psgi_res_to_mojo_res($plack_app->($plack_env)));
69 23 100       596 $c->rendered if (! $plack_env->{'mojo.routed'});
70 17         1094 });
71             }
72            
73             ### ---
74             ### chunk size
75             ### ---
76 6   50 6   56 use constant CHUNK_SIZE => $ENV{MOJO_CHUNK_SIZE} || 131072;
  6         27  
  6         7849  
77            
78             ### ---
79             ### convert psgi env to mojo req
80             ### ---
81             sub psgi_env_to_mojo_req {
82            
83 25     25 1 1309 my $env = shift;
84 25         123 my $req = Mojo::Message::Request->new->parse($env);
85            
86 25         19471 $req->reverse_proxy($env->{MOJO_REVERSE_PROXY});
87            
88             # Request body
89 25         251 my $len = $env->{CONTENT_LENGTH};
90 25         97 while (!$req->is_finished) {
91 4 50 33     52 my $chunk = ($len && $len < CHUNK_SIZE) ? $len : CHUNK_SIZE;
92 4         20 my $read = $env->{'psgi.input'}->read(my $buffer, $chunk, 0);
93 4 100       15 last unless $read;
94 3         14 $req->parse($buffer);
95 3         1398 $len -= $read;
96 3 50       18 last if $len <= 0;
97             }
98            
99 25         231 return $req;
100             }
101            
102             ### ---
103             ### convert mojo tx to psgi env
104             ### ---
105             sub mojo_req_to_psgi_env {
106            
107 24     24 1 3038 my $mojo_req = shift;
108 24         208 my $url = $mojo_req->url;
109 24         157 my $base = $url->base;
110 24         202 my $body =
111             Mojolicious::Plugin::PlackMiddleware::_PSGIInput->new($mojo_req->build_body);
112            
113 24         73 my %headers_org = %{$mojo_req->headers->to_hash};
  24         90  
114 24         1820 my %headers;
115 24         96 for my $key (keys %headers_org) {
116            
117 79         162 my $value = $headers_org{$key};
118 79         256 $key =~ s{-}{_}g;
119 79         206 $key = uc $key;
120 79 100       309 $key = "HTTP_$key" if ($key !~ /^(?:CONTENT_LENGTH|CONTENT_TYPE)$/);
121 79         263 $headers{$key} = $value;
122             }
123            
124             return {
125 24         479 %ENV,
126             %headers,
127             'SERVER_PROTOCOL' => $base->protocol. '/'. $mojo_req->version,
128             'SERVER_NAME' => $base->host,
129             'SERVER_PORT' => $base->port,
130             'REQUEST_METHOD' => $mojo_req->method,
131             'SCRIPT_NAME' => '',
132             'PATH_INFO' => $url->path->to_string,
133             'REQUEST_URI' => $url->to_string,
134             'QUERY_STRING' => $url->query->to_string,
135             'psgi.url_scheme' => $base->scheme,
136             'psgi.version' => [1,1],
137             'psgi.errors' => *STDERR,
138             'psgi.input' => $body,
139             'psgi.multithread' => Plack::Util::FALSE,
140             'psgi.multiprocess' => Plack::Util::TRUE,
141             'psgi.run_once' => Plack::Util::FALSE,
142             'psgi.streaming' => Plack::Util::TRUE,
143             'psgi.nonblocking' => Plack::Util::FALSE,
144             };
145             }
146            
147             ### ---
148             ### convert psgi res to mojo res
149             ### ---
150             sub psgi_res_to_mojo_res {
151 24     24 1 13101 my $psgi_res = shift;
152 24         191 my $mojo_res = Mojo::Message::Response->new;
153 24         240 $mojo_res->code($psgi_res->[0]);
154 24         224 my $headers = $mojo_res->headers;
155 24         1330 while (scalar @{$psgi_res->[1]}) {
  82         1146  
156 58         109 $headers->add(shift @{$psgi_res->[1]} => shift @{$psgi_res->[1]});
  58         137  
  58         191  
157             }
158            
159 24         132 $headers->remove('Content-Length'); # should be set by mojolicious later
160            
161 24         273 my $asset = $mojo_res->content->asset;
162 24     23   602 Plack::Util::foreach($psgi_res->[2], sub {$asset->add_chunk($_[0])});
  23         638  
163 24         1153 weaken($psgi_res);
164 24         133 return $mojo_res;
165             }
166            
167             ### ---
168             ### convert mojo res to psgi res
169             ### ---
170             sub mojo_res_to_psgi_res {
171 24     24 1 175 my $mojo_res = shift;
172 24         80 my $status = $mojo_res->code;
173 24         170 my $headers = $mojo_res->content->headers;
174 24         191 my @headers;
175 24         64 push @headers, $_ => $headers->header($_) for (@{$headers->names});
  24         132  
176 24         1142 my @body;
177 24         58 my $offset = 0;
178            
179             # don't know why but this block makes long polling tests to pass
180 24 0 33     74 if ($mojo_res->content->is_dynamic && $mojo_res->content->{delay}) {
181 0         0 $mojo_res->get_body_chunk(0);
182             }
183            
184 24         371 while (length(my $chunk = $mojo_res->get_body_chunk($offset))) {
185 24         1686 push(@body, $chunk);
186 24         96 $offset += length $chunk;
187             }
188 24         1581 return [$status, \@headers, \@body];
189             }
190            
191             ### ---
192             ### load mw class
193             ### ---
194             sub _load_class {
195 20     20   71 my($class, $prefix) = @_;
196            
197 20 50       70 if ($prefix) {
198 20 50 33     439 unless ($class =~ s/^\+// || $class =~ /^$prefix/) {
199 20         103 $class = "$prefix\::$class";
200             }
201             }
202 20 100       334 if ($class->can('call')) {
203 16         59 return $class;
204             }
205 4         13 my $file = $class;
206 4         24 $file =~ s!::!/!g;
207 4         2801 require "$file.pm"; ## no critic
208            
209 4         16669 return $class;
210             }
211              
212              
213             ### ---
214             ### Error Handler
215             ### ---
216             package Mojolicious::Plugin::PlackMiddleware::_EH;
217 6     6   73 use Mojo::Base -base;
  6         15  
  6         49  
218            
219             __PACKAGE__->attr('handler');
220            
221             sub new {
222 23     23   86 my ($class, $handler) = @_;
223 23         130 my $self = $class->SUPER::new;
224 23         226 $self->handler($handler);
225             }
226            
227             sub print {
228 0     0   0 my ($self, $error) = @_;
229 0         0 $self->handler->($error);
230             }
231              
232             ### ---
233             ### Port of Plack::Middleware::Conditional with mojolicious controller
234             ### ---
235             package Mojolicious::Plugin::PlackMiddleware::_Cond;
236 6     6   2002 use strict;
  6         16  
  6         152  
237 6     6   46 use warnings;
  6         14  
  6         230  
238 6     6   37 use parent qw(Plack::Middleware::Conditional);
  6         14  
  6         61  
239            
240             sub call {
241 29     29   844 my($self, $env) = @_;
242 29         165 my $cond = $self->condition;
243 29 100 100     254 if (! $cond || $cond->($env->{'mojo.c'}, $env)) {
244 28         675 return $self->middleware->($env);
245             } else {
246 1         12 return $self->app->($env);
247             }
248             }
249            
250             ### ---
251             ### PSGI Input handler
252             ### ---
253             package Mojolicious::Plugin::PlackMiddleware::_PSGIInput;
254 6     6   17238 use strict;
  6         86  
  6         141  
255 6     6   33 use warnings;
  6         14  
  6         1548  
256            
257             sub new {
258 27     27   4202 my ($class, $content) = @_;
259 27         131 return bless [$content, 0, length($content)], $class;
260             }
261            
262             sub read {
263 13     13   3540 my $self = shift;
264 13   100     72 my $offset = ($_[2] || $self->[1]);
265 13 50       46 if ($offset <= $self->[2]) {
266 13 100       54 if ($_[0] = substr($self->[0], $offset, $_[1])) {
267 10         26 $self->[1] = $offset + length($_[0]);
268 10         28 return 1;
269             }
270             }
271             }
272              
273             1;
274              
275             __END__