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   749482 use strict;
  6         24  
  6         189  
3 6     6   33 use warnings;
  6         13  
  6         166  
4 6     6   617 use Mojo::Base 'Mojolicious::Plugin';
  6         206589  
  6         38  
5 6     6   4849 use Plack::Util;
  6         11753  
  6         189  
6 6     6   564 use Mojo::Message::Request;
  6         111187  
  6         86  
7 6     6   816 use Mojo::Message::Response;
  6         7325  
  6         68  
8             our $VERSION = '0.38';
9 6     6   328 use Scalar::Util 'weaken';
  6         17  
  6         4680  
10            
11             ### ---
12             ### register
13             ### ---
14             sub register {
15 17     17 1 191937 my ($self, $app, $mws) = @_;
16            
17             my $plack_app = sub {
18 24     24   821 my $env = shift;
19 24         65 my $c = $env->{'mojo.c'};
20 24         62 my $tx = $c->tx;
21            
22 24         140 $tx->req(psgi_env_to_mojo_req($env));
23            
24 24 100       197 if ($env->{'mojo.routed'}) {
25 2         11 my $stash = $c->stash;
26 2         20 for my $key (grep {$_ =~ qr{^mojo\.}} keys %{$stash}) {
  15         82  
  2         10  
27 10         27 delete $stash->{$key};
28             }
29 2         5 delete $stash->{'status'};
30 2         9 my $sever = $tx->res->headers->header('server');
31 2         55 $tx->res(Mojo::Message::Response->new);
32 2         37 $tx->res->headers->header('server', $sever);
33 2         127 $c->match(Mojolicious::Routes::Match->new->root($c->app->routes));
34 2         48 $env->{'mojo.inside_app'}->();
35             } else {
36 22         149 $env->{'mojo.inside_app'}->();
37 22         128141 $env->{'mojo.routed'} = 1;
38             }
39            
40 24         2874 return mojo_res_to_psgi_res($tx->res);
41 17         101 };
42            
43 17         65 my @mws = reverse @$mws;
44 17         66 while (scalar @mws) {
45 20 100       211 my $args = (ref $mws[0] eq 'HASH') ? shift @mws : undef;
46 20 100       61 my $cond = (ref $mws[0] eq 'CODE') ? shift @mws : undef;
47 20         66 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   1268 builder => sub {$e->wrap($_[0], %$args)},
52 20         232 );
53             }
54            
55             $app->hook('around_dispatch' => sub {
56 23     23   230375 my ($next, $c) = @_;
57            
58 23 50       98 return $next->() if ($c->tx->req->error);
59            
60 23         391 my $plack_env = mojo_req_to_psgi_env($c->req);
61 23         7482 $plack_env->{'mojo.c'} = $c;
62 23         65 $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         217 });
67            
68 23         250 $c->tx->res(psgi_res_to_mojo_res($plack_app->($plack_env)));
69 23 100       493 $c->rendered if (! $plack_env->{'mojo.routed'});
70 17         954 });
71             }
72            
73             ### ---
74             ### chunk size
75             ### ---
76 6   50 6   52 use constant CHUNK_SIZE => $ENV{MOJO_CHUNK_SIZE} || 131072;
  6         16  
  6         7656  
