File Coverage

lib/Mojolicious/Plugin/PlackMiddleware.pm
Criterion Covered Total %
statement 157 161 97.5
branch 24 32 75.0
condition 10 17 58.8
subroutine 28 29 96.5
pod 5 5 100.0
total 224 244 91.8


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