File Coverage

blib/lib/LWP/Protocol/UWSGI.pm
Criterion Covered Total %
statement 24 222 10.8
branch 0 136 0.0
condition 0 61 0.0
subroutine 8 13 61.5
pod 1 1 100.0
total 33 433 7.6


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