├── .gemtest
├── .gitignore
├── .autotest
├── Manifest.txt
├── .travis.yml
├── Gemfile
├── lib
└── net
│ └── http
│ ├── persistent
│ ├── connection.rb
│ ├── pool.rb
│ └── timed_stack_multi.rb
│ └── persistent.rb
├── Rakefile
├── README.rdoc
├── test
├── test_net_http_persistent_timed_stack_multi.rb
└── test_net_http_persistent.rb
└── History.txt
/.gemtest:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | /.rdoc
3 | /TAGS
4 | /doc
5 | /pkg
6 | .idea
7 | /Gemfile.lock
8 |
--------------------------------------------------------------------------------
/.autotest:
--------------------------------------------------------------------------------
1 | # -*- ruby -*-
2 |
3 | require 'autotest/restart'
4 |
5 | Autotest.add_hook :initialize do |at|
6 | at.add_exception '.git'
7 | at.add_exception '.rdoc'
8 | end
9 |
10 |
--------------------------------------------------------------------------------
/Manifest.txt:
--------------------------------------------------------------------------------
1 | .autotest
2 | .gemtest
3 | .travis.yml
4 | Gemfile
5 | History.txt
6 | Manifest.txt
7 | README.rdoc
8 | Rakefile
9 | lib/net/http/persistent.rb
10 | lib/net/http/persistent/connection.rb
11 | lib/net/http/persistent/pool.rb
12 | lib/net/http/persistent/timed_stack_multi.rb
13 | test/test_net_http_persistent.rb
14 | test/test_net_http_persistent_timed_stack_multi.rb
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | after_script:
3 | - rake travis:after -t
4 | before_script:
5 | - gem install hoe-travis --no-document
6 | - rake travis:before -t
7 | language: ruby
8 | notifications:
9 | email:
10 | - drbrain@segment7.net
11 | rvm:
12 | - 2.3
13 | - 2.4
14 | - 2.5
15 | - 2.6
16 | - 2.7
17 | script: rake travis
18 | install: "" # avoid running default bundler install
19 |
20 | matrix:
21 | include:
22 | - rvm: "2.7"
23 | env: TRAVIS_MATRIX=pipeline
24 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # -*- ruby -*-
2 |
3 | # DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`.
4 |
5 | source "https://rubygems.org/"
6 |
7 | gem "connection_pool", "~>2.2"
8 |
9 | gem "minitest", "~>5.11", :group => [:development, :test]
10 | gem "hoe-bundler", "~>1.5", :group => [:development, :test]
11 | gem "hoe-travis", "~>1.4", ">=1.4.1", :group => [:development, :test]
12 | gem "rdoc", ">=4.0", "<7", :group => [:development, :test]
13 | gem "hoe", "~>3.17", :group => [:development, :test]
14 |
15 | # vim: syntax=ruby
16 |
--------------------------------------------------------------------------------
/lib/net/http/persistent/connection.rb:
--------------------------------------------------------------------------------
1 | ##
2 | # A Net::HTTP connection wrapper that holds extra information for managing the
3 | # connection's lifetime.
4 |
5 | class Net::HTTP::Persistent::Connection # :nodoc:
6 |
7 | attr_accessor :http
8 |
9 | attr_accessor :last_use
10 |
11 | attr_accessor :requests
12 |
13 | attr_accessor :ssl_generation
14 |
15 | def initialize http_class, http_args, ssl_generation
16 | @http = http_class.new(*http_args)
17 | @ssl_generation = ssl_generation
18 |
19 | reset
20 | end
21 |
22 | def finish
23 | @http.finish
24 | rescue IOError
25 | ensure
26 | reset
27 | end
28 |
29 | def reset
30 | @last_use = Net::HTTP::Persistent::EPOCH
31 | @requests = 0
32 | end
33 |
34 | def ressl ssl_generation
35 | @ssl_generation = ssl_generation
36 |
37 | finish
38 | end
39 |
40 | end
41 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # -*- ruby -*-
2 |
3 | require 'hoe'
4 |
5 | Hoe.plugin :bundler
6 | Hoe.plugin :git
7 | Hoe.plugin :minitest
8 | Hoe.plugin :travis
9 |
10 | Hoe.spec 'net-http-persistent' do
11 | developer 'Eric Hodel', 'drbrain@segment7.net'
12 |
13 | self.readme_file = 'README.rdoc'
14 | self.extra_rdoc_files += Dir['*.rdoc']
15 |
16 | self.require_ruby_version '>= 2.3'
17 |
18 | license 'MIT'
19 |
20 | rdoc_locations <<
21 | 'docs-push.seattlerb.org:/data/www/docs.seattlerb.org/net-http-persistent/'
22 |
23 | dependency 'connection_pool', '~> 2.2'
24 | dependency 'minitest', '~> 5.2', :development
25 | dependency 'hoe-bundler', '~> 1.5', :development
26 | dependency 'hoe-travis', ['~> 1.4', '>= 1.4.1'], :development
27 | dependency 'net-http-pipeline', '~> 1.0' if
28 | ENV['TRAVIS_MATRIX'] == 'pipeline'
29 | end
30 |
31 | # vim: syntax=Ruby
32 |
--------------------------------------------------------------------------------
/lib/net/http/persistent/pool.rb:
--------------------------------------------------------------------------------
1 | class Net::HTTP::Persistent::Pool < ConnectionPool # :nodoc:
2 |
3 | attr_reader :available # :nodoc:
4 | attr_reader :key # :nodoc:
5 |
6 | def initialize(options = {}, &block)
7 | super
8 |
9 | @available = Net::HTTP::Persistent::TimedStackMulti.new(@size, &block)
10 | @key = "current-#{@available.object_id}"
11 | end
12 |
13 | def checkin net_http_args
14 | stack = Thread.current[@key][net_http_args] ||= []
15 |
16 | raise ConnectionPool::Error, 'no connections are checked out' if
17 | stack.empty?
18 |
19 | conn = stack.pop
20 |
21 | if stack.empty?
22 | @available.push conn, connection_args: net_http_args
23 |
24 | Thread.current[@key].delete(net_http_args)
25 | Thread.current[@key] = nil if Thread.current[@key].empty?
26 | end
27 |
28 | nil
29 | end
30 |
31 | def checkout net_http_args
32 | stacks = Thread.current[@key] ||= {}
33 | stack = stacks[net_http_args] ||= []
34 |
35 | if stack.empty? then
36 | conn = @available.pop connection_args: net_http_args
37 | else
38 | conn = stack.last
39 | end
40 |
41 | stack.push conn
42 |
43 | conn
44 | end
45 |
46 | def shutdown
47 | Thread.current[@key] = nil
48 | super
49 | end
50 | end
51 |
52 | require_relative 'timed_stack_multi'
53 |
54 |
--------------------------------------------------------------------------------
/lib/net/http/persistent/timed_stack_multi.rb:
--------------------------------------------------------------------------------
1 | class Net::HTTP::Persistent::TimedStackMulti < ConnectionPool::TimedStack # :nodoc:
2 |
3 | ##
4 | # Returns a new hash that has arrays for keys
5 | #
6 | # Using a class method to limit the bindings referenced by the hash's
7 | # default_proc
8 |
9 | def self.hash_of_arrays # :nodoc:
10 | Hash.new { |h,k| h[k] = [] }
11 | end
12 |
13 | def initialize(size = 0, &block)
14 | super
15 |
16 | @enqueued = 0
17 | @ques = self.class.hash_of_arrays
18 | @lru = {}
19 | @key = :"connection_args-#{object_id}"
20 | end
21 |
22 | def empty?
23 | (@created - @enqueued) >= @max
24 | end
25 |
26 | def length
27 | @max - @created + @enqueued
28 | end
29 |
30 | private
31 |
32 | def connection_stored? options = {} # :nodoc:
33 | !@ques[options[:connection_args]].empty?
34 | end
35 |
36 | def fetch_connection options = {} # :nodoc:
37 | connection_args = options[:connection_args]
38 |
39 | @enqueued -= 1
40 | lru_update connection_args
41 | @ques[connection_args].pop
42 | end
43 |
44 | def lru_update connection_args # :nodoc:
45 | @lru.delete connection_args
46 | @lru[connection_args] = true
47 | end
48 |
49 | def shutdown_connections # :nodoc:
50 | @ques.each_key do |key|
51 | super connection_args: key
52 | end
53 | end
54 |
55 | def store_connection obj, options = {} # :nodoc:
56 | @ques[options[:connection_args]].push obj
57 | @enqueued += 1
58 | end
59 |
60 | def try_create options = {} # :nodoc:
61 | connection_args = options[:connection_args]
62 |
63 | if @created >= @max && @enqueued >= 1
64 | oldest, = @lru.first
65 | @lru.delete oldest
66 | @ques[oldest].pop
67 |
68 | @created -= 1
69 | end
70 |
71 | if @created < @max
72 | @created += 1
73 | lru_update connection_args
74 | return @create_block.call(connection_args)
75 | end
76 | end
77 |
78 | end
79 |
80 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = net-http-persistent
2 |
3 | home :: https://github.com/drbrain/net-http-persistent
4 | rdoc :: http://docs.seattlerb.org/net-http-persistent
5 |
6 | == DESCRIPTION:
7 |
8 | Manages persistent connections using Net::HTTP including a thread pool for
9 | connecting to multiple hosts.
10 |
11 | Using persistent HTTP connections can dramatically increase the speed of HTTP.
12 | Creating a new HTTP connection for every request involves an extra TCP
13 | round-trip and causes TCP congestion avoidance negotiation to start over.
14 |
15 | Net::HTTP supports persistent connections with some API methods but does not
16 | make setting up a single persistent connection or managing multiple
17 | connections easy. Net::HTTP::Persistent wraps Net::HTTP and allows you to
18 | focus on how to make HTTP requests.
19 |
20 | == FEATURES/PROBLEMS:
21 |
22 | * Supports TLS with secure defaults
23 | * Thread-safe
24 | * Pure ruby
25 |
26 | == SYNOPSIS
27 |
28 | The following example will make two requests to the same server. The
29 | connection is kept alive between requests:
30 |
31 | require 'net/http/persistent'
32 |
33 | uri = URI 'http://example.com/awesome/web/service'
34 |
35 | http = Net::HTTP::Persistent.new name: 'my_app_name'
36 |
37 | # perform a GET
38 | response = http.request uri
39 |
40 | # create a POST
41 | post_uri = uri + 'create'
42 | post = Net::HTTP::Post.new post_uri.path
43 | post.set_form_data 'some' => 'cool data'
44 |
45 | # perform the POST, the URI is always required
46 | response = http.request post_uri, post
47 |
48 | # if you are done making http requests, or won't make requests for several
49 | # minutes
50 | http.shutdown
51 |
52 | Please see the documentation on Net::HTTP::Persistent for more information,
53 | including SSL connection verification, header handling and tunable options.
54 |
55 | == INSTALL:
56 |
57 | gem install net-http-persistent
58 |
59 | == LICENSE:
60 |
61 | (The MIT License)
62 |
63 | Copyright (c) Eric Hodel, Aaron Patterson
64 |
65 | Permission is hereby granted, free of charge, to any person obtaining
66 | a copy of this software and associated documentation files (the
67 | 'Software'), to deal in the Software without restriction, including
68 | without limitation the rights to use, copy, modify, merge, publish,
69 | distribute, sublicense, and/or sell copies of the Software, and to
70 | permit persons to whom the Software is furnished to do so, subject to
71 | the following conditions:
72 |
73 | The above copyright notice and this permission notice shall be
74 | included in all copies or substantial portions of the Software.
75 |
76 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
77 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
78 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
79 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
80 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
81 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
82 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
83 |
--------------------------------------------------------------------------------
/test/test_net_http_persistent_timed_stack_multi.rb:
--------------------------------------------------------------------------------
1 | require 'minitest/autorun'
2 | require 'net/http/persistent'
3 |
4 | class TestNetHttpPersistentTimedStackMulti < Minitest::Test
5 |
6 | class Connection
7 | attr_reader :host
8 |
9 | def initialize(host)
10 | @host = host
11 | end
12 | end
13 |
14 | def setup
15 | @stack = Net::HTTP::Persistent::TimedStackMulti.new { Object.new }
16 | end
17 |
18 | def test_empty_eh
19 | stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
20 |
21 | refute_empty stack
22 |
23 | popped = stack.pop
24 |
25 | assert_empty stack
26 |
27 | stack.push connection_args: popped
28 |
29 | refute_empty stack
30 | end
31 |
32 | def test_length
33 | stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
34 |
35 | assert_equal 1, stack.length
36 |
37 | popped = stack.pop
38 |
39 | assert_equal 0, stack.length
40 |
41 | stack.push connection_args: popped
42 |
43 | assert_equal 1, stack.length
44 | end
45 |
46 | def test_pop
47 | object = Object.new
48 | @stack.push object
49 |
50 | popped = @stack.pop
51 |
52 | assert_same object, popped
53 | end
54 |
55 | def test_pop_empty
56 | e = assert_raises Timeout::Error do
57 | @stack.pop timeout: 0
58 | end
59 |
60 | assert_equal 'Waited 0 sec', e.message
61 | end
62 |
63 | def test_pop_full
64 | stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
65 |
66 | popped = stack.pop
67 |
68 | refute_nil popped
69 | assert_empty stack
70 | end
71 |
72 | def test_pop_wait
73 | thread = Thread.start do
74 | @stack.pop
75 | end
76 |
77 | Thread.pass while thread.status == 'run'
78 |
79 | object = Object.new
80 |
81 | @stack.push object
82 |
83 | assert_same object, thread.value
84 | end
85 |
86 | def test_pop_shutdown
87 | @stack.shutdown { }
88 |
89 | assert_raises ConnectionPool::PoolShuttingDownError do
90 | @stack.pop
91 | end
92 | end
93 |
94 | def test_push
95 | stack = Net::HTTP::Persistent::TimedStackMulti.new(1) { Object.new }
96 |
97 | conn = stack.pop
98 |
99 | stack.push connection_args: conn
100 |
101 | refute_empty stack
102 | end
103 |
104 | def test_push_shutdown
105 | called = []
106 |
107 | @stack.shutdown do |object|
108 | called << object
109 | end
110 |
111 | @stack.push connection_args: Object.new
112 |
113 | refute_empty called
114 | assert_empty @stack
115 | end
116 |
117 | def test_shutdown
118 | @stack.push connection_args: Object.new
119 |
120 | called = []
121 |
122 | @stack.shutdown do |object|
123 | called << object
124 | end
125 |
126 | refute_empty called
127 | assert_empty @stack
128 | end
129 |
130 | def test_pop_recycle
131 | stack = Net::HTTP::Persistent::TimedStackMulti.new(2) { |host| Connection.new(host) }
132 |
133 | a_conn = stack.pop connection_args: 'a.example'
134 | stack.push a_conn, connection_args: 'a.example'
135 |
136 | b_conn = stack.pop connection_args: 'b.example'
137 | stack.push b_conn, connection_args: 'b.example'
138 |
139 | c_conn = stack.pop connection_args: 'c.example'
140 |
141 | assert_equal 'c.example', c_conn.host
142 |
143 | stack.push c_conn, connection_args: 'c.example'
144 |
145 | recreated = stack.pop connection_args: 'a.example'
146 |
147 | refute_same a_conn, recreated
148 | end
149 |
150 | end
151 |
152 |
--------------------------------------------------------------------------------
/History.txt:
--------------------------------------------------------------------------------
1 | === 4.0.1 / 2021-01-12
2 |
3 | Bug fixes:
4 |
5 | * Loosen Ruby version requirement so Ruby 3.0 will work.
6 |
7 | === 4.0.0 / 2020-04-30
8 |
9 | Breaking changes:
10 |
11 | * Removed built-in support for retrying failed requests as Net::HTTP has this
12 | built-in for all supported versions. Pull request #100 by Michael Grosser.
13 | * Dropped support for EoL ruby versions (< 2.4). Future feature releases may
14 | drop support for ruby versions that are at end-of-life or in security-only
15 | maintenance mode with any release. Pull request #113 by David Rodríguez
16 |
17 | New features:
18 |
19 | * Added Net::HTTP::Persistent#max_retries= to configure the number of retries
20 | performed on a request for ruby versions that support it (2.5+).
21 | * URI-ness is determined through #respond_to? to allow compatibility with
22 | Addressable::URI. Pull request #67 by Ryan McKern.
23 | * Use require_relative to reduce patch burden for vendored versions. Pull
24 | Request #106 by David Rodríguez
25 |
26 | Bug fixes:
27 |
28 | * Stop wasting a connection when the keep-alive timeout is less than the idle
29 | timeout. Pull request #115 by Yap Sok Ann.
30 | * Improved use of URI#hostname for IPv6 connections. Pull request #76 by
31 | Tomas Koutsky.
32 | * Improved check for Process::RLIMIT_NOFILE support. Pull request #109 by Vít
33 | Ondruch.
34 | * Fix namespace in comments for escape/unescape wrappers. Pull request #114
35 | by David Rodríguez.
36 | * Fix History.txt timestamp for 3.0.0 release. Pull request #107 by Joe Van
37 | Dyk.
38 | * Fix link to PR #98 in 3.1.0 release notes. Pull request #110 by Justin
39 | Reid.
40 |
41 | Other:
42 |
43 | * Updated Net::HTTP::Persistent#reconnect documentation to indicate that all
44 | connections are reset. Issue #117 by Taisuke Miyazaki.
45 |
46 | === 3.1.0 / 2019-07-24
47 |
48 | New features:
49 | * Support ruby 2.6 Net::HTTP#write_timeout=. Pull request #99 by Víctor
50 | Roldán Betancort.
51 | * Support setting TLS min/max version. Pull request #94 by Bart.
52 |
53 | Bug fixes:
54 | * Reduce potential for memory leak through Hash default proc bindings. Pull
55 | request #64 by Dominic Metzger.
56 | * Test proxy auto detection from no_proxy in ENV. Pull request #60 by
57 | HINOHARA Hiroshi.
58 | * Add missing timestamp for 3.0 release. Pull request #78 by Joe Van Dyk.
59 | * Support IPv6 URLs in proxy checks. Pull request #82 by Nicolás Sanguinetti.
60 | * Use Net::HTTPGenericRequest#method to check idempotence for improved
61 | compatibility. Pull request #83 by Fotos Georgiadis.
62 | * Run net-http-pipeline tests in travis. Pull request #86 by T.J. Schuck.
63 | * Correct +no_proxy+ support to override Net::HTTP proxy fallback. Pull
64 | request #88 by Jared Kauppila.
65 | * Mitigate memory leak when combined with faraday. Pull request #105 by Yohei
66 | Kitamura.
67 | * Set default connection pool size for Windows. Pull request #90 by Jared
68 | Kauppila.
69 | * Fix missing +name:+ argument in documentation. Pull requests #85 by T.J.
70 | Schuck, #84 by James White.
71 | * Fix memory leak from connection pooling. Pull request #98 by Aaron
72 | Patterson.
73 | * Update tests for minitest assert_equal deprecation. Pull request #92 by
74 | Olle Jonsson.
75 | * Fix typo in Net::HTTP::Persistent#pipeline. Pull request #91 by Kazuma
76 | Furuhashi.
77 |
78 | Other:
79 | * Added bundler hoe plugin. Pull request #103 by Michael Grosser.
80 | * Updated ruby versions in Travis CI. Pull request #93 by Olle Jonsson. Pull
81 | request #103 by Michael Grosser.
82 |
83 | === 3.0 / 2016-10-05
84 |
85 | Breaking changes:
86 |
87 | * No longer supports ruby 2.0 and earlier
88 | * Net::HTTP::Persistent::new now uses keyword arguments for +name+ and
89 | +proxy+.
90 | * Removed #max_age, use #expired?
91 |
92 | New features:
93 |
94 | * Uses connection_pool to manage all connections for a Net::HTTP::Persistent
95 | instance.
96 |
97 | Bug fixes:
98 |
99 | * Add missing SSL options ca_path, ciphers, ssl_timeout, verify_depth.
100 | Issue #63 by Johnneylee Jack Rollins.
101 |
102 | === 2.9.4 / 2014-02-10
103 |
104 | * Bug fixes
105 | * Improve proxy escaping from 2.9.2. Pull request #59 by Mislav Marohnić.
106 |
107 | === 2.9.3 / 2014-02-06
108 |
109 | * Bug fixes
110 | * Fix breakage in 2.9.2 for users without proxies. Pull request #56 by
111 | Yoshihiro TAKAHARA (merged), #57 by ChuckLin, #58 by Kenny Meyer.
112 |
113 | === 2.9.2 / 2014-02-05
114 |
115 | * Bug fixes
116 | * Special characters in proxy passwords are now handled correctly. Issue
117 | #48 by Mislav Marohnić. Pull request #54 by Juha Kajava
118 |
119 | === 2.9.1 / 2014-01-22
120 |
121 | * Bug fixes
122 | * Added license to gemspec. Issue #47 by Benjamin Fleischer
123 | * Set Net::HTTP#keep_alive_timeout when supported by ruby. Pull request #53
124 | by Dylan Thacker-Smith.
125 | * The backtrace is preserved for errors in #reset to help with debugging.
126 | Issue #41 by Andrew Cholakian.
127 |
128 | === 2.9 / 2013-07-24
129 |
130 | * Minor enhancement
131 | * Added Net::HTTP::Persistent#max_requests to avoid ECONNRESET for a server
132 | that allows a limited number of requests on a connection. Pull request
133 | #42 by James Tucker.
134 | * Request failures are now raised with the backtrace of the original
135 | exception. This gives better insight into the reason for the failure.
136 | See #41 by Andrew Cholakian.
137 | * OpenSSL is no longer required. If OpenSSL is not available an exception
138 | will be raised when attempting to access HTTPS resources. Feature request
139 | by André Arko
140 |
141 | * Bug fixes
142 | * Explain the proper way of sending parameters depending upon the request
143 | method. Issue #35 by André Arko.
144 | * Handle Errno::ETIMEDOUT by retrying the request. Issue #36 by André Arko.
145 | * Requests retried by ruby 2.x are no longer retried by net-http-persistent.
146 | * Finish the connection if an otherwise unhandled exception happens during a
147 | request. Bug #46 by Mark Oude Veldhuis.
148 | * Net::HTTP::Persistent::detect_idle_timeout now assumes a StandardError
149 | indicates the idle timeout has been found. Bug #43 by James Tucker.
150 |
151 | === 2.8 / 2012-10-17
152 |
153 | * Minor enhancements
154 | * Added Net::HTTP::Persistent::detect_idle_timeout which can be used to
155 | determine the idle timeout for a host.
156 | * The read timeout may now be updated for every request. Issue #33 by
157 | Mislav Marohnić
158 | * Added NO_PROXY support. Pull Request #31 by Laurence Rowe.
159 | * Added #cert and #key aliases for Net::HTTP compatibility. Pull request
160 | #26 by dlee.
161 | * The artifice gem now disables SSL session reuse to prevent breakage of
162 | testing frameworks. Pull Request #29 by Christopher Cooke.
163 | * Disabled Net::HTTP::Persistent::SSLReuse on Ruby 2+. This feature is now
164 | built-in to Net::HTTP.
165 | * Bug fixes
166 | * Socket options are set again following connection reset. Pull request #28
167 | by cmaion.
168 | * #shutdown now works even if no connections were made. Pull Request #24 by
169 | James Tucker.
170 | * Updated test RSA key size to 1024 bits. Bug #25 by Gunnar Wolf.
171 | * The correct host:port are shown in the exception when a proxy connection
172 | fails. Bug #30 by glebtv.
173 |
174 | === 2.7 / 2012-06-06
175 |
176 | * Minor enhancement
177 | * Added JRuby compatibility by default for HTTPS connections. (JRuby lacks
178 | OpenSSL::SSL::Session.)
179 |
180 | === 2.6 / 2012-03-26
181 |
182 | * Minor enhancement
183 | * Net::HTTP::Persistent#idle_timeout may be set to nil to disable expiration
184 | of connections. Pull Request #21 by Aaron Stone
185 |
186 | === 2.5.2 / 2012-02-13
187 |
188 | * Bug fix
189 | * Fix variable shadowing warning.
190 |
191 | === 2.5.1 / 2012-02-10
192 |
193 | * Bug fix
194 | * Reusing SSL connections with HTTP proxies now works. Issue #15 by Paul
195 | Ingham and mcrmfc
196 |
197 | === 2.5 / 2012-02-07
198 |
199 | * Minor enhancements
200 | * The proxy may be changed at any time.
201 | * The allowed SSL version may now be set via #ssl_version.
202 | Issue #16 by astera
203 | * Added Net::HTTP::Persistent#override_headers which allows overriding
204 | * Net::HTTP default headers like User-Agent. See
205 | Net::HTTP::Persistent@Headers for details. Issue #17 by andkerosine
206 |
207 | * Bug fixes
208 | * The ruby 1.8 speed monkeypatch now handles EAGAIN for windows users.
209 | Issue #12 by Alwyn Schoeman
210 | * Fixed POST example in README. Submitted by injekt.
211 | * Fixed various bugs in the shutdown of connections especially cross-thread
212 | (which you shouldn't be doing anyways).
213 |
214 | === 2.4.1 / 2012-02-03
215 |
216 | * Bug fixes
217 | * When FakeWeb or WebMock are loaded SSL sessions will not be reused to
218 | prevent breakage of testing frameworks. Issue #13 by Matt Brictson, pull
219 | request #14 by Zachary Scott
220 | * SSL connections are reset when the SSL parameters change.
221 | Mechanize issue #194 by dsisnero
222 | * Last-use times are now cleaned up in #shutdown.
223 |
224 | === 2.4 / 2012-01-31
225 |
226 | * Minor enhancement
227 | * net-http-persistent now complains if OpenSSL::SSL::VERIFY_PEER is equal to
228 | OpenSSL::SSL::VERIFY_NONE. If you have a platform that is broken this way
229 | you must define the constant:
230 |
231 | I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil
232 |
233 | at the top level of your application to disable the warning.
234 |
235 | * Bug fix
236 | * Fix persisting SSL sessions through HTTP proxies. Mechanize issue #178 by
237 | Robert Poor, net-http-persistent issues #10, #11.
238 |
239 | === 2.3.2 / 2011-12-21
240 |
241 | * Bug fix
242 | * Finish connections that were closed by Net::HTTP so they can be restarted.
243 |
244 | === 2.3.1 / 2011-10-26
245 |
246 | * Bug fix
247 | * If a request object already contains a Connection header it will no longer
248 | be overridden. This allows keep-alive connections to be disabled on a
249 | per-request basis.
250 |
251 | === 2.3 / 2011-10-25
252 |
253 | * Minor Enhancement
254 | * The time since last use for a connection is now recorded in error
255 | messages for the connection.
256 |
257 | === 2.2 / 2011-10-24
258 |
259 | * Minor Enhancements
260 | * Added timeouts for idle connections which are set through #idle_timeout.
261 | The default timeout is 5 seconds. Reducing the idle timeout is preferred
262 | over setting #retry_change_requests to true if you wish to avoid the "too
263 | many connection resets" error when POSTing data.
264 | * Documented tunables and settings in one place in Net::HTTP::Persistent
265 |
266 | === 2.1 / 2011-09-19
267 |
268 | * Minor Enhancement
269 | * For HTTPS connections, SSL sessions are now reused avoiding the extra
270 | round trips and computations of extra SSL handshakes. If you have
271 | problems with SSL session reuse it can be disabled by
272 | Net::HTTP::Persistent#reuse_ssl_sessions
273 |
274 | * Bug Fixes
275 | * The default certificate store is now used even if #verify_mode was not
276 | set. Issue #7, Pull Request #8 by Matthew M. Boedicker
277 |
278 | === 2.0 / 2011-08-26
279 |
280 | * Incompatibility
281 | * Net::HTTP::Persistent#verify_mode now defaults to
282 | OpenSSL::SSL::VERIFY_PEER. This may cause HTTPS request failures if your
283 | default certificate store lacks the correct certificates.
284 |
285 | === 1.9 / 2011-08-26
286 |
287 | * Minor Enhancement
288 | * Added Net::HTTP::Persistent#cert_store to set an SSL certificate store
289 | which defaults to the OpenSSL default certificate store.
290 |
291 | HTTPS server certificates will be validated when this option is combined
292 | with setting Net::HTTP::Persistent#verify_mode to
293 | OpenSSL::SSL::VERIFY_PEER.
294 |
295 | === 1.8.1 / 2011-08-08
296 |
297 | * Bug Fix
298 | * Requests with OpenSSL errors are retried now. Pull Request #5 by James
299 | Tucker.
300 |
301 | === 1.8 / 2011-06-27
302 |
303 | * Minor Enhancement
304 | * Added Net::HTTP::Persistent#retry_change_requests which allows POST and
305 | other non-idempotent requests to be retried automatically. Take care when
306 | enabling this option to ensure the server will handle multiple POSTs with
307 | the same data in a sane manner.
308 |
309 | === 1.7 / 2011-04-17
310 |
311 | * Minor Enhancement
312 | * Added Net::HTTP::Persistent#pipeline which integrates with
313 | net-http-pipeline when it is present.
314 | * Bug Fix
315 | * Perform a case-insensitive check of the URI scheme for HTTPS URIs
316 |
317 | === 1.6.1 / 2011-03-08
318 |
319 | * Bug Fix
320 | * Net::HTTP::Persistent#request now handles Errno::EINVAL as a connection
321 | reset and will be retried for idempotent requests. Reported by Aaron
322 | Qian.
323 |
324 | === 1.6 / 2011-03-01
325 |
326 | * Minor Enhancement
327 | * Added Net::HTTP::Persistent#socket_options to set multiple socket options
328 | at socket startup.
329 |
330 | === 1.5.2 / 2011-02-24
331 |
332 | * Bug Fix
333 | * Only set TCP_NODELAY if the connection has an @socket. Allows
334 | net-http-persistent to be used with fake_web. Reported by Sathish
335 | Pasupunuri.
336 |
337 | === 1.5.1 / 2011-02-10
338 |
339 | * Bug fix
340 | * Only set TCP_NODELAY at connection start. Reported by Brian Henderson.
341 |
342 | === 1.5 / 2011-01-25
343 |
344 | * Minor Enhancements
345 | * Set TCP_NODELAY on created socket if possible. (This will only help for
346 | requests that send bodies.)
347 |
348 | === 1.4.1 / 2010-10-13
349 |
350 | * Bug Fixes
351 | * Don't finish the connection when we're retrying, reset it. Patch by James
352 | Tucker.
353 |
354 | === 1.4 / 2010-09-29
355 |
356 | * Minor Enhancements
357 | * Added the very dangerous #shutdown_in_all_threads. IT IS DANGEROUS!.
358 | Patch by Torsten Schönebaum.
359 |
360 | === 1.3.1 / 2010-09-13
361 |
362 | * Bug Fixes
363 | * #connection_for no longer tries to ssl-enable an existing connection.
364 | Patch by Joseph West.
365 |
366 | === 1.3 / 2010-09-08
367 |
368 | * Minor Enhancements
369 | * HTTP versions are now recorded. This information is not currently used.
370 |
371 | * Bug Fixes
372 | * #shutdown no longer fails when an unstarted HTTP connection is shut down.
373 |
374 | === 1.2.5 / 2010-07-27
375 |
376 | * Bug Fixes
377 | * Fix duplicated test name. Noted by Peter Higgins.
378 | * #shutdown now works even when no connections were made.
379 |
380 | === 1.2.4 / 2010-07-26
381 |
382 | * Bug Fixes
383 | * Actually have #request only finish a connection. Somehow this got
384 | missed.
385 |
386 | === 1.2.3 / 2010-06-29
387 |
388 | * Bug Fixes
389 | * Fix example code (pointed out by Alex Stahl)
390 |
391 | === 1.2.2 / 2010-06-22
392 |
393 | * Bug Fixes
394 | * #request only finishes a connection instead of restarting it. This helps
395 | prevents errors on non-idempotent HTTP requests after errors.
396 | * #connection_for handles EHOSTDOWN like #reset
397 |
398 | === 1.2.1 / 2010-05-25
399 |
400 | * Bug Fixes
401 | * Don't alter Net::BufferedIO#rbuf_fill on 1.9+
402 |
403 | === 1.2 / 2010-05-20
404 |
405 | * Minor Enhancements
406 | * Net::HTTP#read_timeout is now supported
407 | * Net::HTTP#open_timeout is now supported
408 | * Net::HTTP::Persistent#request now supports a block like Net::HTTP#request
409 |
410 | === 1.1 / 2010-05-18
411 |
412 | * Minor Enhancements
413 | * Proxy support, see Net::HTTP::Persistent::new,
414 | Net::HTTP::Persistent#proxy_from_env
415 | * Added +name+ parameter to Net::HTTP::Persistent::new for separation of
416 | connection pools.
417 | * Added Net::HTTP::Persistent#shutdown so you can clean up after yourself
418 | * Net::HTTP::Persistent now suppresses "peer certificate won't be verified
419 | in this SSL session" warning.
420 |
421 | * Bug Fixes
422 | * Net::HTTP::Persistent retries requests in accordance with RFC 2616.
423 |
424 | === 1.0.1 / 2010-05-05
425 |
426 | * Minor Enhancements
427 | * Added #debug_output
428 | * Now uses Hoe minitest plugin
429 | * Bug Fixes
430 | * Tests pass on 1.9
431 |
432 | === 1.0.0 / 2010-05-04
433 |
434 | * Major Enhancements
435 | * Birthday!
436 |
437 |
--------------------------------------------------------------------------------
/lib/net/http/persistent.rb:
--------------------------------------------------------------------------------
1 | require 'net/http'
2 | require 'uri'
3 | require 'cgi' # for escaping
4 | require 'connection_pool'
5 |
6 | begin
7 | require 'net/http/pipeline'
8 | rescue LoadError
9 | end
10 |
11 | autoload :OpenSSL, 'openssl'
12 |
13 | ##
14 | # Persistent connections for Net::HTTP
15 | #
16 | # Net::HTTP::Persistent maintains persistent connections across all the
17 | # servers you wish to talk to. For each host:port you communicate with a
18 | # single persistent connection is created.
19 | #
20 | # Connections will be shared across threads through a connection pool to
21 | # increase reuse of connections.
22 | #
23 | # You can shut down any remaining HTTP connections when done by calling
24 | # #shutdown.
25 | #
26 | # Example:
27 | #
28 | # require 'net/http/persistent'
29 | #
30 | # uri = URI 'http://example.com/awesome/web/service'
31 | #
32 | # http = Net::HTTP::Persistent.new
33 | #
34 | # # perform a GET
35 | # response = http.request uri
36 | #
37 | # # or
38 | #
39 | # get = Net::HTTP::Get.new uri.request_uri
40 | # response = http.request get
41 | #
42 | # # create a POST
43 | # post_uri = uri + 'create'
44 | # post = Net::HTTP::Post.new post_uri.path
45 | # post.set_form_data 'some' => 'cool data'
46 | #
47 | # # perform the POST, the URI is always required
48 | # response http.request post_uri, post
49 | #
50 | # Note that for GET, HEAD and other requests that do not have a body you want
51 | # to use URI#request_uri not URI#path. The request_uri contains the query
52 | # params which are sent in the body for other requests.
53 | #
54 | # == TLS/SSL
55 | #
56 | # TLS connections are automatically created depending upon the scheme of the
57 | # URI. TLS connections are automatically verified against the default
58 | # certificate store for your computer. You can override this by changing
59 | # verify_mode or by specifying an alternate cert_store.
60 | #
61 | # Here are the TLS settings, see the individual methods for documentation:
62 | #
63 | # #certificate :: This client's certificate
64 | # #ca_file :: The certificate-authorities
65 | # #ca_path :: Directory with certificate-authorities
66 | # #cert_store :: An SSL certificate store
67 | # #ciphers :: List of SSl ciphers allowed
68 | # #private_key :: The client's SSL private key
69 | # #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new
70 | # connection
71 | # #ssl_timeout :: Session lifetime
72 | # #ssl_version :: Which specific SSL version to use
73 | # #verify_callback :: For server certificate verification
74 | # #verify_depth :: Depth of certificate verification
75 | # #verify_mode :: How connections should be verified
76 | #
77 | # == Proxies
78 | #
79 | # A proxy can be set through #proxy= or at initialization time by providing a
80 | # second argument to ::new. The proxy may be the URI of the proxy server or
81 | # :ENV which will consult environment variables.
82 | #
83 | # See #proxy= and #proxy_from_env for details.
84 | #
85 | # == Headers
86 | #
87 | # Headers may be specified for use in every request. #headers are appended to
88 | # any headers on the request. #override_headers replace existing headers on
89 | # the request.
90 | #
91 | # The difference between the two can be seen in setting the User-Agent. Using
92 | # http.headers['User-Agent'] = 'MyUserAgent' will send "Ruby,
93 | # MyUserAgent" while http.override_headers['User-Agent'] =
94 | # 'MyUserAgent' will send "MyUserAgent".
95 | #
96 | # == Tuning
97 | #
98 | # === Segregation
99 | #
100 | # Each Net::HTTP::Persistent instance has its own pool of connections. There
101 | # is no sharing with other instances (as was true in earlier versions).
102 | #
103 | # === Idle Timeout
104 | #
105 | # If a connection hasn't been used for this number of seconds it will
106 | # automatically be reset upon the next use to avoid attempting to send to a
107 | # closed connection. The default value is 5 seconds. nil means no timeout.
108 | # Set through #idle_timeout.
109 | #
110 | # Reducing this value may help avoid the "too many connection resets" error
111 | # when sending non-idempotent requests while increasing this value will cause
112 | # fewer round-trips.
113 | #
114 | # === Read Timeout
115 | #
116 | # The amount of time allowed between reading two chunks from the socket. Set
117 | # through #read_timeout
118 | #
119 | # === Max Requests
120 | #
121 | # The number of requests that should be made before opening a new connection.
122 | # Typically many keep-alive capable servers tune this to 100 or less, so the
123 | # 101st request will fail with ECONNRESET. If unset (default), this value has
124 | # no effect, if set, connections will be reset on the request after
125 | # max_requests.
126 | #
127 | # === Open Timeout
128 | #
129 | # The amount of time to wait for a connection to be opened. Set through
130 | # #open_timeout.
131 | #
132 | # === Socket Options
133 | #
134 | # Socket options may be set on newly-created connections. See #socket_options
135 | # for details.
136 | #
137 | # === Connection Termination
138 | #
139 | # If you are done using the Net::HTTP::Persistent instance you may shut down
140 | # all the connections in the current thread with #shutdown. This is not
141 | # recommended for normal use, it should only be used when it will be several
142 | # minutes before you make another HTTP request.
143 | #
144 | # If you are using multiple threads, call #shutdown in each thread when the
145 | # thread is done making requests. If you don't call shutdown, that's OK.
146 | # Ruby will automatically garbage collect and shutdown your HTTP connections
147 | # when the thread terminates.
148 |
149 | class Net::HTTP::Persistent
150 |
151 | ##
152 | # The beginning of Time
153 |
154 | EPOCH = Time.at 0 # :nodoc:
155 |
156 | ##
157 | # Is OpenSSL available? This test works with autoload
158 |
159 | HAVE_OPENSSL = defined? OpenSSL::SSL # :nodoc:
160 |
161 | ##
162 | # The default connection pool size is 1/4 the allowed open files
163 | # (ulimit -n) or 256 if your OS does not support file handle
164 | # limits (typically windows).
165 |
166 | if Process.const_defined? :RLIMIT_NOFILE
167 | DEFAULT_POOL_SIZE = Process.getrlimit(Process::RLIMIT_NOFILE).first / 4
168 | else
169 | DEFAULT_POOL_SIZE = 256
170 | end
171 |
172 | ##
173 | # The version of Net::HTTP::Persistent you are using
174 |
175 | VERSION = '4.0.1'
176 |
177 | ##
178 | # Error class for errors raised by Net::HTTP::Persistent. Various
179 | # SystemCallErrors are re-raised with a human-readable message under this
180 | # class.
181 |
182 | class Error < StandardError; end
183 |
184 | ##
185 | # Use this method to detect the idle timeout of the host at +uri+. The
186 | # value returned can be used to configure #idle_timeout. +max+ controls the
187 | # maximum idle timeout to detect.
188 | #
189 | # After
190 | #
191 | # Idle timeout detection is performed by creating a connection then
192 | # performing a HEAD request in a loop until the connection terminates
193 | # waiting one additional second per loop.
194 | #
195 | # NOTE: This may not work on ruby > 1.9.
196 |
197 | def self.detect_idle_timeout uri, max = 10
198 | uri = URI uri unless URI::Generic === uri
199 | uri += '/'
200 |
201 | req = Net::HTTP::Head.new uri.request_uri
202 |
203 | http = new 'net-http-persistent detect_idle_timeout'
204 |
205 | http.connection_for uri do |connection|
206 | sleep_time = 0
207 |
208 | http = connection.http
209 |
210 | loop do
211 | response = http.request req
212 |
213 | $stderr.puts "HEAD #{uri} => #{response.code}" if $DEBUG
214 |
215 | unless Net::HTTPOK === response then
216 | raise Error, "bad response code #{response.code} detecting idle timeout"
217 | end
218 |
219 | break if sleep_time >= max
220 |
221 | sleep_time += 1
222 |
223 | $stderr.puts "sleeping #{sleep_time}" if $DEBUG
224 | sleep sleep_time
225 | end
226 | end
227 | rescue
228 | # ignore StandardErrors, we've probably found the idle timeout.
229 | ensure
230 | return sleep_time unless $!
231 | end
232 |
233 | ##
234 | # This client's OpenSSL::X509::Certificate
235 |
236 | attr_reader :certificate
237 |
238 | ##
239 | # For Net::HTTP parity
240 |
241 | alias cert certificate
242 |
243 | ##
244 | # An SSL certificate authority. Setting this will set verify_mode to
245 | # VERIFY_PEER.
246 |
247 | attr_reader :ca_file
248 |
249 | ##
250 | # A directory of SSL certificates to be used as certificate authorities.
251 | # Setting this will set verify_mode to VERIFY_PEER.
252 |
253 | attr_reader :ca_path
254 |
255 | ##
256 | # An SSL certificate store. Setting this will override the default
257 | # certificate store. See verify_mode for more information.
258 |
259 | attr_reader :cert_store
260 |
261 | ##
262 | # The ciphers allowed for SSL connections
263 |
264 | attr_reader :ciphers
265 |
266 | ##
267 | # Sends debug_output to this IO via Net::HTTP#set_debug_output.
268 | #
269 | # Never use this method in production code, it causes a serious security
270 | # hole.
271 |
272 | attr_accessor :debug_output
273 |
274 | ##
275 | # Current connection generation
276 |
277 | attr_reader :generation # :nodoc:
278 |
279 | ##
280 | # Headers that are added to every request using Net::HTTP#add_field
281 |
282 | attr_reader :headers
283 |
284 | ##
285 | # Maps host:port to an HTTP version. This allows us to enable version
286 | # specific features.
287 |
288 | attr_reader :http_versions
289 |
290 | ##
291 | # Maximum time an unused connection can remain idle before being
292 | # automatically closed.
293 |
294 | attr_accessor :idle_timeout
295 |
296 | ##
297 | # Maximum number of requests on a connection before it is considered expired
298 | # and automatically closed.
299 |
300 | attr_accessor :max_requests
301 |
302 | ##
303 | # Number of retries to perform if a request fails.
304 | #
305 | # See also #max_retries=, Net::HTTP#max_retries=.
306 |
307 | attr_reader :max_retries
308 |
309 | ##
310 | # The value sent in the Keep-Alive header. Defaults to 30. Not needed for
311 | # HTTP/1.1 servers.
312 | #
313 | # This may not work correctly for HTTP/1.0 servers
314 | #
315 | # This method may be removed in a future version as RFC 2616 does not
316 | # require this header.
317 |
318 | attr_accessor :keep_alive
319 |
320 | ##
321 | # The name for this collection of persistent connections.
322 |
323 | attr_reader :name
324 |
325 | ##
326 | # Seconds to wait until a connection is opened. See Net::HTTP#open_timeout
327 |
328 | attr_accessor :open_timeout
329 |
330 | ##
331 | # Headers that are added to every request using Net::HTTP#[]=
332 |
333 | attr_reader :override_headers
334 |
335 | ##
336 | # This client's SSL private key
337 |
338 | attr_reader :private_key
339 |
340 | ##
341 | # For Net::HTTP parity
342 |
343 | alias key private_key
344 |
345 | ##
346 | # The URL through which requests will be proxied
347 |
348 | attr_reader :proxy_uri
349 |
350 | ##
351 | # List of host suffixes which will not be proxied
352 |
353 | attr_reader :no_proxy
354 |
355 | ##
356 | # Test-only accessor for the connection pool
357 |
358 | attr_reader :pool # :nodoc:
359 |
360 | ##
361 | # Seconds to wait until reading one block. See Net::HTTP#read_timeout
362 |
363 | attr_accessor :read_timeout
364 |
365 | ##
366 | # Seconds to wait until writing one block. See Net::HTTP#write_timeout
367 |
368 | attr_accessor :write_timeout
369 |
370 | ##
371 | # By default SSL sessions are reused to avoid extra SSL handshakes. Set
372 | # this to false if you have problems communicating with an HTTPS server
373 | # like:
374 | #
375 | # SSL_connect [...] read finished A: unexpected message (OpenSSL::SSL::SSLError)
376 |
377 | attr_accessor :reuse_ssl_sessions
378 |
379 | ##
380 | # An array of options for Socket#setsockopt.
381 | #
382 | # By default the TCP_NODELAY option is set on sockets.
383 | #
384 | # To set additional options append them to this array:
385 | #
386 | # http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
387 |
388 | attr_reader :socket_options
389 |
390 | ##
391 | # Current SSL connection generation
392 |
393 | attr_reader :ssl_generation # :nodoc:
394 |
395 | ##
396 | # SSL session lifetime
397 |
398 | attr_reader :ssl_timeout
399 |
400 | ##
401 | # SSL version to use.
402 | #
403 | # By default, the version will be negotiated automatically between client
404 | # and server. Ruby 1.9 and newer only. Deprecated since Ruby 2.5.
405 |
406 | attr_reader :ssl_version
407 |
408 | ##
409 | # Minimum SSL version to use, e.g. :TLS1_1
410 | #
411 | # By default, the version will be negotiated automatically between client
412 | # and server. Ruby 2.5 and newer only.
413 |
414 | attr_reader :min_version
415 |
416 | ##
417 | # Maximum SSL version to use, e.g. :TLS1_2
418 | #
419 | # By default, the version will be negotiated automatically between client
420 | # and server. Ruby 2.5 and newer only.
421 |
422 | attr_reader :max_version
423 |
424 | ##
425 | # Where this instance's last-use times live in the thread local variables
426 |
427 | attr_reader :timeout_key # :nodoc:
428 |
429 | ##
430 | # SSL verification callback. Used when ca_file or ca_path is set.
431 |
432 | attr_reader :verify_callback
433 |
434 | ##
435 | # Sets the depth of SSL certificate verification
436 |
437 | attr_reader :verify_depth
438 |
439 | ##
440 | # HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER which verifies
441 | # the server certificate.
442 | #
443 | # If no ca_file, ca_path or cert_store is set the default system certificate
444 | # store is used.
445 | #
446 | # You can use +verify_mode+ to override any default values.
447 |
448 | attr_reader :verify_mode
449 |
450 | ##
451 | # Creates a new Net::HTTP::Persistent.
452 | #
453 | # Set a +name+ for fun. Your library name should be good enough, but this
454 | # otherwise has no purpose.
455 | #
456 | # +proxy+ may be set to a URI::HTTP or :ENV to pick up proxy options from
457 | # the environment. See proxy_from_env for details.
458 | #
459 | # In order to use a URI for the proxy you may need to do some extra work
460 | # beyond URI parsing if the proxy requires a password:
461 | #
462 | # proxy = URI 'http://proxy.example'
463 | # proxy.user = 'AzureDiamond'
464 | # proxy.password = 'hunter2'
465 | #
466 | # Set +pool_size+ to limit the maximum number of connections allowed.
467 | # Defaults to 1/4 the number of allowed file handles or 256 if your OS does
468 | # not support a limit on allowed file handles. You can have no more than
469 | # this many threads with active HTTP transactions.
470 |
471 | def initialize name: nil, proxy: nil, pool_size: DEFAULT_POOL_SIZE
472 | @name = name
473 |
474 | @debug_output = nil
475 | @proxy_uri = nil
476 | @no_proxy = []
477 | @headers = {}
478 | @override_headers = {}
479 | @http_versions = {}
480 | @keep_alive = 30
481 | @open_timeout = nil
482 | @read_timeout = nil
483 | @write_timeout = nil
484 | @idle_timeout = 5
485 | @max_requests = nil
486 | @max_retries = 1
487 | @socket_options = []
488 | @ssl_generation = 0 # incremented when SSL session variables change
489 |
490 | @socket_options << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
491 | Socket.const_defined? :TCP_NODELAY
492 |
493 | @pool = Net::HTTP::Persistent::Pool.new size: pool_size do |http_args|
494 | Net::HTTP::Persistent::Connection.new Net::HTTP, http_args, @ssl_generation
495 | end
496 |
497 | @certificate = nil
498 | @ca_file = nil
499 | @ca_path = nil
500 | @ciphers = nil
501 | @private_key = nil
502 | @ssl_timeout = nil
503 | @ssl_version = nil
504 | @min_version = nil
505 | @max_version = nil
506 | @verify_callback = nil
507 | @verify_depth = nil
508 | @verify_mode = nil
509 | @cert_store = nil
510 |
511 | @generation = 0 # incremented when proxy URI changes
512 |
513 | if HAVE_OPENSSL then
514 | @verify_mode = OpenSSL::SSL::VERIFY_PEER
515 | @reuse_ssl_sessions = OpenSSL::SSL.const_defined? :Session
516 | end
517 |
518 | self.proxy = proxy if proxy
519 | end
520 |
521 | ##
522 | # Sets this client's OpenSSL::X509::Certificate
523 |
524 | def certificate= certificate
525 | @certificate = certificate
526 |
527 | reconnect_ssl
528 | end
529 |
530 | # For Net::HTTP parity
531 | alias cert= certificate=
532 |
533 | ##
534 | # Sets the SSL certificate authority file.
535 |
536 | def ca_file= file
537 | @ca_file = file
538 |
539 | reconnect_ssl
540 | end
541 |
542 | ##
543 | # Sets the SSL certificate authority path.
544 |
545 | def ca_path= path
546 | @ca_path = path
547 |
548 | reconnect_ssl
549 | end
550 |
551 | ##
552 | # Overrides the default SSL certificate store used for verifying
553 | # connections.
554 |
555 | def cert_store= store
556 | @cert_store = store
557 |
558 | reconnect_ssl
559 | end
560 |
561 | ##
562 | # The ciphers allowed for SSL connections
563 |
564 | def ciphers= ciphers
565 | @ciphers = ciphers
566 |
567 | reconnect_ssl
568 | end
569 |
570 | ##
571 | # Creates a new connection for +uri+
572 |
573 | def connection_for uri
574 | use_ssl = uri.scheme.downcase == 'https'
575 |
576 | net_http_args = [uri.hostname, uri.port]
577 |
578 | # I'm unsure if uri.host or uri.hostname should be checked against
579 | # the proxy bypass list.
580 | if @proxy_uri and not proxy_bypass? uri.host, uri.port then
581 | net_http_args.concat @proxy_args
582 | else
583 | net_http_args.concat [nil, nil, nil, nil]
584 | end
585 |
586 | connection = @pool.checkout net_http_args
587 |
588 | http = connection.http
589 |
590 | connection.ressl @ssl_generation if
591 | connection.ssl_generation != @ssl_generation
592 |
593 | if not http.started? then
594 | ssl http if use_ssl
595 | start http
596 | elsif expired? connection then
597 | reset connection
598 | end
599 |
600 | http.keep_alive_timeout = @idle_timeout if @idle_timeout
601 | http.max_retries = @max_retries if http.respond_to?(:max_retries=)
602 | http.read_timeout = @read_timeout if @read_timeout
603 | http.write_timeout = @write_timeout if
604 | @write_timeout && http.respond_to?(:write_timeout=)
605 |
606 | return yield connection
607 | rescue Errno::ECONNREFUSED
608 | address = http.proxy_address || http.address
609 | port = http.proxy_port || http.port
610 |
611 | raise Error, "connection refused: #{address}:#{port}"
612 | rescue Errno::EHOSTDOWN
613 | address = http.proxy_address || http.address
614 | port = http.proxy_port || http.port
615 |
616 | raise Error, "host down: #{address}:#{port}"
617 | ensure
618 | @pool.checkin net_http_args
619 | end
620 |
621 | ##
622 | # CGI::escape wrapper
623 |
624 | def escape str
625 | CGI.escape str if str
626 | end
627 |
628 | ##
629 | # CGI::unescape wrapper
630 |
631 | def unescape str
632 | CGI.unescape str if str
633 | end
634 |
635 |
636 | ##
637 | # Returns true if the connection should be reset due to an idle timeout, or
638 | # maximum request count, false otherwise.
639 |
640 | def expired? connection
641 | return true if @max_requests && connection.requests >= @max_requests
642 | return false unless @idle_timeout
643 | return true if @idle_timeout.zero?
644 |
645 | Time.now - connection.last_use > @idle_timeout
646 | end
647 |
648 | ##
649 | # Starts the Net::HTTP +connection+
650 |
651 | def start http
652 | http.set_debug_output @debug_output if @debug_output
653 | http.open_timeout = @open_timeout if @open_timeout
654 |
655 | http.start
656 |
657 | socket = http.instance_variable_get :@socket
658 |
659 | if socket then # for fakeweb
660 | @socket_options.each do |option|
661 | socket.io.setsockopt(*option)
662 | end
663 | end
664 | end
665 |
666 | ##
667 | # Finishes the Net::HTTP +connection+
668 |
669 | def finish connection
670 | connection.finish
671 |
672 | connection.http.instance_variable_set :@last_communicated, nil
673 | connection.http.instance_variable_set :@ssl_session, nil unless
674 | @reuse_ssl_sessions
675 | end
676 |
677 | ##
678 | # Returns the HTTP protocol version for +uri+
679 |
680 | def http_version uri
681 | @http_versions["#{uri.hostname}:#{uri.port}"]
682 | end
683 |
684 | ##
685 | # Adds "http://" to the String +uri+ if it is missing.
686 |
687 | def normalize_uri uri
688 | (uri =~ /^https?:/) ? uri : "http://#{uri}"
689 | end
690 |
691 | ##
692 | # Set the maximum number of retries for a request.
693 | #
694 | # Defaults to one retry.
695 | #
696 | # Set this to 0 to disable retries.
697 |
698 | def max_retries= retries
699 | retries = retries.to_int
700 |
701 | raise ArgumentError, "max_retries must be positive" if retries < 0
702 |
703 | @max_retries = retries
704 |
705 | reconnect
706 | end
707 |
708 | ##
709 | # Pipelines +requests+ to the HTTP server at +uri+ yielding responses if a
710 | # block is given. Returns all responses received.
711 | #
712 | # See
713 | # Net::HTTP::Pipeline[http://docs.seattlerb.org/net-http-pipeline/Net/HTTP/Pipeline.html]
714 | # for further details.
715 | #
716 | # Only if net-http-pipeline was required before
717 | # net-http-persistent #pipeline will be present.
718 |
719 | def pipeline uri, requests, &block # :yields: responses
720 | connection_for uri do |connection|
721 | connection.http.pipeline requests, &block
722 | end
723 | end
724 |
725 | ##
726 | # Sets this client's SSL private key
727 |
728 | def private_key= key
729 | @private_key = key
730 |
731 | reconnect_ssl
732 | end
733 |
734 | # For Net::HTTP parity
735 | alias key= private_key=
736 |
737 | ##
738 | # Sets the proxy server. The +proxy+ may be the URI of the proxy server,
739 | # the symbol +:ENV+ which will read the proxy from the environment or nil to
740 | # disable use of a proxy. See #proxy_from_env for details on setting the
741 | # proxy from the environment.
742 | #
743 | # If the proxy URI is set after requests have been made, the next request
744 | # will shut-down and re-open all connections.
745 | #
746 | # The +no_proxy+ query parameter can be used to specify hosts which shouldn't
747 | # be reached via proxy; if set it should be a comma separated list of
748 | # hostname suffixes, optionally with +:port+ appended, for example
749 | # example.com,some.host:8080.
750 |
751 | def proxy= proxy
752 | @proxy_uri = case proxy
753 | when :ENV then proxy_from_env
754 | when URI::HTTP then proxy
755 | when nil then # ignore
756 | else raise ArgumentError, 'proxy must be :ENV or a URI::HTTP'
757 | end
758 |
759 | @no_proxy.clear
760 |
761 | if @proxy_uri then
762 | @proxy_args = [
763 | @proxy_uri.hostname,
764 | @proxy_uri.port,
765 | unescape(@proxy_uri.user),
766 | unescape(@proxy_uri.password),
767 | ]
768 |
769 | @proxy_connection_id = [nil, *@proxy_args].join ':'
770 |
771 | if @proxy_uri.query then
772 | @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? }
773 | end
774 | end
775 |
776 | reconnect
777 | reconnect_ssl
778 | end
779 |
780 | ##
781 | # Creates a URI for an HTTP proxy server from ENV variables.
782 | #
783 | # If +HTTP_PROXY+ is set a proxy will be returned.
784 | #
785 | # If +HTTP_PROXY_USER+ or +HTTP_PROXY_PASS+ are set the URI is given the
786 | # indicated user and password unless HTTP_PROXY contains either of these in
787 | # the URI.
788 | #
789 | # The +NO_PROXY+ ENV variable can be used to specify hosts which shouldn't
790 | # be reached via proxy; if set it should be a comma separated list of
791 | # hostname suffixes, optionally with +:port+ appended, for example
792 | # example.com,some.host:8080. When set to * no proxy will
793 | # be returned.
794 | #
795 | # For Windows users, lowercase ENV variables are preferred over uppercase ENV
796 | # variables.
797 |
798 | def proxy_from_env
799 | env_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
800 |
801 | return nil if env_proxy.nil? or env_proxy.empty?
802 |
803 | uri = URI normalize_uri env_proxy
804 |
805 | env_no_proxy = ENV['no_proxy'] || ENV['NO_PROXY']
806 |
807 | # '*' is special case for always bypass
808 | return nil if env_no_proxy == '*'
809 |
810 | if env_no_proxy then
811 | uri.query = "no_proxy=#{escape(env_no_proxy)}"
812 | end
813 |
814 | unless uri.user or uri.password then
815 | uri.user = escape ENV['http_proxy_user'] || ENV['HTTP_PROXY_USER']
816 | uri.password = escape ENV['http_proxy_pass'] || ENV['HTTP_PROXY_PASS']
817 | end
818 |
819 | uri
820 | end
821 |
822 | ##
823 | # Returns true when proxy should by bypassed for host.
824 |
825 | def proxy_bypass? host, port
826 | host = host.downcase
827 | host_port = [host, port].join ':'
828 |
829 | @no_proxy.each do |name|
830 | return true if host[-name.length, name.length] == name or
831 | host_port[-name.length, name.length] == name
832 | end
833 |
834 | false
835 | end
836 |
837 | ##
838 | # Forces reconnection of all HTTP connections, including TLS/SSL
839 | # connections.
840 |
841 | def reconnect
842 | @generation += 1
843 | end
844 |
845 | ##
846 | # Forces reconnection of only TLS/SSL connections.
847 |
848 | def reconnect_ssl
849 | @ssl_generation += 1
850 | end
851 |
852 | ##
853 | # Finishes then restarts the Net::HTTP +connection+
854 |
855 | def reset connection
856 | http = connection.http
857 |
858 | finish connection
859 |
860 | start http
861 | rescue Errno::ECONNREFUSED
862 | e = Error.new "connection refused: #{http.address}:#{http.port}"
863 | e.set_backtrace $@
864 | raise e
865 | rescue Errno::EHOSTDOWN
866 | e = Error.new "host down: #{http.address}:#{http.port}"
867 | e.set_backtrace $@
868 | raise e
869 | end
870 |
871 | ##
872 | # Makes a request on +uri+. If +req+ is nil a Net::HTTP::Get is performed
873 | # against +uri+.
874 | #
875 | # If a block is passed #request behaves like Net::HTTP#request (the body of
876 | # the response will not have been read).
877 | #
878 | # +req+ must be a Net::HTTPGenericRequest subclass (see Net::HTTP for a list).
879 |
880 | def request uri, req = nil, &block
881 | uri = URI uri
882 | req = request_setup req || uri
883 | response = nil
884 |
885 | connection_for uri do |connection|
886 | http = connection.http
887 |
888 | begin
889 | connection.requests += 1
890 |
891 | response = http.request req, &block
892 |
893 | if req.connection_close? or
894 | (response.http_version <= '1.0' and
895 | not response.connection_keep_alive?) or
896 | response.connection_close? then
897 | finish connection
898 | end
899 | rescue Exception # make sure to close the connection when it was interrupted
900 | finish connection
901 |
902 | raise
903 | ensure
904 | connection.last_use = Time.now
905 | end
906 | end
907 |
908 | @http_versions["#{uri.hostname}:#{uri.port}"] ||= response.http_version
909 |
910 | response
911 | end
912 |
913 | ##
914 | # Creates a GET request if +req_or_uri+ is a URI and adds headers to the
915 | # request.
916 | #
917 | # Returns the request.
918 |
919 | def request_setup req_or_uri # :nodoc:
920 | req = if req_or_uri.respond_to? 'request_uri' then
921 | Net::HTTP::Get.new req_or_uri.request_uri
922 | else
923 | req_or_uri
924 | end
925 |
926 | @headers.each do |pair|
927 | req.add_field(*pair)
928 | end
929 |
930 | @override_headers.each do |name, value|
931 | req[name] = value
932 | end
933 |
934 | unless req['Connection'] then
935 | req.add_field 'Connection', 'keep-alive'
936 | req.add_field 'Keep-Alive', @keep_alive
937 | end
938 |
939 | req
940 | end
941 |
942 | ##
943 | # Shuts down all connections
944 | #
945 | # *NOTE*: Calling shutdown for can be dangerous!
946 | #
947 | # If any thread is still using a connection it may cause an error! Call
948 | # #shutdown when you are completely done making requests!
949 |
950 | def shutdown
951 | @pool.shutdown { |http| http.finish }
952 | end
953 |
954 | ##
955 | # Enables SSL on +connection+
956 |
957 | def ssl connection
958 | connection.use_ssl = true
959 |
960 | connection.ciphers = @ciphers if @ciphers
961 | connection.ssl_timeout = @ssl_timeout if @ssl_timeout
962 | connection.ssl_version = @ssl_version if @ssl_version
963 | connection.min_version = @min_version if @min_version
964 | connection.max_version = @max_version if @max_version
965 |
966 | connection.verify_depth = @verify_depth
967 | connection.verify_mode = @verify_mode
968 |
969 | if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and
970 | not Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then
971 | warn <<-WARNING
972 | !!!SECURITY WARNING!!!
973 |
974 | The SSL HTTP connection to:
975 |
976 | #{connection.address}:#{connection.port}
977 |
978 | !!!MAY NOT BE VERIFIED!!!
979 |
980 | On your platform your OpenSSL implementation is broken.
981 |
982 | There is no difference between the values of VERIFY_NONE and VERIFY_PEER.
983 |
984 | This means that attempting to verify the security of SSL connections may not
985 | work. This exposes you to man-in-the-middle exploits, snooping on the
986 | contents of your connection and other dangers to the security of your data.
987 |
988 | To disable this warning define the following constant at top-level in your
989 | application:
990 |
991 | I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG = nil
992 |
993 | WARNING
994 | end
995 |
996 | connection.ca_file = @ca_file if @ca_file
997 | connection.ca_path = @ca_path if @ca_path
998 |
999 | if @ca_file or @ca_path then
1000 | connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
1001 | connection.verify_callback = @verify_callback if @verify_callback
1002 | end
1003 |
1004 | if @certificate and @private_key then
1005 | connection.cert = @certificate
1006 | connection.key = @private_key
1007 | end
1008 |
1009 | connection.cert_store = if @cert_store then
1010 | @cert_store
1011 | else
1012 | store = OpenSSL::X509::Store.new
1013 | store.set_default_paths
1014 | store
1015 | end
1016 | end
1017 |
1018 | ##
1019 | # SSL session lifetime
1020 |
1021 | def ssl_timeout= ssl_timeout
1022 | @ssl_timeout = ssl_timeout
1023 |
1024 | reconnect_ssl
1025 | end
1026 |
1027 | ##
1028 | # SSL version to use
1029 |
1030 | def ssl_version= ssl_version
1031 | @ssl_version = ssl_version
1032 |
1033 | reconnect_ssl
1034 | end
1035 |
1036 | ##
1037 | # Minimum SSL version to use
1038 |
1039 | def min_version= min_version
1040 | @min_version = min_version
1041 |
1042 | reconnect_ssl
1043 | end
1044 |
1045 | ##
1046 | # maximum SSL version to use
1047 |
1048 | def max_version= max_version
1049 | @max_version = max_version
1050 |
1051 | reconnect_ssl
1052 | end
1053 |
1054 | ##
1055 | # Sets the depth of SSL certificate verification
1056 |
1057 | def verify_depth= verify_depth
1058 | @verify_depth = verify_depth
1059 |
1060 | reconnect_ssl
1061 | end
1062 |
1063 | ##
1064 | # Sets the HTTPS verify mode. Defaults to OpenSSL::SSL::VERIFY_PEER.
1065 | #
1066 | # Setting this to VERIFY_NONE is a VERY BAD IDEA and should NEVER be used.
1067 | # Securely transfer the correct certificate and update the default
1068 | # certificate store or set the ca file instead.
1069 |
1070 | def verify_mode= verify_mode
1071 | @verify_mode = verify_mode
1072 |
1073 | reconnect_ssl
1074 | end
1075 |
1076 | ##
1077 | # SSL verification callback.
1078 |
1079 | def verify_callback= callback
1080 | @verify_callback = callback
1081 |
1082 | reconnect_ssl
1083 | end
1084 | end
1085 |
1086 | require_relative 'persistent/connection'
1087 | require_relative 'persistent/pool'
1088 |
1089 |
--------------------------------------------------------------------------------
/test/test_net_http_persistent.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'minitest/autorun'
3 | require 'net/http/persistent'
4 | require 'stringio'
5 |
6 | HAVE_OPENSSL = defined?(OpenSSL::SSL)
7 |
8 | module Net::HTTP::Persistent::TestConnect
9 | def self.included mod
10 | mod.send :alias_method, :orig_connect, :connect
11 |
12 | def mod.use_connect which
13 | self.send :remove_method, :connect
14 | self.send :alias_method, :connect, which
15 | end
16 | end
17 |
18 | def host_down_connect
19 | raise Errno::EHOSTDOWN
20 | end
21 |
22 | def test_connect
23 | unless use_ssl? then
24 | io = Object.new
25 | def io.setsockopt(*a) @setsockopts ||= []; @setsockopts << a end
26 |
27 | @socket = Net::BufferedIO.new io
28 |
29 | return
30 | end
31 |
32 | io = open '/dev/null'
33 | def io.setsockopt(*a) @setsockopts ||= []; @setsockopts << a end
34 |
35 | @ssl_context ||= OpenSSL::SSL::SSLContext.new
36 |
37 | @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER unless
38 | @ssl_context.verify_mode
39 |
40 | s = OpenSSL::SSL::SSLSocket.new io, @ssl_context
41 |
42 | @socket = Net::BufferedIO.new s
43 | end
44 |
45 | def refused_connect
46 | raise Errno::ECONNREFUSED
47 | end
48 | end
49 |
50 | class Net::HTTP
51 | include Net::HTTP::Persistent::TestConnect
52 | end
53 |
54 | class TestNetHttpPersistent < Minitest::Test
55 |
56 | def setup
57 | @http = Net::HTTP::Persistent.new
58 |
59 | @uri = URI 'http://example.com/path'
60 | @uri_v6 = URI 'http://[2001:db8::1]/path'
61 |
62 | ENV.delete 'http_proxy'
63 | ENV.delete 'HTTP_PROXY'
64 | ENV.delete 'http_proxy_user'
65 | ENV.delete 'HTTP_PROXY_USER'
66 | ENV.delete 'http_proxy_pass'
67 | ENV.delete 'HTTP_PROXY_PASS'
68 | ENV.delete 'no_proxy'
69 | ENV.delete 'NO_PROXY'
70 |
71 | Net::HTTP.use_connect :test_connect
72 | end
73 |
74 | def teardown
75 | Net::HTTP.use_connect :orig_connect
76 | end
77 |
78 | class BasicConnection
79 | attr_accessor :started, :finished, :address, :port, :use_ssl,
80 | :read_timeout, :open_timeout, :keep_alive_timeout
81 | attr_accessor :ciphers, :ssl_timeout, :ssl_version, :min_version,
82 | :max_version, :verify_depth, :verify_mode, :cert_store,
83 | :ca_file, :ca_path, :cert, :key
84 | attr_reader :req, :debug_output
85 | def initialize
86 | @started, @finished = 0, 0
87 | @address, @port = 'example.com', 80
88 | @use_ssl = false
89 | end
90 | def finish
91 | @finished += 1
92 | @socket = nil
93 | end
94 | def finished?
95 | @finished >= 1
96 | end
97 | def pipeline requests, &block
98 | requests.map { |r| r.path }
99 | end
100 | def reset?
101 | @started == @finished + 1
102 | end
103 | def set_debug_output io
104 | @debug_output = io
105 | end
106 | def start
107 | @started += 1
108 | io = Object.new
109 | def io.setsockopt(*a) @setsockopts ||= []; @setsockopts << a end
110 | @socket = Net::BufferedIO.new io
111 | end
112 | def started?
113 | @started >= 1
114 | end
115 | def proxy_address
116 | end
117 | def proxy_port
118 | end
119 | end
120 |
121 | def basic_connection
122 | raise "#{@uri} is not HTTP" unless @uri.scheme.downcase == 'http'
123 |
124 | net_http_args = [@uri.hostname, @uri.port, nil, nil, nil, nil]
125 |
126 | connection = Net::HTTP::Persistent::Connection.allocate
127 | connection.ssl_generation = @http.ssl_generation
128 | connection.http = BasicConnection.new
129 | connection.reset
130 |
131 | @http.pool.available.push connection, connection_args: net_http_args
132 |
133 | connection
134 | end
135 |
136 | def connection uri = @uri
137 | @uri = uri
138 |
139 | connection = basic_connection
140 | connection.last_use = Time.now
141 |
142 | def (connection.http).request(req)
143 | @req = req
144 | r = Net::HTTPResponse.allocate
145 | r.instance_variable_set :@header, {}
146 | def r.http_version() '1.1' end
147 | def r.read_body() :read_body end
148 | yield r if block_given?
149 | r
150 | end
151 |
152 | connection
153 | end
154 |
155 | def ssl_connection
156 | raise "#{@uri} is not HTTPS" unless @uri.scheme.downcase == 'https'
157 |
158 | net_http_args = [@uri.hostname, @uri.port, nil, nil, nil, nil]
159 |
160 | connection = Net::HTTP::Persistent::Connection.allocate
161 | connection.ssl_generation = @http.ssl_generation
162 | connection.http = BasicConnection.new
163 | connection.reset
164 |
165 | @http.pool.available.push connection, connection_args: net_http_args
166 |
167 | connection
168 | end
169 |
170 | def test_initialize
171 | assert_nil @http.proxy_uri
172 |
173 | assert_empty @http.no_proxy
174 |
175 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
176 |
177 | ssl_session_exists = OpenSSL::SSL.const_defined? :Session
178 |
179 | assert_equal ssl_session_exists, @http.reuse_ssl_sessions
180 | end
181 |
182 | def test_initialize_name
183 | http = Net::HTTP::Persistent.new name: 'name'
184 | assert_equal 'name', http.name
185 | end
186 |
187 | def test_initialize_no_ssl_session
188 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
189 |
190 | skip "OpenSSL::SSL::Session does not exist on #{RUBY_PLATFORM}" unless
191 | OpenSSL::SSL.const_defined? :Session
192 |
193 | ssl_session = OpenSSL::SSL::Session
194 |
195 | OpenSSL::SSL.send :remove_const, :Session
196 |
197 | http = Net::HTTP::Persistent.new
198 |
199 | refute http.reuse_ssl_sessions
200 | ensure
201 | OpenSSL::SSL.const_set :Session, ssl_session if ssl_session
202 | end
203 |
204 | def test_initialize_proxy
205 | proxy_uri = URI.parse 'http://proxy.example'
206 |
207 | http = Net::HTTP::Persistent.new proxy: proxy_uri
208 |
209 | assert_equal proxy_uri, http.proxy_uri
210 | end
211 |
212 | def test_ca_file_equals
213 | @http.ca_file = :ca_file
214 |
215 | assert_equal :ca_file, @http.ca_file
216 | assert_equal 1, @http.ssl_generation
217 | end
218 |
219 | def test_ca_path_equals
220 | @http.ca_path = :ca_path
221 |
222 | assert_equal :ca_path, @http.ca_path
223 | assert_equal 1, @http.ssl_generation
224 | end
225 |
226 | def test_cert_store_equals
227 | @http.cert_store = :cert_store
228 |
229 | assert_equal :cert_store, @http.cert_store
230 | assert_equal 1, @http.ssl_generation
231 | end
232 |
233 | def test_certificate_equals
234 | @http.certificate = :cert
235 |
236 | assert_equal :cert, @http.certificate
237 | assert_equal 1, @http.ssl_generation
238 | end
239 |
240 | def test_ciphers_equals
241 | @http.ciphers = :ciphers
242 |
243 | assert_equal :ciphers, @http.ciphers
244 | assert_equal 1, @http.ssl_generation
245 | end
246 |
247 | def test_connection_for
248 | @http.open_timeout = 123
249 | @http.read_timeout = 321
250 | @http.idle_timeout = 42
251 | @http.max_retries = 5
252 |
253 | used = @http.connection_for @uri do |c|
254 | assert_kind_of Net::HTTP, c.http
255 |
256 | assert c.http.started?
257 | refute c.http.proxy?
258 |
259 | assert_equal 123, c.http.open_timeout
260 | assert_equal 321, c.http.read_timeout
261 | assert_equal 42, c.http.keep_alive_timeout
262 | assert_equal 5, c.http.max_retries if c.http.respond_to?(:max_retries)
263 |
264 | c
265 | end
266 |
267 | stored = @http.pool.checkout ['example.com', 80, nil, nil, nil, nil]
268 |
269 | assert_same used, stored
270 | end
271 |
272 | def test_connection_for_cached
273 | cached = basic_connection
274 | cached.http.start
275 |
276 | @http.read_timeout = 5
277 |
278 | @http.connection_for @uri do |c|
279 | assert c.http.started?
280 |
281 | assert_equal 5, c.http.read_timeout
282 |
283 | assert_same cached, c
284 | end
285 | end
286 |
287 | def test_connection_for_closed
288 | cached = basic_connection
289 | cached.http.start
290 | if Socket.const_defined? :TCP_NODELAY then
291 | io = Object.new
292 | def io.setsockopt(*a) raise IOError, 'closed stream' end
293 | cached.instance_variable_set :@socket, Net::BufferedIO.new(io)
294 | end
295 |
296 | @http.connection_for @uri do |c|
297 | assert c.http.started?
298 |
299 | socket = c.http.instance_variable_get :@socket
300 |
301 | refute_includes socket.io.instance_variables, :@setsockopt
302 | refute_includes socket.io.instance_variables, '@setsockopt'
303 | end
304 | end
305 |
306 | def test_connection_for_debug_output
307 | io = StringIO.new
308 | @http.debug_output = io
309 |
310 | @http.connection_for @uri do |c|
311 | assert c.http.started?
312 | assert_equal io, c.http.instance_variable_get(:@debug_output)
313 | end
314 | end
315 |
316 | def test_connection_for_cached_expire_always
317 | cached = basic_connection
318 | cached.http.start
319 | cached.requests = 10
320 | cached.last_use = Time.now # last used right now
321 |
322 | @http.idle_timeout = 0
323 |
324 | @http.connection_for @uri do |c|
325 | assert c.http.started?
326 |
327 | assert_same cached, c
328 |
329 | assert_equal 0, c.requests, 'connection reset due to timeout'
330 | end
331 | end
332 |
333 | def test_connection_for_cached_expire_never
334 | cached = basic_connection
335 | cached.http.start
336 | cached.requests = 10
337 | cached.last_use = Time.now # last used right now
338 |
339 | @http.idle_timeout = nil
340 |
341 | @http.connection_for @uri do |c|
342 | assert c.http.started?
343 |
344 | assert_same cached, c
345 |
346 | assert_equal 10, c.requests, 'connection reset despite no timeout'
347 | end
348 | end
349 |
350 | def test_connection_for_cached_expired
351 | cached = basic_connection
352 | cached.http.start
353 | cached.requests = 10
354 | cached.last_use = Time.now - 3600
355 |
356 | @http.connection_for @uri do |c|
357 | assert c.http.started?
358 |
359 | assert_same cached, c
360 | assert_equal 0, cached.requests, 'connection not reset due to timeout'
361 | end
362 | end
363 |
364 | def test_connection_for_finished_ssl
365 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
366 |
367 | uri = URI.parse 'https://example.com/path'
368 |
369 | @http.connection_for uri do |c|
370 | assert c.http.started?
371 | assert c.http.use_ssl?
372 |
373 | @http.finish c
374 |
375 | refute c.http.started?
376 | end
377 |
378 | @http.connection_for uri do |c2|
379 | assert c2.http.started?
380 | end
381 | end
382 |
383 | def test_connection_for_ipv6
384 | @http.connection_for @uri_v6 do |c|
385 | assert_equal '2001:db8::1', c.http.address
386 | end
387 | end
388 |
389 | def test_connection_for_host_down
390 | c = basic_connection
391 | def (c.http).start; raise Errno::EHOSTDOWN end
392 | def (c.http).started?; false end
393 |
394 | e = assert_raises Net::HTTP::Persistent::Error do
395 | @http.connection_for @uri do end
396 | end
397 |
398 | assert_equal 'host down: example.com:80', e.message
399 | end
400 |
401 | def test_connection_for_http_class_with_fakeweb
402 | Object.send :const_set, :FakeWeb, nil
403 |
404 | @http.connection_for @uri do |c|
405 | assert_instance_of Net::HTTP, c.http
406 | end
407 | ensure
408 | if Object.const_defined?(:FakeWeb) then
409 | Object.send :remove_const, :FakeWeb
410 | end
411 | end
412 |
413 | def test_connection_for_http_class_with_webmock
414 | Object.send :const_set, :WebMock, nil
415 | @http.connection_for @uri do |c|
416 | assert_instance_of Net::HTTP, c.http
417 | end
418 | ensure
419 | if Object.const_defined?(:WebMock) then
420 | Object.send :remove_const, :WebMock
421 | end
422 | end
423 |
424 | def test_connection_for_http_class_with_artifice
425 | Object.send :const_set, :Artifice, nil
426 | @http.connection_for @uri do |c|
427 | assert_instance_of Net::HTTP, c.http
428 | end
429 | ensure
430 | if Object.const_defined?(:Artifice) then
431 | Object.send :remove_const, :Artifice
432 | end
433 | end
434 |
435 | def test_connection_for_name
436 | http = Net::HTTP::Persistent.new name: 'name'
437 | uri = URI.parse 'http://example/'
438 |
439 | http.connection_for uri do |c|
440 | assert c.http.started?
441 | end
442 | end
443 |
444 | def test_connection_for_proxy
445 | uri = URI.parse 'http://proxy.example'
446 | uri.user = 'johndoe'
447 | uri.password = 'muffins'
448 |
449 | http = Net::HTTP::Persistent.new proxy: uri
450 |
451 | used = http.connection_for @uri do |c|
452 | assert c.http.started?
453 | assert c.http.proxy?
454 |
455 | c
456 | end
457 |
458 | stored = http.pool.checkout ['example.com', 80,
459 | 'proxy.example', 80,
460 | 'johndoe', 'muffins']
461 |
462 | assert_same used, stored
463 | end
464 |
465 | def test_connection_for_proxy_unescaped
466 | uri = URI.parse 'http://proxy.example'
467 | uri.user = 'john%40doe'
468 | uri.password = 'muf%3Afins'
469 | uri.freeze
470 |
471 | http = Net::HTTP::Persistent.new proxy: uri
472 |
473 | http.connection_for @uri do end
474 |
475 | stored = http.pool.checkout ['example.com', 80,
476 | 'proxy.example', 80,
477 | 'john@doe', 'muf:fins']
478 |
479 | assert stored
480 | end
481 |
482 | def test_connection_for_proxy_host_down
483 | Net::HTTP.use_connect :host_down_connect
484 |
485 | uri = URI.parse 'http://proxy.example'
486 | uri.user = 'johndoe'
487 | uri.password = 'muffins'
488 |
489 | http = Net::HTTP::Persistent.new proxy: uri
490 |
491 | e = assert_raises Net::HTTP::Persistent::Error do
492 | http.connection_for @uri do end
493 | end
494 |
495 | assert_equal 'host down: proxy.example:80', e.message
496 | end
497 |
498 | def test_connection_for_proxy_refused
499 | Net::HTTP.use_connect :refused_connect
500 |
501 | uri = URI.parse 'http://proxy.example'
502 | uri.user = 'johndoe'
503 | uri.password = 'muffins'
504 |
505 | http = Net::HTTP::Persistent.new proxy: uri
506 |
507 | e = assert_raises Net::HTTP::Persistent::Error do
508 | http.connection_for @uri do end
509 | end
510 |
511 | assert_equal 'connection refused: proxy.example:80', e.message
512 | end
513 |
514 | def test_connection_for_no_proxy
515 | uri = URI.parse 'http://proxy.example'
516 | uri.user = 'johndoe'
517 | uri.password = 'muffins'
518 | uri.query = 'no_proxy=example.com'
519 |
520 | http = Net::HTTP::Persistent.new proxy: uri
521 |
522 | http.connection_for @uri do |c|
523 | assert c.http.started?
524 | refute c.http.proxy?
525 | end
526 |
527 | stored = http.pool.checkout ['example.com', 80]
528 |
529 | assert stored
530 | end
531 |
532 | def test_connection_for_no_proxy_from_env
533 | ENV['http_proxy'] = 'proxy.example'
534 | ENV['no_proxy'] = 'localhost, example.com,'
535 | ENV['proxy_user'] = 'johndoe'
536 | ENV['proxy_password'] = 'muffins'
537 |
538 | http = Net::HTTP::Persistent.new proxy: :ENV
539 |
540 | http.connection_for @uri do |c|
541 | assert c.http.started?
542 | refute c.http.proxy?
543 | refute c.http.proxy_from_env?
544 | end
545 | end
546 |
547 | def test_connection_for_refused
548 | Net::HTTP.use_connect :refused_connect
549 |
550 | e = assert_raises Net::HTTP::Persistent::Error do
551 | @http.connection_for @uri do end
552 | end
553 |
554 | assert_equal 'connection refused: example.com:80', e.message
555 | end
556 |
557 | def test_connection_for_ssl
558 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
559 |
560 | uri = URI.parse 'https://example.com/path'
561 |
562 | @http.connection_for uri do |c|
563 | assert c.http.started?
564 | assert c.http.use_ssl?
565 | end
566 | end
567 |
568 | def test_connection_for_ssl_cached
569 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
570 |
571 | @uri = URI.parse 'https://example.com/path'
572 |
573 | cached = ssl_connection
574 |
575 | @http.connection_for @uri do |c|
576 | assert_same cached, c
577 | end
578 | end
579 |
580 | def test_connection_for_ssl_cached_reconnect
581 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
582 |
583 | @uri = URI.parse 'https://example.com/path'
584 |
585 | cached = ssl_connection
586 |
587 | ssl_generation = @http.ssl_generation
588 |
589 | @http.reconnect_ssl
590 |
591 | @http.connection_for @uri do |c|
592 | assert_same cached, c
593 | refute_equal ssl_generation, c.ssl_generation
594 | end
595 | end
596 |
597 | def test_connection_for_ssl_case
598 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
599 |
600 | uri = URI.parse 'HTTPS://example.com/path'
601 | @http.connection_for uri do |c|
602 | assert c.http.started?
603 | assert c.http.use_ssl?
604 | end
605 | end
606 |
607 | def test_connection_for_timeout
608 | cached = basic_connection
609 | cached.http.start
610 | cached.requests = 10
611 | cached.last_use = Time.now - 6
612 |
613 | @http.connection_for @uri do |c|
614 | assert c.http.started?
615 | assert_equal 0, c.requests
616 |
617 | assert_same cached, c
618 | end
619 | end
620 |
621 | def test_escape
622 | assert_nil @http.escape nil
623 |
624 | assert_equal '+%3F', @http.escape(' ?')
625 | end
626 |
627 | def test_unescape
628 | assert_nil @http.unescape nil
629 |
630 | assert_equal ' ?', @http.unescape('+%3F')
631 | end
632 |
633 | def test_expired_eh
634 | c = basic_connection
635 | c.requests = 0
636 | c.last_use = Time.now - 11
637 |
638 | @http.idle_timeout = 0
639 | assert @http.expired? c
640 |
641 | @http.idle_timeout = 10
642 | assert @http.expired? c
643 |
644 | @http.idle_timeout = 11
645 | assert @http.expired? c
646 |
647 | @http.idle_timeout = 12
648 | refute @http.expired? c
649 |
650 | @http.idle_timeout = nil
651 | refute @http.expired? c
652 | end
653 |
654 | def test_expired_due_to_max_requests
655 | c = basic_connection
656 | c.requests = 0
657 | c.last_use = Time.now
658 |
659 | refute @http.expired? c
660 |
661 | c.requests = 10
662 | refute @http.expired? c
663 |
664 | @http.max_requests = 10
665 | assert @http.expired? c
666 |
667 | c.requests = 9
668 | refute @http.expired? c
669 | end
670 |
671 | def test_finish
672 | c = basic_connection
673 | c.requests = 5
674 | c.http.instance_variable_set(:@last_communicated, Process.clock_gettime(Process::CLOCK_MONOTONIC))
675 |
676 | @http.finish c
677 |
678 | refute c.http.started?
679 | assert c.http.finished?
680 |
681 | assert_equal 0, c.requests
682 | assert_equal Net::HTTP::Persistent::EPOCH, c.last_use
683 | assert_nil c.http.instance_variable_get(:@last_communicated)
684 | end
685 |
686 | def test_finish_io_error
687 | c = basic_connection
688 | def (c.http).finish; @finished += 1; raise IOError end
689 | c.requests = 5
690 |
691 | @http.finish c
692 |
693 | refute c.http.started?
694 | assert c.http.finished?
695 | end
696 |
697 | def test_finish_ssl_no_session_reuse
698 | http = Net::HTTP.new 'localhost', 443, ssl: true
699 | http.instance_variable_set :@ssl_session, :something
700 |
701 | c = Net::HTTP::Persistent::Connection.allocate
702 | c.instance_variable_set :@http, http
703 |
704 | @http.reuse_ssl_sessions = false
705 |
706 | @http.finish c
707 |
708 | assert_nil c.http.instance_variable_get :@ssl_session
709 | end
710 |
711 | def test_http_version
712 | assert_nil @http.http_version @uri
713 |
714 | connection
715 |
716 | @http.request @uri
717 |
718 | assert_equal '1.1', @http.http_version(@uri)
719 | end
720 |
721 | def test_http_version_IPv6
722 | assert_nil @http.http_version @uri_v6
723 |
724 | connection @uri_v6
725 |
726 | @http.request @uri_v6
727 |
728 | assert_equal '1.1', @http.http_version(@uri_v6)
729 | end
730 |
731 | def test_max_retries_equals
732 | @http.max_retries = 5
733 |
734 | assert_equal 5, @http.max_retries
735 | assert_equal 1, @http.generation
736 | end
737 |
738 | def test_normalize_uri
739 | assert_equal 'http://example', @http.normalize_uri('example')
740 | assert_equal 'http://example', @http.normalize_uri('http://example')
741 | assert_equal 'https://example', @http.normalize_uri('https://example')
742 | end
743 |
744 | def test_override_haeders
745 | assert_empty @http.override_headers
746 |
747 | @http.override_headers['User-Agent'] = 'MyCustomAgent'
748 |
749 | expected = { 'User-Agent' => 'MyCustomAgent' }
750 |
751 | assert_equal expected, @http.override_headers
752 | end
753 |
754 | def test_pipeline
755 | skip 'net-http-pipeline not installed' unless defined?(Net::HTTP::Pipeline)
756 |
757 | cached = basic_connection
758 | cached.http.start
759 |
760 | requests = [
761 | Net::HTTP::Get.new((@uri + '1').request_uri),
762 | Net::HTTP::Get.new((@uri + '2').request_uri),
763 | ]
764 |
765 | responses = @http.pipeline @uri, requests
766 |
767 | assert_equal 2, responses.length
768 | assert_equal '/1', responses.first
769 | assert_equal '/2', responses.last
770 | end
771 |
772 | def test_private_key_equals
773 | @http.private_key = :private_key
774 |
775 | assert_equal :private_key, @http.private_key
776 | assert_equal 1, @http.ssl_generation
777 | end
778 |
779 | def test_proxy_equals_env
780 | ENV['http_proxy'] = 'proxy.example'
781 |
782 | @http.proxy = :ENV
783 |
784 | assert_equal URI.parse('http://proxy.example'), @http.proxy_uri
785 |
786 | assert_equal 1, @http.generation, 'generation'
787 | assert_equal 1, @http.ssl_generation, 'ssl_generation'
788 | end
789 |
790 | def test_proxy_equals_nil
791 | @http.proxy = nil
792 |
793 | assert_nil @http.proxy_uri
794 |
795 | assert_equal 1, @http.generation, 'generation'
796 | assert_equal 1, @http.ssl_generation, 'ssl_generation'
797 | end
798 |
799 | def test_proxy_equals_uri
800 | proxy_uri = URI.parse 'http://proxy.example'
801 |
802 | @http.proxy = proxy_uri
803 |
804 | assert_equal proxy_uri, @http.proxy_uri
805 | end
806 |
807 | def test_proxy_equals_uri_IPv6
808 | proxy_uri = @uri_v6
809 |
810 | @http.proxy = proxy_uri
811 |
812 | assert_equal proxy_uri, @http.proxy_uri
813 | end
814 |
815 | def test_proxy_from_env
816 | ENV['http_proxy'] = 'proxy.example'
817 | ENV['http_proxy_user'] = 'johndoe'
818 | ENV['http_proxy_pass'] = 'muffins'
819 | ENV['NO_PROXY'] = 'localhost,example.com'
820 |
821 | uri = @http.proxy_from_env
822 |
823 | expected = URI.parse 'http://proxy.example'
824 | expected.user = 'johndoe'
825 | expected.password = 'muffins'
826 | expected.query = 'no_proxy=localhost%2Cexample.com'
827 |
828 | assert_equal expected, uri
829 | end
830 |
831 | def test_proxy_from_env_lower
832 | ENV['http_proxy'] = 'proxy.example'
833 | ENV['http_proxy_user'] = 'johndoe'
834 | ENV['http_proxy_pass'] = 'muffins'
835 | ENV['no_proxy'] = 'localhost,example.com'
836 |
837 | uri = @http.proxy_from_env
838 |
839 | expected = URI.parse 'http://proxy.example'
840 | expected.user = 'johndoe'
841 | expected.password = 'muffins'
842 | expected.query = 'no_proxy=localhost%2Cexample.com'
843 |
844 | assert_equal expected, uri
845 | end
846 |
847 | def test_proxy_from_env_nil
848 | uri = @http.proxy_from_env
849 |
850 | assert_nil uri
851 |
852 | ENV['http_proxy'] = ''
853 |
854 | uri = @http.proxy_from_env
855 |
856 | assert_nil uri
857 | end
858 |
859 | def test_proxy_from_env_no_proxy_star
860 | uri = @http.proxy_from_env
861 |
862 | assert_nil uri
863 |
864 | ENV['http_proxy'] = 'proxy.example'
865 | ENV['no_proxy'] = '*'
866 |
867 | uri = @http.proxy_from_env
868 |
869 | assert_nil uri
870 | end
871 |
872 | def test_proxy_bypass
873 | ENV['http_proxy'] = 'proxy.example'
874 | ENV['no_proxy'] = 'localhost,example.com:80'
875 |
876 | @http.proxy = :ENV
877 |
878 | assert @http.proxy_bypass? 'localhost', 80
879 | assert @http.proxy_bypass? 'localhost', 443
880 | assert @http.proxy_bypass? 'LOCALHOST', 80
881 | assert @http.proxy_bypass? 'example.com', 80
882 | refute @http.proxy_bypass? 'example.com', 443
883 | assert @http.proxy_bypass? 'www.example.com', 80
884 | refute @http.proxy_bypass? 'www.example.com', 443
885 | assert @http.proxy_bypass? 'endingexample.com', 80
886 | refute @http.proxy_bypass? 'example.org', 80
887 | end
888 |
889 | def test_proxy_bypass_space
890 | ENV['http_proxy'] = 'proxy.example'
891 | ENV['no_proxy'] = 'localhost, example.com'
892 |
893 | @http.proxy = :ENV
894 |
895 | assert @http.proxy_bypass? 'example.com', 80
896 | refute @http.proxy_bypass? 'example.org', 80
897 | end
898 |
899 | def test_proxy_bypass_trailing
900 | ENV['http_proxy'] = 'proxy.example'
901 | ENV['no_proxy'] = 'localhost,example.com,'
902 |
903 | @http.proxy = :ENV
904 |
905 | assert @http.proxy_bypass? 'example.com', 80
906 | refute @http.proxy_bypass? 'example.org', 80
907 | end
908 |
909 | def test_proxy_bypass_double_comma
910 | ENV['http_proxy'] = 'proxy.example'
911 | ENV['no_proxy'] = 'localhost,,example.com'
912 |
913 | @http.proxy = :ENV
914 |
915 | assert @http.proxy_bypass? 'example.com', 80
916 | refute @http.proxy_bypass? 'example.org', 80
917 | end
918 |
919 | def test_reconnect
920 | result = @http.reconnect
921 |
922 | assert_equal 1, result
923 | end
924 |
925 | def test_reconnect_ssl
926 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
927 |
928 | @uri = URI 'https://example.com'
929 | now = Time.now
930 |
931 | ssl_http = ssl_connection
932 |
933 | def (ssl_http.http).finish
934 | @started = 0
935 | end
936 |
937 | used1 = @http.connection_for @uri do |c|
938 | c.requests = 1
939 | c.last_use = now
940 | c
941 | end
942 |
943 | assert_equal OpenSSL::SSL::VERIFY_PEER, used1.http.verify_mode
944 |
945 | @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
946 | @http.reconnect_ssl
947 |
948 | used2 = @http.connection_for @uri do |c|
949 | c
950 | end
951 |
952 | assert_same used1, used2
953 |
954 | assert_equal OpenSSL::SSL::VERIFY_NONE, used2.http.verify_mode,
955 | 'verify mode must change'
956 | assert_equal 0, used2.requests
957 | assert_equal Net::HTTP::Persistent::EPOCH, used2.last_use
958 | end
959 |
960 | def test_requestx
961 | @http.override_headers['user-agent'] = 'test ua'
962 | @http.headers['accept'] = 'text/*'
963 | c = connection
964 |
965 | res = @http.request @uri
966 | req = c.http.req
967 |
968 | assert_kind_of Net::HTTPResponse, res
969 |
970 | assert_kind_of Net::HTTP::Get, req
971 | assert_equal '/path', req.path
972 |
973 | assert_equal 'test ua', req['user-agent']
974 | assert_match %r%text/\*%, req['accept']
975 |
976 | assert_equal 'keep-alive', req['connection']
977 | assert_equal '30', req['keep-alive']
978 |
979 | assert_in_delta Time.now, c.last_use
980 |
981 | assert_equal 1, c.requests
982 | end
983 |
984 | def test_request_block
985 | @http.headers['user-agent'] = 'test ua'
986 | c = connection
987 | body = nil
988 |
989 | res = @http.request @uri do |r|
990 | body = r.read_body
991 | end
992 |
993 | req = c.http.req
994 |
995 | assert_kind_of Net::HTTPResponse, res
996 | refute_nil body
997 |
998 | assert_kind_of Net::HTTP::Get, req
999 | assert_equal '/path', req.path
1000 | assert_equal 'keep-alive', req['connection']
1001 | assert_equal '30', req['keep-alive']
1002 | assert_match %r%test ua%, req['user-agent']
1003 |
1004 | assert_equal 1, c.requests
1005 | end
1006 |
1007 | def test_request_close_1_0
1008 | c = connection
1009 |
1010 | class << c.http
1011 | remove_method :request
1012 | end
1013 |
1014 | def (c.http).request req
1015 | @req = req
1016 | r = Net::HTTPResponse.allocate
1017 | r.instance_variable_set :@header, {}
1018 | def r.http_version() '1.0' end
1019 | def r.read_body() :read_body end
1020 | yield r if block_given?
1021 | r
1022 | end
1023 |
1024 | request = Net::HTTP::Get.new @uri.request_uri
1025 |
1026 | res = @http.request @uri, request
1027 | req = c.http.req
1028 |
1029 | assert_kind_of Net::HTTPResponse, res
1030 |
1031 | assert_kind_of Net::HTTP::Get, req
1032 | assert_equal '/path', req.path
1033 | assert_equal 'keep-alive', req['connection']
1034 | assert_equal '30', req['keep-alive']
1035 |
1036 | assert c.http.finished?
1037 | end
1038 |
1039 | def test_request_connection_close_request
1040 | c = connection
1041 |
1042 | request = Net::HTTP::Get.new @uri.request_uri
1043 | request['connection'] = 'close'
1044 |
1045 | res = @http.request @uri, request
1046 | req = c.http.req
1047 |
1048 | assert_kind_of Net::HTTPResponse, res
1049 |
1050 | assert_kind_of Net::HTTP::Get, req
1051 | assert_equal '/path', req.path
1052 | assert_equal 'close', req['connection']
1053 | assert_nil req['keep-alive']
1054 |
1055 | assert c.http.finished?
1056 | end
1057 |
1058 | def test_request_connection_close_response
1059 | c = connection
1060 |
1061 | class << c.http
1062 | remove_method :request
1063 | end
1064 |
1065 | def (c.http).request req
1066 | @req = req
1067 | r = Net::HTTPResponse.allocate
1068 | r.instance_variable_set :@header, {}
1069 | r['connection'] = 'close'
1070 | def r.http_version() '1.1' end
1071 | def r.read_body() :read_body end
1072 | yield r if block_given?
1073 | r
1074 | end
1075 |
1076 | request = Net::HTTP::Get.new @uri.request_uri
1077 |
1078 | res = @http.request @uri, request
1079 | req = c.http.req
1080 |
1081 | assert_kind_of Net::HTTPResponse, res
1082 |
1083 | assert_kind_of Net::HTTP::Get, req
1084 | assert_equal '/path', req.path
1085 | assert_equal 'keep-alive', req['connection']
1086 | assert_equal '30', req['keep-alive']
1087 |
1088 | assert c.http.finished?
1089 | end
1090 |
1091 | def test_request_exception
1092 | c = basic_connection
1093 | def (c.http).request(*a)
1094 | raise Exception, "very bad things happened"
1095 | end
1096 |
1097 | assert_raises Exception do
1098 | @http.request @uri
1099 | end
1100 |
1101 | assert_equal 0, c.requests
1102 | assert c.http.finished?
1103 | end
1104 |
1105 | def test_request_invalid
1106 | c = basic_connection
1107 | def (c.http).request(*a) raise Errno::EINVAL, "write" end
1108 |
1109 | e = assert_raises Errno::EINVAL do
1110 | @http.request @uri
1111 | end
1112 |
1113 | assert_equal 0, c.requests
1114 | assert_match %r%Invalid argument - write%, e.message
1115 | end
1116 |
1117 | def test_request_post
1118 | c = connection
1119 |
1120 | post = Net::HTTP::Post.new @uri.path
1121 |
1122 | @http.request @uri, post
1123 | req = c.http.req
1124 |
1125 | assert_same post, req
1126 | end
1127 |
1128 | def test_request_setup
1129 | @http.override_headers['user-agent'] = 'test ua'
1130 | @http.headers['accept'] = 'text/*'
1131 |
1132 | input = Net::HTTP::Post.new '/path'
1133 |
1134 | req = @http.request_setup input
1135 |
1136 | assert_same input, req
1137 | assert_equal '/path', req.path
1138 |
1139 | assert_equal 'test ua', req['user-agent']
1140 | assert_match %r%text/\*%, req['accept']
1141 |
1142 | assert_equal 'keep-alive', req['connection']
1143 | assert_equal '30', req['keep-alive']
1144 | end
1145 |
1146 | def test_request_string
1147 | @http.override_headers['user-agent'] = 'test ua'
1148 | @http.headers['accept'] = 'text/*'
1149 | c = connection
1150 |
1151 | res = @http.request @uri.to_s
1152 | req = c.http.req
1153 |
1154 | assert_kind_of Net::HTTPResponse, res
1155 |
1156 | assert_kind_of Net::HTTP::Get, req
1157 | assert_equal '/path', req.path
1158 |
1159 | assert_equal 1, c.requests
1160 | end
1161 |
1162 | def test_request_setup_uri
1163 | uri = @uri + '?a=b'
1164 |
1165 | req = @http.request_setup uri
1166 |
1167 | assert_kind_of Net::HTTP::Get, req
1168 | assert_equal '/path?a=b', req.path
1169 | end
1170 |
1171 | def test_reset
1172 | c = basic_connection
1173 | c.http.start
1174 | c.last_use = Time.now
1175 | c.requests = 5
1176 |
1177 | @http.reset c
1178 |
1179 | assert c.http.started?
1180 | assert c.http.finished?
1181 | assert c.http.reset?
1182 | assert_equal 0, c.requests
1183 | assert_equal Net::HTTP::Persistent::EPOCH, c.last_use
1184 | end
1185 |
1186 | def test_reset_host_down
1187 | c = basic_connection
1188 | c.last_use = Time.now
1189 | def (c.http).start; raise Errno::EHOSTDOWN end
1190 | c.requests = 5
1191 |
1192 | e = assert_raises Net::HTTP::Persistent::Error do
1193 | @http.reset c
1194 | end
1195 |
1196 | assert_match %r%host down%, e.message
1197 | assert_match __FILE__, e.backtrace.first
1198 | end
1199 |
1200 | def test_reset_io_error
1201 | c = basic_connection
1202 | c.last_use = Time.now
1203 | c.requests = 5
1204 |
1205 | @http.reset c
1206 |
1207 | assert c.http.started?
1208 | assert c.http.finished?
1209 | end
1210 |
1211 | def test_reset_refused
1212 | c = basic_connection
1213 | c.last_use = Time.now
1214 | def (c.http).start; raise Errno::ECONNREFUSED end
1215 | c.requests = 5
1216 |
1217 | e = assert_raises Net::HTTP::Persistent::Error do
1218 | @http.reset c
1219 | end
1220 |
1221 | assert_match %r%connection refused%, e.message
1222 | assert_match __FILE__, e.backtrace.first
1223 | end
1224 |
1225 | def test_shutdown
1226 | c = connection
1227 |
1228 | orig = @http
1229 | @http = Net::HTTP::Persistent.new name: 'name'
1230 | c2 = connection
1231 |
1232 | orig.shutdown
1233 |
1234 | @http = orig
1235 |
1236 | assert c.http.finished?, 'last-generation connection must be finished'
1237 | refute c2.http.finished?, 'present generation connection must not be finished'
1238 | end
1239 |
1240 | def test_ssl
1241 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1242 |
1243 | @http.verify_callback = :callback
1244 | c = Net::HTTP.new 'localhost', 80
1245 |
1246 | @http.ssl c
1247 |
1248 | assert c.use_ssl?
1249 | assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode
1250 | assert_kind_of OpenSSL::X509::Store, c.cert_store
1251 | assert_nil c.verify_callback
1252 | end
1253 |
1254 | def test_ssl_ca_file
1255 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1256 |
1257 | @http.ca_file = 'ca_file'
1258 | @http.verify_callback = :callback
1259 | c = Net::HTTP.new 'localhost', 80
1260 |
1261 | @http.ssl c
1262 |
1263 | assert c.use_ssl?
1264 | assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode
1265 | assert_equal :callback, c.verify_callback
1266 | end
1267 |
1268 | def test_ssl_ca_path
1269 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1270 |
1271 | @http.ca_path = 'ca_path'
1272 | @http.verify_callback = :callback
1273 | c = Net::HTTP.new 'localhost', 80
1274 |
1275 | @http.ssl c
1276 |
1277 | assert c.use_ssl?
1278 | assert_equal OpenSSL::SSL::VERIFY_PEER, c.verify_mode
1279 | assert_equal :callback, c.verify_callback
1280 | end
1281 |
1282 | def test_ssl_cert_store
1283 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1284 |
1285 | store = OpenSSL::X509::Store.new
1286 | @http.cert_store = store
1287 |
1288 | c = Net::HTTP.new 'localhost', 80
1289 |
1290 | @http.ssl c
1291 |
1292 | assert c.use_ssl?
1293 | assert_equal store, c.cert_store
1294 | end
1295 |
1296 | def test_ssl_cert_store_default
1297 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1298 |
1299 | @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
1300 |
1301 | c = Net::HTTP.new 'localhost', 80
1302 |
1303 | @http.ssl c
1304 |
1305 | assert c.use_ssl?
1306 | assert c.cert_store
1307 | end
1308 |
1309 | def test_ssl_certificate
1310 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1311 |
1312 | @http.certificate = :cert
1313 | @http.private_key = :key
1314 | c = Net::HTTP.new 'localhost', 80
1315 |
1316 | @http.ssl c
1317 |
1318 | assert c.use_ssl?
1319 | assert_equal :cert, c.cert
1320 | assert_equal :key, c.key
1321 | end
1322 |
1323 | def test_ssl_verify_mode
1324 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1325 |
1326 | @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
1327 | c = Net::HTTP.new 'localhost', 80
1328 |
1329 | @http.ssl c
1330 |
1331 | assert c.use_ssl?
1332 | assert_equal OpenSSL::SSL::VERIFY_NONE, c.verify_mode
1333 | end
1334 |
1335 | def test_ssl_warning
1336 | skip 'OpenSSL is missing' unless HAVE_OPENSSL
1337 |
1338 | begin
1339 | orig_verify_peer = OpenSSL::SSL::VERIFY_PEER
1340 | OpenSSL::SSL.send :remove_const, :VERIFY_PEER
1341 | OpenSSL::SSL.send :const_set, :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE
1342 |
1343 | c = Net::HTTP.new 'localhost', 80
1344 |
1345 | out, err = capture_io do
1346 | @http.ssl c
1347 | end
1348 |
1349 | assert_empty out
1350 |
1351 | assert_match %r%localhost:80%, err
1352 | assert_match %r%I_KNOW_THAT_OPENSSL%, err
1353 |
1354 | Object.send :const_set, :I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG, nil
1355 |
1356 | assert_silent do
1357 | @http.ssl c
1358 | end
1359 | ensure
1360 | OpenSSL::SSL.send :remove_const, :VERIFY_PEER
1361 | OpenSSL::SSL.send :const_set, :VERIFY_PEER, orig_verify_peer
1362 | if Object.const_defined?(:I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG) then
1363 | Object.send :remove_const, :I_KNOW_THAT_OPENSSL_VERIFY_PEER_EQUALS_VERIFY_NONE_IS_WRONG
1364 | end
1365 | end
1366 | end
1367 |
1368 | def test_ssl_timeout_equals
1369 | @http.ssl_timeout = :ssl_timeout
1370 |
1371 | assert_equal :ssl_timeout, @http.ssl_timeout
1372 | assert_equal 1, @http.ssl_generation
1373 | end
1374 |
1375 | def test_ssl_version_equals
1376 | @http.ssl_version = :ssl_version
1377 |
1378 | assert_equal :ssl_version, @http.ssl_version
1379 | assert_equal 1, @http.ssl_generation
1380 | end
1381 |
1382 | def test_min_version_equals
1383 | @http.min_version = :min_version
1384 |
1385 | assert_equal :min_version, @http.min_version
1386 | assert_equal 1, @http.ssl_generation
1387 | end
1388 |
1389 | def test_max_version_equals
1390 | @http.max_version = :max_version
1391 |
1392 | assert_equal :max_version, @http.max_version
1393 | assert_equal 1, @http.ssl_generation
1394 | end
1395 |
1396 | def test_start
1397 | c = basic_connection
1398 | c = c.http
1399 |
1400 | @http.socket_options << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
1401 | @http.debug_output = $stderr
1402 | @http.open_timeout = 6
1403 |
1404 | @http.start c
1405 |
1406 | assert_equal $stderr, c.debug_output
1407 | assert_equal 6, c.open_timeout
1408 |
1409 | socket = c.instance_variable_get :@socket
1410 |
1411 | expected = []
1412 | expected << [Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1] if
1413 | Socket.const_defined? :TCP_NODELAY
1414 | expected << [Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1]
1415 |
1416 | assert_equal expected, socket.io.instance_variable_get(:@setsockopts)
1417 | end
1418 |
1419 | def test_verify_callback_equals
1420 | @http.verify_callback = :verify_callback
1421 |
1422 | assert_equal :verify_callback, @http.verify_callback
1423 | assert_equal 1, @http.ssl_generation
1424 | end
1425 |
1426 | def test_verify_depth_equals
1427 | @http.verify_depth = :verify_depth
1428 |
1429 | assert_equal :verify_depth, @http.verify_depth
1430 | assert_equal 1, @http.ssl_generation
1431 | end
1432 |
1433 | def test_verify_mode_equals
1434 | @http.verify_mode = :verify_mode
1435 |
1436 | assert_equal :verify_mode, @http.verify_mode
1437 | assert_equal 1, @http.ssl_generation
1438 | end
1439 |
1440 | end
1441 |
1442 |
--------------------------------------------------------------------------------