Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #31 from lrowe/no_proxy

Support for no_proxy environment variable.
  • Loading branch information...
commit 7fae5b1231645b4e72533117726c0bf226e72fb8 2 parents 13b89f5 + ce23c7d
@drbrain authored
Showing with 132 additions and 3 deletions.
  1. +47 −3 lib/net/http/persistent.rb
  2. +85 −0 test/test_net_http_persistent.rb
View
50 lib/net/http/persistent.rb
@@ -328,6 +328,11 @@ def self.detect_idle_timeout uri, max = 10
attr_reader :proxy_uri
##
+ # List of host suffixes which will not be proxied
+
+ attr_reader :no_proxy
+
+ ##
# Seconds to wait until reading one block. See Net::HTTP#read_timeout
attr_accessor :read_timeout
@@ -429,6 +434,7 @@ def initialize name = nil, proxy = nil
@debug_output = nil
@proxy_uri = nil
+ @no_proxy = []
@headers = {}
@override_headers = {}
@http_versions = {}
@@ -546,7 +552,7 @@ def connection_for uri
net_http_args = [uri.host, uri.port]
connection_id = net_http_args.join ':'
- if @proxy_uri then
+ if @proxy_uri and not proxy_bypass? uri.host, uri.port then
connection_id << @proxy_connection_id
net_http_args.concat @proxy_args
end
@@ -755,8 +761,10 @@ def private_key= key
# If the proxy URI is set after requests have been made, the next request
# will shut-down and re-open all connections.
#
- # If you are making some requests through a proxy and others without a proxy
- # use separate Net::Http::Persistent instances.
+ # The +no_proxy+ query parameter can be used to specify hosts which shouldn't
+ # be reached via proxy; if set it should be a comma separated list of
+ # hostname suffixes, optionally with +:port+ appended, for example
+ # <tt>example.com,some.host:8080</tt>.
def proxy= proxy
@proxy_uri = case proxy
@@ -766,6 +774,8 @@ def proxy= proxy
else raise ArgumentError, 'proxy must be :ENV or a URI::HTTP'
end
+ @no_proxy.clear
+
if @proxy_uri then
@proxy_args = [
@proxy_uri.host,
@@ -775,6 +785,10 @@ def proxy= proxy
]
@proxy_connection_id = [nil, *@proxy_args].join ':'
+
+ if @proxy_uri.query then
+ @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
+ end
end
reconnect
@@ -790,6 +804,12 @@ def proxy= proxy
# indicated user and password unless HTTP_PROXY contains either of these in
# the URI.
#
+ # The +NO_PROXY+ ENV variable can be used to specify hosts which shouldn't
+ # be reached via proxy; if set it should be a comma separated list of
+ # hostname suffixes, optionally with +:port+ appended, for example
+ # <tt>example.com,some.host:8080</tt>. When set to <tt>*</tt> no proxy will
+ # be returned.
+ #
# For Windows users, lowercase ENV variables are preferred over uppercase ENV
# variables.
@@ -800,6 +820,15 @@ def proxy_from_env
uri = URI normalize_uri env_proxy
+ env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
+
+ # '*' is special case for always bypass
+ return nil if env_no_proxy == '*'
+
+ if env_no_proxy then
+ uri.query = "no_proxy=#{escape(env_no_proxy)}"
+ end
+
unless uri.user or uri.password then
uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
@@ -809,6 +838,21 @@ def proxy_from_env
end
##
+ # Returns true when proxy should by bypassed for host.
+
+ def proxy_bypass? host, port
+ host = host.downcase
+ host_port = [host, port].join ':'
+
+ @no_proxy.each do |name|
+ return true if host[-name.length, name.length] == name or
+ host_port[-name.length, name.length] == name
+ end
+
+ false
+ end
+
+ ##
# Forces reconnection of HTTP connections.
def reconnect
View
85 test/test_net_http_persistent.rb
@@ -48,6 +48,8 @@ def setup
ENV.delete 'HTTP_PROXY_USER'
ENV.delete 'http_proxy_pass'
ENV.delete 'HTTP_PROXY_PASS'
+ ENV.delete 'no_proxy'
+ ENV.delete 'NO_PROXY'
Net::HTTP::Persistent::SSLReuse.use_connect :test_connect
end
@@ -146,6 +148,8 @@ def touts
def test_initialize
assert_nil @http.proxy_uri
+ assert_empty @http.no_proxy
+
ssl_session_exists = OpenSSL::SSL.const_defined? :Session
assert_equal ssl_session_exists, @http.reuse_ssl_sessions
@@ -424,6 +428,23 @@ def test_connection_for_proxy
assert_same c, conns[1]['example.com:80:proxy.example:80:johndoe:muffins']
end
+ def test_connection_for_no_proxy
+ uri = URI.parse 'http://proxy.example'
+ uri.user = 'johndoe'
+ uri.password = 'muffins'
+ uri.query = 'no_proxy=example.com'
+
+ http = Net::HTTP::Persistent.new nil, uri
+
+ c = http.connection_for @uri
+
+ assert c.started?
+ refute c.proxy?
+
+ assert_includes conns[1].keys, 'example.com:80'
+ assert_same c, conns[1]['example.com:80']
+ end
+
def test_connection_for_refused
cached = basic_connection
def cached.start; raise Errno::ECONNREFUSED end
@@ -642,12 +663,14 @@ def test_proxy_from_env
ENV['HTTP_PROXY'] = 'proxy.example'
ENV['HTTP_PROXY_USER'] = 'johndoe'
ENV['HTTP_PROXY_PASS'] = 'muffins'
+ ENV['NO_PROXY'] = 'localhost,example.com'
uri = @http.proxy_from_env
expected = URI.parse 'http://proxy.example'
expected.user = 'johndoe'
expected.password = 'muffins'
+ expected.query = 'no_proxy=localhost%2Cexample.com'
assert_equal expected, uri
end
@@ -656,12 +679,14 @@ def test_proxy_from_env_lower
ENV['http_proxy'] = 'proxy.example'
ENV['http_proxy_user'] = 'johndoe'
ENV['http_proxy_pass'] = 'muffins'
+ ENV['no_proxy'] = 'localhost,example.com'
uri = @http.proxy_from_env
expected = URI.parse 'http://proxy.example'
expected.user = 'johndoe'
expected.password = 'muffins'
+ expected.query = 'no_proxy=localhost%2Cexample.com'
assert_equal expected, uri
end
@@ -678,6 +703,66 @@ def test_proxy_from_env_nil
assert_nil uri
end
+ def test_proxy_from_env_no_proxy_star
+ uri = @http.proxy_from_env
+
+ assert_nil uri
+
+ ENV['HTTP_PROXY'] = 'proxy.example'
+ ENV['NO_PROXY'] = '*'
+
+ uri = @http.proxy_from_env
+
+ assert_nil uri
+ end
+
+ def test_proxy_bypass
+ ENV['HTTP_PROXY'] = 'proxy.example'
+ ENV['NO_PROXY'] = 'localhost,example.com:80'
+
+ @http.proxy = :ENV
+
+ assert @http.proxy_bypass? 'localhost', 80
+ assert @http.proxy_bypass? 'localhost', 443
+ assert @http.proxy_bypass? 'LOCALHOST', 80
+ assert @http.proxy_bypass? 'example.com', 80
+ refute @http.proxy_bypass? 'example.com', 443
+ assert @http.proxy_bypass? 'www.example.com', 80
+ refute @http.proxy_bypass? 'www.example.com', 443
+ assert @http.proxy_bypass? 'endingexample.com', 80
+ refute @http.proxy_bypass? 'example.org', 80
+ end
+
+ def test_proxy_bypass_space
+ ENV['HTTP_PROXY'] = 'proxy.example'
+ ENV['NO_PROXY'] = 'localhost, example.com'
+
+ @http.proxy = :ENV
+
+ assert @http.proxy_bypass? 'example.com', 80
+ refute @http.proxy_bypass? 'example.org', 80
+ end
+
+ def test_proxy_bypass_trailing
+ ENV['HTTP_PROXY'] = 'proxy.example'
+ ENV['NO_PROXY'] = 'localhost,example.com,'
+
+ @http.proxy = :ENV
+
+ assert @http.proxy_bypass? 'example.com', 80
+ refute @http.proxy_bypass? 'example.org', 80
+ end
+
+ def test_proxy_bypass_double_comma
+ ENV['HTTP_PROXY'] = 'proxy.example'
+ ENV['NO_PROXY'] = 'localhost,,example.com'
+
+ @http.proxy = :ENV
+
+ assert @http.proxy_bypass? 'example.com', 80
+ refute @http.proxy_bypass? 'example.org', 80
+ end
+
def test_reconnect
result = @http.reconnect
Please sign in to comment.
Something went wrong with that request. Please try again.