Plack-Middleware-Proxy-Connect-IO

 view release on metacpan or  search on metacpan

lib/Plack/Middleware/Proxy/Connect/IO.pm  view on Meta::CPAN


    builder {
        enable "Proxy::Connect::IO", timeout => 30;
        enable "Proxy::Requests";
        Plack::App::Proxy->new->to_app;
    };

=for markdown ```

=head1 DESCRIPTION

This middleware handles the C<CONNECT> method. It allows to connect to
C<https> addresses.

The middleware runs on servers supporting C<psgix.io> and provides own
event loop so does not work correctly with C<psgi.nonblocking> servers.

The middleware uses only Perl's core modules: L<IO::Socket::INET> and
L<IO::Select>.

=for readme stop

=cut

use 5.006;

use strict;
use warnings;

our $VERSION = '0.0305';

use parent qw(Plack::Middleware);

use Plack::Util::Accessor qw(
    timeout
);

use IO::Socket::INET;
use IO::Select;
use Socket qw(IPPROTO_TCP TCP_NODELAY);

use constant CHUNKSIZE       => 64 * 1024;
use constant DEFAULT_TIMEOUT => 60;
use constant READ_TIMEOUT    => 0.5;
use constant WRITE_TIMEOUT   => 0.5;

sub prepare_app {
    my ($self) = @_;

    # the default values
    $self->timeout(DEFAULT_TIMEOUT) unless defined $self->timeout;
}

sub call {
    my ($self, $env) = @_;

    return $self->app->($env)
        unless $env->{REQUEST_METHOD} eq 'CONNECT';

    return [501, [], ['']]
        unless $env->{'psgi.streaming'} and $env->{'psgix.io'};

    return sub {
        my ($respond) = @_;

        my $client = $env->{'psgix.io'};

        my ($host, $port) = $env->{REQUEST_URI} =~ m{^(?:.+\@)?(.+?)(?::(\d+))?$};

        my $remote = IO::Socket::INET->new(
            PeerAddr => $host,
            PeerPort => $port,
            Blocking => 0,
            Timeout  => $self->timeout,
        );

        if (!$remote) {
            if ($! eq 'Operation timed out') {
                return $respond->([504, [], ['']]);
            } else {
                return $respond->([502, [], ['']]);
            }
        }

        $client->blocking(0);

        # missing on Android
        if (eval { TCP_NODELAY }) {
            $client->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
            $remote->setsockopt(IPPROTO_TCP, TCP_NODELAY, 1);
        }

        my $ioset = IO::Select->new;

        $ioset->add($client);
        $ioset->add($remote);

        my $writer = $respond->([200, []]);

        my $bufin = '';
        my $bufout = '';

    IOLOOP: while (1) {
            for my $socket ($ioset->can_read(READ_TIMEOUT)) {
                my $read = $socket->sysread(my $chunk, CHUNKSIZE);

                if ($read) {
                    if ($socket == $client) {
                        $bufout .= $chunk;
                    } elsif ($socket == $remote) {
                        $bufin .= $chunk;
                    }
                } else {
                    $client->syswrite($bufin);
                    $client->close;
                    $remote->syswrite($bufout);
                    $remote->close;
                    last IOLOOP;
                }
            }



( run in 0.752 second using v1.01-cache-2.11-cpan-39bf76dae61 )