77            
78             ### ---
79             ### convert psgi env to mojo req
80             ### ---
81             sub psgi_env_to_mojo_req {
82            
83 25     25 1 1278 my $env = shift;
84 25         94 my $req = Mojo::Message::Request->new->parse($env);
85            
86 25         18137 $req->reverse_proxy($env->{MOJO_REVERSE_PROXY});
87            
88             # Request body
89 25         203 my $len = $env->{CONTENT_LENGTH};
90 25         81 while (!$req->is_finished) {
91 4 50 33     40 my $chunk = ($len && $len < CHUNK_SIZE) ? $len : CHUNK_SIZE;
92 4         18 my $read = $env->{'psgi.input'}->read(my $buffer, $chunk, 0);
93 4 100       13 last unless $read;
94 3         13 $req->parse($buffer);
95 3         1174 $len -= $read;
96 3 50       16 last if $len <= 0;
97             }
98            
99 25         200 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 3049 my $mojo_req = shift;
108 24         85 my $url = $mojo_req->url;
109 24         140 my $base = $url->base;
110 24         163 my $body =
111             Mojolicious::Plugin::PlackMiddleware::_PSGIInput->new($mojo_req->build_body);
112            
113 24         83 my %headers_org = %{$mojo_req->headers->to_hash};
  24         72  
114 24         1852 my %headers;
115 24         96 for my $key (keys %headers_org) {
116            
117 99         213 my $value = $headers_org{$key};
118 99         278 $key =~ s{-}{_}g;
119 99         215 $key = uc $key;
120 99 100       411 $key = "HTTP_$key" if ($key !~ /^(?:CONTENT_LENGTH|CONTENT_TYPE)$/);
121 99         308 $headers{$key} = $value;
122             }
123            
124             return {
125 24         381 %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 12026 my $psgi_res = shift;
152 24         228 my $mojo_res = Mojo::Message::Response->new;
153 24         223 $mojo_res->code($psgi_res->[0]);
154 24         246 my $headers = $mojo_res->headers;
155 24         1056 while (scalar @{$psgi_res->[1]}) {
  82         1027  
156 58         106 $headers->add(shift @{$psgi_res->[1]} => shift @{$psgi_res->[1]});
  58         123  
  58         174  
157             }
158            
159 24         115 $headers->remove('Content-Length'); # should be set by mojolicious later
160            
161 24         197 my $asset = $mojo_res->content->asset;
162 24     23   522 Plack::Util::foreach($psgi_res->[2], sub {$asset->add_chunk($_[0])});
  23         480  
163 24         977 weaken($psgi_res);
164 24         126 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 141 my $mojo_res = shift;
172 24         89 my $status = $mojo_res->code;
173 24         137 my $headers = $mojo_res->content->headers;
174 24         180 my @headers;
175 24         54 push @headers, $_ => $headers->header($_) for (@{$headers->names});
  24         87  
176 24         878 my @body;
177 24         53 my $offset = 0;
178            
179             # don't know why but this block makes long polling tests to pass
180 24 0 33     83 if ($mojo_res->content->is_dynamic && $mojo_res->content->{delay}) {
181 0         0 $mojo_res->get_body_chunk(0);
182             }
183            
184 24         343 while (length(my $chunk = $mojo_res->get_body_chunk($offset))) {
185 24         1419 push(@body, $chunk);
186 24         85 $offset += length $chunk;
187             }
188 24         1535 return [$status, \@headers, \@body];
189             }
190            
191             ### ---
192             ### load mw class
193             ### ---
194             sub _load_class {
195 20     20   53 my($class, $prefix) = @_;
196            
197 20 50       60 if ($prefix) {
198 20 50 33     224 unless ($class =~ s/^\+// || $class =~ /^$prefix/) {
199 20         77 $class = "$prefix\::$class";
200             }
201             }
202 20 100       231 if ($class->can('call')) {
203 16         50 return $class;
204             }
205 4         11 my $file = $class;
206 4         20 $file =~ s!::!/!g;
207 4         2176 require "$file.pm"; ## no critic
208            
209 4         14924 return $class;
210             }
211              
212              
213             ### ---
214             ### Error Handler
215             ### ---
216             package Mojolicious::Plugin::PlackMiddleware::_EH;
217 6     6   76 use Mojo::Base -base;
  6         14  
  6         40  
218            
219             __PACKAGE__->attr('handler');
220            
221             sub new {
222 23     23   73 my ($class, $handler) = @_;
223 23         98 my $self = $class->SUPER::new;
224 23         201 $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   1741 use strict;
  6         14  
  6         184  
237 6     6   51 use warnings;
  6         15  
  6         220  
238 6     6   36 use parent qw(Plack::Middleware::Conditional);
  6         11  
  6         46  
239            
240             sub call {
241 29     29   703 my($self, $env) = @_;
242 29         122 my $cond = $self->condition;
243 29 100 100     234 if (! $cond || $cond->($env->{'mojo.c'}, $env)) {
244 28         615 return $self->middleware->($env);
245             } else {
246 1         23 return $self->app->($env);
247             }
248             }
249            
250             ### ---
251             ### PSGI Input handler
252             ### ---
253             package Mojolicious::Plugin::PlackMiddleware::_PSGIInput;
254 6     6   15298 use strict;
  6         69  
  6         139  
255 6     6   43 use warnings;
  6         15  
  6         1423  
256            
257             sub new {
258 27     27   3858 my ($class, $content) = @_;
259 27         111 return bless [$content, 0, length($content)], $class;
260             }
261            
262             sub read {
263 13     13   3498 my $self = shift;
264 13   100     64 my $offset = ($_[2] || $self->[1]);
265 13 50       43 if ($offset <= $self->[2]) {
266 13 100       51 if ($_[0] = substr($self->[0], $offset, $_[1])) {
267 10         24 $self->[1] = $offset + length($_[0]);
268 10         26 return 1;
269             }
270             }
271             }
272              
273             1;
274              
275             __END__