File Coverage

blib/lib/Plack/Middleware/Proxy/Connect/IO.pm
Criterion Covered Total %
statement 35 78 44.8
branch 0 22 0.0
condition 0 9 0.0
subroutine 12 15 80.0
pod 2 2 100.0
total 49 126 38.8


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             =for markdown ```perl
10              
11             # In app.psgi
12             use Plack::Builder;
13             use Plack::App::Proxy;
14              
15             builder {
16             enable "Proxy::Connect::IO", timeout => 30;
17             enable "Proxy::Requests";
18             Plack::App::Proxy->new->to_app;
19             };
20              
21             =for markdown ```
22              
23             =head1 DESCRIPTION
24              
25             This middleware handles the C method. It allows to connect to
26             C addresses.
27              
28             The middleware runs on servers supporting C and provides own
29             event loop so does not work correctly with C servers.
30              
31             The middleware uses only Perl's core modules: L and
32             L.
33              
34             =for readme stop
35              
36             =cut
37              
38 2     2   199597 use 5.006;
  2         9  
39              
40 2     2   11 use strict;
  2         4  
  2         39  
41 2     2   10 use warnings;
  2         3  
  2         109  
42              
43             our $VERSION = '0.0303';
44              
45 2     2   397 use parent qw(Plack::Middleware);
  2         238  
  2         9  
46              
47 2         6 use Plack::Util::Accessor qw(
48             timeout
49 2     2   14592 );
  2         5  
50              
51 2     2   529 use IO::Socket::INET;
  2         12092  
  2         13  
52 2     2   1576 use IO::Select;
  2         2793  
  2         82  
53 2     2   12 use Socket qw(IPPROTO_TCP TCP_NODELAY);
  2         5  
  2         167  
54              
55 2     2   11 use constant CHUNKSIZE => 64 * 1024;
  2         4  
  2         139  
56 2     2   19 use constant DEFAULT_TIMEOUT => 60;
  2         10  
  2         81  
57 2     2   11 use constant READ_TIMEOUT => 0.5;
  2         4  
  2         98  
58 2     2   11 use constant WRITE_TIMEOUT => 0.5;
  2         4  
  2         1007  
59              
60             sub prepare_app {
61 0     0 1   my ($self) = @_;
62              
63             # the default values
64 0 0         $self->timeout(DEFAULT_TIMEOUT) unless defined $self->timeout;
65             }
66              
67             sub call {
68 0     0 1   my ($self, $env) = @_;
69              
70             return $self->app->($env)
71 0 0         unless $env->{REQUEST_METHOD} eq 'CONNECT';
72              
73             return [501, [], ['']]
74 0 0 0       unless $env->{'psgi.streaming'} and $env->{'psgix.io'};
75              
76             return sub {
77 0     0     my ($respond) = @_;
78              
79 0           my $client = $env->{'psgix.io'};
80              
81 0           my ($host, $port) = $env->{REQUEST_URI} =~ m{^(?:.+\@)?(.+?)(?::(\d+))?$};
82              
83 0           my $remote = IO::Socket::INET->new(
84             PeerAddr => $host,
85             PeerPort => $port,
86             Blocking => 0,
87             Timeout => $self->timeout,
88             );
89              
90 0 0         if (!$remote) {
91 0 0         if ($! eq 'Operation timed out') {
92 0           return $respond->([504, [], ['']]);
93             } else {
94 0           return $respond->([502, [], ['']]);
95             }
96             }
97              
98 0           $client->blocking(0);
99              
100             # missing on Android
101 0 0         if (eval { TCP_NODELAY }) {
  0            
102 0           $client->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
103 0           $remote->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
104             }
105              
106 0           my $ioset = IO::Select->new;
107              
108 0           $ioset->add($client);
109 0           $ioset->add($remote);
110              
111 0           my $writer = $respond->([200, []]);
112              
113 0           my $bufin = '';
114 0           my $bufout = '';
115              
116 0           IOLOOP: while (1) {
117 0           for my $socket ($ioset->can_read(READ_TIMEOUT)) {
118 0           my $read = $socket->sysread(my $chunk, CHUNKSIZE);
119              
120 0 0         if ($read) {
121 0 0         if ($socket == $client) {
    0          
122 0           $bufout .= $chunk;
123             } elsif ($socket == $remote) {
124 0           $bufin .= $chunk;
125             }
126             } else {
127 0           $client->syswrite($bufin);
128 0           $client->close;
129 0           $remote->syswrite($bufout);
130 0           $remote->close;
131 0           last IOLOOP;
132             }
133             }
134              
135 0           for my $socket ($ioset->can_write(WRITE_TIMEOUT)) {
136 0 0 0       if ($socket == $client and length $bufin) {
    0 0        
137 0           my $write = $socket->syswrite($bufin);
138 0           substr $bufin, 0, $write, '';
139             } elsif ($socket == $remote and length $bufout) {
140 0           my $write = $socket->syswrite($bufout);
141 0           substr $bufout, 0, $write, '';
142             }
143             }
144             }
145 0           };
146             }
147              
148             1;
149              
150             =head1 CONFIGURATION
151              
152             =over 4
153              
154             =item timeout
155              
156             Timeout for the socket. The default value is C<60> seconds.
157              
158             =back
159              
160             =for readme continue
161              
162             =head1 SEE ALSO
163              
164             L, L, L.
165              
166             =head1 BUGS
167              
168             If you find the bug or want to implement new features, please report it at
169             L
170              
171             The code repository is available at
172             L
173              
174             =head1 AUTHOR
175              
176             Piotr Roszatycki
177              
178             =head1 LICENSE
179              
180             Copyright (c) 2014, 2016, 2023 Piotr Roszatycki .
181              
182             This is free software; you can redistribute it and/or modify it under
183             the same terms as perl itself.
184              
185             See L