From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS30633 207.244.64.0/18 X-Spam-Status: No, score=-1.8 required=3.0 tests=BAYES_00,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_ZBI,RCVD_IN_XBL,RDNS_NONE,SPF_FAIL,SPF_HELO_FAIL, TO_EQ_FM_DOM_SPF_FAIL shortcircuit=no autolearn=no autolearn_force=no version=3.4.0 Received: from 80x24.org (unknown [207.244.70.35]) by dcvr.yhbt.net (Postfix) with ESMTP id D097E202AC for ; Wed, 5 Jul 2017 11:24:41 +0000 (UTC) From: Eric Wong To: spew@80x24.org Subject: [PATCH] net/http: allow existing socket arg for Net::HTTP.start Date: Wed, 5 Jul 2017 11:24:39 +0000 Message-Id: <20170705112439.22359-1-e@80x24.org> List-Id: This allows Net::HTTP users to use Unix domain sockets or any other type of stream socket, including TCP. Several HTTP servers (e.g. nginx and puma) support listening on local Unix sockets instead of TCP. * lib/net/http.rb (HTTP.start): handle :socket option (initialize): @live_socket defaults to nil (connect): only connect if @live_socket is missing (do_finish): remove reference to @live_socket * test/net/http/test_http.rb (test_socket_arg_unix): new test --- lib/net/http.rb | 36 +++++++++++++++++++++--------------- test/net/http/test_http.rb | 20 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index d632020458..6e1bddcb50 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -576,10 +576,13 @@ def HTTP.socket_type #:nodoc: obsolete # # _opt_ sets following values by its accessor. # The keys are ca_file, ca_path, cert, cert_store, ciphers, - # close_on_empty_response, key, open_timeout, read_timeout, ssl_timeout, - # ssl_version, use_ssl, verify_callback, verify_depth and verify_mode. + # close_on_empty_response, key, open_timeout, read_timeout, socket, + # ssl_timeout, ssl_version, use_ssl, verify_callback, verify_depth + # and verify_mode. # If you set :use_ssl as true, you can use https and default value of # verify_mode is set as OpenSSL::SSL::VERIFY_PEER. + # :socket may be any connected Socket object, including UNIXSocket + # for connecting to nginx or similar. # # If the optional block is given, the newly # created Net::HTTP object is passed to it and closed when the @@ -604,6 +607,7 @@ def HTTP.start(address, *arg, &block) # :yield: +http+ opt.key?(key) or next http.__send__(meth, opt[key]) end + http.instance_variable_set(:@live_socket, opt[:socket]) end http.start(&block) @@ -666,6 +670,7 @@ def initialize(address, port = nil) @last_communicated = nil @close_on_empty_response = false @socket = nil + @live_socket = nil @started = false @open_timeout = 60 @read_timeout = 60 @@ -898,18 +903,19 @@ def connect conn_address = address conn_port = port end - - D "opening connection to #{conn_address}:#{conn_port}..." - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { - begin - TCPSocket.open(conn_address, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_address}:#{conn_port} (#{e.message})" - end - } - s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) - D "opened" + unless s = @live_socket + D "opening connection to #{conn_address}:#{conn_port}..." + s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { + begin + TCPSocket.open(conn_address, conn_port, @local_host, @local_port) + rescue => e + raise e, "Failed to open TCP connection to " + + "#{conn_address}:#{conn_port} (#{e.message})" + end + } + s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + D "opened" + end if use_ssl? if proxy? plain_sock = BufferedIO.new(s, read_timeout: @read_timeout, @@ -980,7 +986,7 @@ def finish def do_finish @started = false @socket.close if @socket - @socket = nil + @live_socket = @socket = nil end private :do_finish diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index 9f766c5722..5ea74b7833 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -262,6 +262,26 @@ def test_s_start assert_equal false, h.instance_variable_get(:@proxy_from_env) end + def test_socket_arg_unix + UNIXSocket.pair do |c,s| + th = Thread.new do + req = ''.b + req << s.readpartial(128) until req.include?("\r\n\r\n") + s.write("HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/plain\r\n" \ + "Content-Length: 2\r\n\r\n") + s.close + req + end + Net::HTTP.start('example.com', 80, socket: c) do |http| + res = http.head('/') + assert_instance_of Net::HTTPOK, res + end + assert_match %r{\AHEAD / HTTP/1\.1\r\n}, th.value + assert_predicate c, :closed? + end + end if defined?(UNIXSocket) + def test_s_get assert_equal $test_net_http_data, Net::HTTP.get(config('host'), '/', config('port')) -- EW