File Coverage

blib/lib/Plack/Middleware/Proxy/Connect/IO.pm
Criterion Covered Total %
statement 26 62 41.9
branch 0 18 0.0
condition 0 6 0.0
subroutine 9 11 81.8
pod 1 1 100.0
total 36 98 36.7


line stmt bran cond sub pod time code
1             package Plack::Middleware::Proxy::Connect::IO;
2              
3             =head1 NAME
4              
5             Plack::Middleware::Proxy::Connect::IO - CONNECT method
6              
7             =head1 SYNOPSIS
8              
9             # In app.psgi
10             use Plack::Builder;
11             use Plack::App::Proxy;
12              
13             builder {
14             enable "Proxy::Connect::IO";
15             enable "Proxy::Requests";
16             Plack::App::Proxy->new->to_app;
17             };
18              
19             =head1 DESCRIPTION
20              
21             This middleware handles the C method. It allows to connect to
22             C addresses.
23              
24             The middleware runs on servers supporting C and provides own
25             event loop so does not work correctly with C servers.
26              
27             The middleware uses only Perl's core modules: L and
28             L.
29              
30             =for readme stop
31              
32             =cut
33              
34              
35 2     2   189908 use 5.006;
  2         9  
36              
37 2     2   12 use strict;
  2         3  
  2         71  
38 2     2   11 use warnings;
  2         16  
  2         158  
39              
40             our $VERSION = '0.0200';
41              
42              
43 2     2   745 use parent qw(Plack::Middleware);
  2         409  
  2         21  
44              
45 2     2   27855 use IO::Socket::INET;
  2         23472  
  2         22  
46 2     2   3805 use IO::Select;
  2         4857  
  2         148  
47 2     2   18 use Socket qw(IPPROTO_TCP TCP_NODELAY);
  2         4  
  2         381  
48              
49              
50 2     2   16 use constant CHUNKSIZE => 64 * 1024;
  2         4  
  2         200  
51 2     2   14 use constant TIMEOUT => 0.5;
  2         5  
  2         1521  
52              
53              
54             sub call {
55 0     0 1   my ($self, $env) = @_;
56              
57 0 0         return $self->app->($env) unless $env->{REQUEST_METHOD} eq 'CONNECT';
58              
59 0 0         my $client = $env->{'psgix.io'}
60             or return [501, [], ['Not implemented CONNECT method']];
61              
62 0           my ($host, $port) = $env->{REQUEST_URI} =~ m{^(?:.+\@)?(.+?)(?::(\d+))?$};
63              
64 0           my $ioset = IO::Select->new;
65              
66             sub {
67 0     0     my ($respond) = @_;
68              
69 0 0         my $remote = IO::Socket::INET->new(
70             PeerAddr => $host,
71             PeerPort => $port,
72             Blocking => 0,
73             ) or return $respond->([502, [], ['Bad Gateway']]);
74              
75 0           my $writer = $respond->([200, []]);
76              
77 0           $client->blocking(0);
78              
79             # missing on Android
80 0 0         if (eval { TCP_NODELAY }) {
  0            
81 0           $client->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
82 0           $remote->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
83             }
84              
85 0           $ioset->add($client);
86 0           $ioset->add($remote);
87              
88 0           my $bufin = '';
89 0           my $bufout = '';
90              
91 0           IOLOOP: while (1) {
92 0           for my $socket ($ioset->can_read(TIMEOUT)) {
93 0           my $read = $socket->sysread(my $chunk, CHUNKSIZE);
94              
95 0 0         if ($read) {
96 0 0         if ($socket == $client) {
    0          
97 0           $bufout .= $chunk;
98             } elsif ($socket == $remote) {
99 0           $bufin .= $chunk;
100             }
101             } else {
102 0           $client->syswrite($bufin);
103 0           $client->close;
104 0           $remote->syswrite($bufout);
105 0           $remote->close;
106 0           last IOLOOP;
107             }
108             }
109              
110 0           for my $socket ($ioset->can_write(TIMEOUT)) {
111 0 0 0       if ($socket == $client and length $bufin) {
    0 0        
112 0           my $write = $socket->syswrite($bufin);
113 0           substr $bufin, 0, $write, '';
114             } elsif ($socket == $remote and length $bufout) {
115 0           my $write = $socket->syswrite($bufout);
116 0           substr $bufout, 0, $write, '';
117             }
118             }
119             }
120              
121 0           };
122             }
123              
124              
125             1;
126              
127              
128             =for readme continue
129              
130             =head1 SEE ALSO
131              
132             L, L, L.
133              
134             =head1 BUGS
135              
136             If you find the bug or want to implement new features, please report it at
137             L
138              
139             The code repository is available at
140             L
141              
142             =head1 AUTHOR
143              
144             Piotr Roszatycki
145              
146             =head1 LICENSE
147              
148             Copyright (c) 2014, 2016 Piotr Roszatycki .
149              
150             This is free software; you can redistribute it and/or modify it under
151             the same terms as perl itself.
152              
153             See L