File Coverage

blib/lib/LWP/Protocol/UWSGI.pm
Criterion Covered Total %
statement 27 227 11.8
branch 0 138 0.0
condition 0 61 0.0
subroutine 9 14 64.2
pod 1 1 100.0
total 37 441 8.3


line stmt bran cond sub pod time code
1             package LWP::Protocol::UWSGI;
2              
3 1     1   21042 use strict;
  1         2  
  1         24  
4 1     1   921 use utf8;
  1         10  
  1         6  
5              
6 1     1   757 use version; our $VERSION = qv('v1.1.6');
  1         2171  
  1         5  
7              
8 1     1   8091 use HTTP::Response qw( );
  1         49286  
  1         32  
9 1     1   885 use LWP::Protocol::http qw( );
  1         68685  
  1         29  
10 1     1   889 use Encode;
  1         10613  
  1         83  
11 1     1   869 use URI::Escape::XS qw();
  1         5004  
  1         92  
12              
13 1     1   17 use base qw/LWP::Protocol::http/;
  1         5  
  1         3649  
14              
15             LWP::Protocol::implementor($_, __PACKAGE__) for qw( uwsgi );
16              
17             our $CRLF = $LWP::Protocol::http::CRLF;
18              
19             =head1 NAME
20              
21             LWP::Protocol::UWSGI - uwsgi support for LWP
22              
23             =head1 SYNOPSIS
24              
25             use LWP::Protocol::UWSGI;
26             use LWP::UserAgent;
27             $res = $ua->get("uwsgi://www.example.com");
28              
29             =head1 DESCRIPTION
30              
31             The LWP::Protocol::UWSGI module provide support for using uwsgi
32             protocol with LWP.
33              
34             This module unbundled with the libwww-perl.
35              
36             =head1 SEE ALSO
37              
38             L, L
39              
40             =head1 COPYRIGHT
41              
42             Copyright 2015 Nikolas Shulyakovskiy.
43              
44             This library is free software; you can redistribute it and/or
45             modify it under the same terms as Perl itself.
46              
47             =cut
48              
49             sub request {
50 0     0 1   my($self, $request, $proxy, $arg, $size, $timeout) = @_;
51              
52 0   0       $size ||= 4096;
53              
54             # check method
55 0           my $method = $request->method;
56 0 0         unless ($method =~ /^[A-Za-z0-9_!\#\$%&\'*+\-.^\`|~]+$/) { # HTTP token
57 0           return HTTP::Response->new( &HTTP::Status::RC_BAD_REQUEST,
58             'Library does not allow method ' .
59             "$method for 'uwsgi:' URLs");
60             }
61              
62 0           my $url = $request->uri;
63 0 0         unless($$url =~ m,^uwsgi://([^/?\#:]+)(?:\:(\d+))/,){
64 0           return HTTP::Response->new( &HTTP::Status::RC_BAD_REQUEST,
65             'Library does not allow this host ' .
66             "$$url for 'uwsgi:' URLs");
67             }
68 0 0         my ($host,$port) = $proxy
69             ? ($proxy->host,$proxy->port)
70             : ($1,$2);
71             my $fullpath =
72             $method eq 'CONNECT'
73             ? $url->host . ":" . $url->port
74             : $proxy
75             ? $url->as_string
76 0 0         : do {
    0          
77 0           my $path = $url->path_query;
78 0 0         $path = "/$path" if $path !~m{^/};
79 0           $path
80             };
81              
82 0           my $socket;
83 0           my $conn_cache = $self->{ua}{conn_cache};
84 0           my $cache_key;
85 0 0         if ( $conn_cache ) {
86 0           $cache_key = "$host:$port";
87 0 0         if ( $socket = $conn_cache->withdraw($self->socket_type,$cache_key)) {
88 0 0         if ($socket->can_read(0)) {
89             # if the socket is readable, then either the peer has closed the
90             # connection or there are some garbage bytes on it. In either
91             # case we abandon it.
92 0           $socket->close;
93 0           $socket = undef;
94             } # else use $socket
95             }
96             }
97              
98 0 0         if ( ! $socket ) {
99             # connect to remote site w/o reusing established socket
100 0           $socket = $self->_new_socket($host, $port, $timeout );
101             }
102              
103 0           $self->_check_sock($request, $socket);
104              
105 0           my %h;
106 0           my $request_headers = $request->headers->clone;
107 0           $self->_fixup_header($request_headers, $url, $proxy);
108              
109             $request_headers->scan(sub {
110 0     0     my($k, $v) = @_;
111 0           $k =~ s/^://;
112 0           $v =~ s/\n/ /g;
113 0           $h{$k}=$v;
114 0           });
115              
116 0           my $content_ref = $request->content_ref;
117 0 0         $content_ref = $$content_ref if ref($$content_ref);
118 0           my $chunked;
119             my $has_content;
120              
121 0 0         if (ref($content_ref) eq 'CODE') {
122 0           my $clen = $request_headers->header('Content-Length');
123 0 0         $has_content++ if $clen;
124 0 0         unless (defined $clen) {
125 0           $h{"Transfer-Encoding"} = "chunked";
126 0           $has_content++;
127 0           $chunked++;
128             }
129             }
130             else {
131             # Set (or override) Content-Length header
132 0           my $clen = $request_headers->header('Content-Length');
133 0 0 0       if (defined($$content_ref) && length($$content_ref)) {
    0          
134 0           $has_content = length($$content_ref);
135 0 0 0       if (!defined($clen) || $clen ne $has_content) {
136 0 0         if (defined $clen) {
137 0           warn "Content-Length header value was wrong, fixed";
138 0           delete $h{'Content-Length'};
139             }
140 0           $h{'Content-Length'} = $has_content;
141             }
142             }
143             elsif ($clen) {
144 0           warn "Content-Length set when there is no content, fixed";
145 0           delete $h{'Content-Length'};
146             }
147             }
148              
149 0           my $env = {};
150 0 0         $env->{QUERY_STRING} = $fullpath =~ m,^[^?]+\?(.+)$, ? $1 : '';
151 0           $env->{REQUEST_METHOD} = $method;
152 0 0         $env->{CONTENT_LENGTH} = defined $request_headers->header('Content-Length') ? $request_headers->header('Content-Length') : '';
153 0 0         $env->{CONTENT_TYPE} = $method =~ /post/i ? 'application/x-www-form-urlencoded' : '';
154 0           $env->{REQUEST_URI} = $fullpath;
155 0           $env->{PATH_INFO} = $url->path;
156 0           $env->{SERVER_PROTOCOL}= 'HTTP/1.1';
157 0           $env->{REMOTE_ADDR} = $socket->sockhost;
158 0           $env->{REMOTE_PORT} = $socket->sockport;
159 0           $env->{SERVER_PORT} = $port;
160 0           $env->{SERVER_NAME} = $host;
161              
162 0 0         if ($request->header('X-UWSGI-Nginx-Compatible-Mode')) {
163             $env->{PATH_INFO} = URI::Escape::XS::uri_unescape(
164             $env->{PATH_INFO}
165 0           );
166             }
167            
168 0           foreach my $k (keys %h) {
169 0           (my $env_k = uc $k) =~ tr/-/_/;
170 0 0         $env->{"HTTP_$env_k"} = defined $h{$k} ? $h{$k} : '';
171             }
172              
173 0           my $data = '';
174 0           foreach my $k (sort keys %$env) {
175 0 0         die "Undef value found for $k" unless defined $env->{$k};
176 0           $data .= pack 'v/a*v/a*', map { Encode::encode('utf8', $_) } $k, $env->{$k};
  0            
177             }
178              
179 0           my $req_buf = pack('C1v1C1',
180             5, # PSGI_MODIFIER1,
181             length($data),
182             0, # PSGI_MODIFIER2,
183             ) . $data;
184              
185 0 0 0       if (!$has_content || $has_content > 8*1024) {
186             WRITE:
187             {
188             # Since this just writes out the header block it should almost
189             # always succeed to send the whole buffer in a single write call.
190 0           my $n = $socket->syswrite($req_buf, length($req_buf));
  0            
191 0 0         unless (defined $n) {
192 0 0         redo WRITE if $!{EINTR};
193 0 0 0       if ($!{EWOULDBLOCK} || $!{EAGAIN}) {
194 0           select(undef, undef, undef, 0.1);
195 0           redo WRITE;
196             }
197 0           die "write failed: $!";
198             }
199 0 0         if ($n) {
200 0           substr($req_buf, 0, $n, "");
201             }
202             else {
203 0           select(undef, undef, undef, 0.5);
204             }
205 0 0         redo WRITE if length $req_buf;
206             }
207             }
208              
209 0           my($code, $mess, @junk);
210 0           my $drop_connection;
211              
212 0 0         if ($has_content) {
213 0           my $eof;
214             my $wbuf;
215 0           my $woffset = 0;
216             INITIAL_READ:
217 0 0         if (ref($content_ref) eq 'CODE') {
218 0           my $buf = &$content_ref();
219 0 0         $buf = "" unless defined($buf);
220 0 0         $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
221             if $chunked;
222 0 0         substr($buf, 0, 0) = $req_buf if $req_buf;
223 0           $wbuf = \$buf;
224             }
225             else {
226 0 0         if ($req_buf) {
227 0           my $buf = $req_buf . $$content_ref;
228 0           $wbuf = \$buf;
229             }
230             else {
231 0           $wbuf = $content_ref;
232             }
233 0           $eof = 1;
234             }
235              
236 0           my $fbits = '';
237 0           vec($fbits, fileno($socket), 1) = 1;
238              
239             WRITE:
240 0           while ($woffset < length($$wbuf)) {
241 0           my $sel_timeout = $timeout;
242 0           my $time_before;
243 0 0         $time_before = time if $sel_timeout;
244              
245 0           my $rbits = $fbits;
246 0           my $wbits = $fbits;
247 0           my $sel_timeout_before = $sel_timeout;
248             SELECT:
249             {
250 0           my $nfound = select($rbits, $wbits, undef, $sel_timeout);
  0            
251 0 0         if ($nfound < 0) {
252 0 0 0       if ($!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN}) {
      0        
253 0 0         if ($time_before) {
254 0           $sel_timeout = $sel_timeout_before - (time - $time_before);
255 0 0         $sel_timeout = 0 if $sel_timeout < 0;
256             }
257 0           redo SELECT;
258             }
259 0           die "select failed: $!";
260             }
261             }
262              
263 0 0 0       if (defined($rbits) && $rbits =~ /[^\0]/) {
264             # readable
265 0           my $buf = $socket->_rbuf;
266 0           my $n = $socket->sysread($buf, 1024, length($buf));
267 0 0         unless (defined $n) {
    0          
268 0 0 0       die "read failed: $!" unless $!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN};
      0        
269             # if we get here the rest of the block will do nothing
270             # and we will retry the read on the next round
271             }
272             elsif ($n == 0) {
273             # the server closed the connection before we finished
274             # writing all the request content. No need to write any more.
275 0           $drop_connection++;
276 0           last WRITE;
277             }
278 0           $socket->_rbuf($buf);
279 0           my @h;
280 0 0 0       if (!$code && $buf =~ /\015?\012\015?\012/) {
281             # a whole response header is present, so we can read it without blocking
282 0           ($code, $mess, @h) = $socket->read_response_headers( laxed => 1, junk_out => \@junk );
283 0 0         if ($code eq "100") {
284 0           undef($code);
285 0           goto INITIAL_READ;
286             }
287             else {
288 0           $drop_connection++;
289 0           last WRITE;
290             # XXX should perhaps try to abort write in a nice way too
291             }
292             }
293             }
294 0 0 0       if (defined($wbits) && $wbits =~ /[^\0]/) {
295 0           my $n = $socket->syswrite($$wbuf, length($$wbuf), $woffset);
296 0 0         unless (defined $n) {
    0          
297 0 0 0       die "write failed: $!" unless $!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN};
      0        
298 0           $n = 0; # will retry write on the next round
299             }
300             elsif ($n == 0) {
301 0           die "write failed: no bytes written";
302             }
303 0           $woffset += $n;
304              
305 0 0 0       if (!$eof && $woffset >= length($$wbuf)) {
306             # need to refill buffer from $content_ref code
307 0           my $buf = &$content_ref();
308 0 0         $buf = "" unless defined($buf);
309 0 0         $eof++ unless length($buf);
310 0 0         $buf = sprintf "%x%s%s%s", length($buf), $CRLF, $buf, $CRLF
311             if $chunked;
312 0           $wbuf = \$buf;
313 0           $woffset = 0;
314             }
315             }
316             } # WRITE
317             }
318            
319 0           my @h;
320 0 0         ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
321             unless $code;
322 0 0         ($code, $mess, @h) = $socket->read_response_headers(laxed => 1, junk_out => \@junk)
323             if $code eq "100";
324              
325 0           my $response = HTTP::Response->new($code, $mess);
326 0           my $peer_http_version = $socket->peer_http_version;
327 0           $response->protocol("HTTP/$peer_http_version");
328             {
329 0           local $HTTP::Headers::TRANSLATE_UNDERSCORE;
  0            
330 0           $response->push_header(@h);
331             }
332 0 0         $response->push_header("Client-Junk" => \@junk) if @junk;
333              
334 0           $response->request($request);
335 0           $self->_get_sock_info($response, $socket);
336              
337 0 0         if ($method eq "CONNECT") {
338 0           $response->{client_socket} = $socket; # so it can be picked up
339 0           return $response;
340             }
341              
342 0 0         if (my @te = $response->remove_header('Transfer-Encoding')) {
343 0           $response->push_header('Client-Transfer-Encoding', \@te);
344             }
345 0           $response->push_header('Client-Response-Num', scalar $socket->increment_response_count);
346              
347 0           my $complete;
348             $response = $self->collect($arg, $response, sub {
349 0     0     my $buf = ""; #prevent use of uninitialized value in SSLeay.xs
350 0           my $n;
351             READ:
352             {
353 0           $n = $socket->read_entity_body($buf, $size);
  0            
354 0 0         unless (defined $n) {
355 0 0 0       redo READ if $!{EINTR} || $!{EWOULDBLOCK} || $!{EAGAIN} || $!{ENOTTY};
      0        
      0        
356 0           die "read failed: $!";
357             }
358 0 0         redo READ if $n == -1;
359             }
360 0 0         $complete++ if !$n;
361 0           return \$buf;
362 0           });
363 0 0         $drop_connection++ unless $complete;
364              
365 0           @h = $socket->get_trailers;
366 0 0         if (@h) {
367 0           local $HTTP::Headers::TRANSLATE_UNDERSCORE;
368 0           $response->push_header(@h);
369             }
370              
371             # keep-alive support
372 0 0         unless ($drop_connection) {
373 0 0         if ($cache_key) {
374 0   0       my %connection = map { (lc($_) => 1) }
  0            
375             split(/\s*,\s*/, ($response->header("Connection") || ""));
376 0 0 0       if (($peer_http_version eq "1.1" && !$connection{close}) || $connection{"keep-alive"}) {
      0        
377 0           $conn_cache->deposit($self->socket_type, $cache_key, $socket);
378             }
379             }
380             }
381 0           $response;
382             }
383              
384              
385             package LWP::Protocol::UWSGI::Socket;
386 1     1   6 use base qw(IO::Socket::INET Net::HTTP);
  1         2  
  1         194  
387              
388             sub ping {
389 0     0     my $self = shift;
390 0           !$self->can_read(0);
391             }
392              
393             sub increment_response_count {
394 0     0     my $self = shift;
395 0           return ++${*$self}{'myhttp_response_count'};
  0            
396             }
397              
398             1;