diff --git a/.gitignore b/.gitignore index a9caac79..175bd9eb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/**/*.swp *.gem .bundle .config diff --git a/lib/httpx/channel.rb b/lib/httpx/channel.rb index 42827e07..b6f9c6ae 100644 --- a/lib/httpx/channel.rb +++ b/lib/httpx/channel.rb @@ -3,12 +3,12 @@ module HTTPX::Channel module_function - def by(uri) + def by(uri, *options) case uri.scheme when "http" - TCP.new(uri) + TCP.new(uri, *options) when "https" - TLS.new(uri) + SSL.new(uri, *options) else raise "#{uri.scheme}: unrecognized channel" end @@ -17,3 +17,4 @@ end require "httpx/channel/http2" require "httpx/channel/tcp" +require "httpx/channel/ssl" diff --git a/lib/httpx/channel/ssl.rb b/lib/httpx/channel/ssl.rb new file mode 100644 index 00000000..3ad55460 --- /dev/null +++ b/lib/httpx/channel/ssl.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "forwardable" +require "openssl" + +module HTTPX::Channel + class SSL < TCP + def initialize(uri, ssl = {}, **) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params(ssl) + ctx.alpn_protocols = %w[h2 http/1.1] if ctx.respond_to?(:alpn_protocols=) + ctx.alpn_select_cb = lambda do |pr| + pr.first unless pr.nil? || pr.empty? + end if ctx.respond_to?(:alpn_select_cb=) + super + @io = OpenSSL::SSL::SSLSocket.new(@io, ctx) + @io.hostname = uri.host + @io.sync_close = true + @io.connect # TODO: non-block variant missing + end + + def protocol + @io.alpn_protocol + end + + def send(request, &block) + if @processor.nil? + @processor = PROTOCOLS[protocol].new(@write_buffer) + @processor.on(:response, &block) + end + @processor.send(request) + end + + if OpenSSL::VERSION < "2.0.6" + # OpenSSL < 2.0.6 has a leak in the buffer destination data. + # It has been fixed as of 2.0.6: https://github.com/ruby/openssl/pull/153 + def dread(size = BUFFER_SIZE) + begin + loop do + @io.read_nonblock(size, @read_buffer) + @processor << @read_buffer + end + rescue IO::WaitReadable + # wait read/write + rescue EOFError + # EOF + throw(:close, self) + end + end + end + + private + + def perform_io + yield + rescue IO::WaitReadable, IO::WaitWritable + # wait read/write + rescue EOFError + # EOF + @io.close + end + + end +end diff --git a/lib/httpx/channel/tcp.rb b/lib/httpx/channel/tcp.rb index ed782d5f..2aff420c 100644 --- a/lib/httpx/channel/tcp.rb +++ b/lib/httpx/channel/tcp.rb @@ -13,25 +13,22 @@ module HTTPX::Channel BUFFER_SIZE = 1 << 16 - attr_reader :remote_ip, :remote_port + attr_reader :remote_ip, :remote_port, :protocol def_delegator :@io, :to_io - def initialize(uri) + def initialize(uri, *) @io = TCPSocket.new(uri.host, uri.port) _, @remote_port, _,@remote_ip = @io.peeraddr @read_buffer = +"" @write_buffer = +"" + @protocol = "h2" end def close @io.close end - def protocol - "h2" - end - def empty? @write_buffer.empty? end diff --git a/lib/httpx/connection.rb b/lib/httpx/connection.rb index d9caaed4..14f112d3 100644 --- a/lib/httpx/connection.rb +++ b/lib/httpx/connection.rb @@ -62,7 +62,7 @@ module HTTPX def close(channel) @channels.delete(channel) - @channel.close + channel.close end end end