Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
90 lines (66 sloc) 3.24 KB
use Net::HTTP::Interfaces;
use Net::HTTP::Utils;
# defaults
use Net::HTTP::Dialer;
use Net::HTTP::Response;
use Net::HTTP::Request;
use experimental :pack;
# Higher level HTTP transport for creating a custom HTTP::Client
# similar to ::GET and ::POST but made for reuse (connection caching and other state control)
class Net::HTTP::Transport does RoundTripper {
also does Net::HTTP::Dialer;
has %!connections;
has $!lock = Lock.new;
# mix in a proxy role and the host and request target url are set appropriately automatically
# method proxy { ::('Net::HTTP::URL').new("http://proxy-lord.org") }
method round-trip(Request:D $req, Response ::RESPONSE = Net::HTTP::Response --> Response) {
self.hijack($req);
# MAKE REQUEST
my $socket := $.get-socket($req);
$socket.write($req.?raw // $req.Str.encode);
my $status-line = $socket.get(:bin).decode('latin-1');
my @header-lines = $socket.lines(:bin).map({$_ or last})>>.decode('latin-1');
my %header andthen do { %header{hc(.[0])}.append(.[1].trim-leading) for @header-lines>>.split(':', 2) }
my $body = buf8.new;
my $buffer = do with %header<Content-Length> { +$_.[0] } // Inf;
my $chunked = do with %header<Transfer-Encoding> { .any ~~ /[:i chunked]/ } ?? True !! False;
$socket.supply(:$buffer, :$chunked).tap: { $body ~= $_ }, done => {
with %header<Connection> { .any ~~ /[:i close]/ ?? $socket.close !! $socket.release }
}
my $res = RESPONSE.new(:$status-line, :$body, :%header);
$res;
}
# no private multi methods :(
multi method hijack(Request:D $req) {
my $header := $req.header;
my $proxy = self.?proxy;
# set the host field to either an optional proxy's url host or the request's url host
$header<Host> = $proxy ?? $proxy.host !! $req.url.host;
# override any possible default start-line() method behavior of using a relative request target url if $proxy
$req does role :: { method path { $req.url.Str } } if $proxy;
# automatically handle content-length setting
$header<Content-Length> = !$req.body ?? 0 !! $req.body ~~ Blob ?? $req.body.bytes !! $req.body.encode.bytes;
$header<Connection> //= 'keep-alive';
}
method get-socket(Request:D $req) {
$!lock.protect({
my $connection;
my $usable := %!connections{$*THREAD}{~$req.header<Host>}{~$req.url.scheme};
if $usable -> $conns {
for $conns.grep(*.closing.not) -> $sock {
# don't wait too long for a new socket before moving on
next unless await Promise.anyof( $sock.promise, start { $ = Promise.in(3); False });
next if $sock.promise.status ~~ Broken;
last if $connection = $sock.init;
}
}
if $connection.not {
$connection = $.dial($req) but IO::Socket::HTTP;
$connection.init;
$usable.append($connection) unless $req.header<Connection>.any ~~ /[:i close]/;
}
$connection.closing = True if $req.header<Connection>.any ~~ /[:i close]/;
$connection;
});
}
}