httpx 1.3.4 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/doc/release_notes/1_4_0.md +43 -0
- data/doc/release_notes/1_4_1.md +19 -0
- data/lib/httpx/adapters/datadog.rb +55 -83
- data/lib/httpx/adapters/faraday.rb +2 -0
- data/lib/httpx/adapters/webmock.rb +18 -6
- data/lib/httpx/callbacks.rb +0 -5
- data/lib/httpx/chainable.rb +3 -1
- data/lib/httpx/connection/http2.rb +12 -8
- data/lib/httpx/connection.rb +192 -22
- data/lib/httpx/errors.rb +12 -0
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +26 -16
- data/lib/httpx/plugins/aws_sigv4.rb +31 -16
- data/lib/httpx/plugins/callbacks.rb +12 -2
- data/lib/httpx/plugins/circuit_breaker.rb +0 -5
- data/lib/httpx/plugins/content_digest.rb +202 -0
- data/lib/httpx/plugins/expect.rb +4 -3
- data/lib/httpx/plugins/follow_redirects.rb +7 -8
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
- data/lib/httpx/plugins/h2c.rb +23 -20
- data/lib/httpx/plugins/internal_telemetry.rb +27 -0
- data/lib/httpx/plugins/persistent.rb +16 -0
- data/lib/httpx/plugins/proxy/http.rb +17 -19
- data/lib/httpx/plugins/proxy.rb +91 -93
- data/lib/httpx/plugins/retries.rb +5 -8
- data/lib/httpx/plugins/upgrade.rb +5 -10
- data/lib/httpx/plugins/webdav.rb +6 -0
- data/lib/httpx/plugins/xml.rb +76 -0
- data/lib/httpx/pool.rb +73 -244
- data/lib/httpx/request/body.rb +25 -26
- data/lib/httpx/request.rb +7 -1
- data/lib/httpx/resolver/https.rb +15 -20
- data/lib/httpx/resolver/multi.rb +34 -16
- data/lib/httpx/resolver/native.rb +66 -25
- data/lib/httpx/resolver/resolver.rb +59 -15
- data/lib/httpx/resolver/system.rb +31 -15
- data/lib/httpx/resolver.rb +21 -14
- data/lib/httpx/response.rb +5 -3
- data/lib/httpx/selector.rb +160 -95
- data/lib/httpx/session.rb +273 -140
- data/lib/httpx/transcoder/body.rb +15 -31
- data/lib/httpx/transcoder/gzip.rb +0 -3
- data/lib/httpx/transcoder/json.rb +14 -2
- data/lib/httpx/transcoder/multipart/part.rb +1 -1
- data/lib/httpx/transcoder/utils/deflater.rb +7 -4
- data/lib/httpx/transcoder/utils/inflater.rb +2 -0
- data/lib/httpx/transcoder.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +20 -21
- data/sig/callbacks.rbs +0 -1
- data/sig/chainable.rbs +4 -0
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +29 -3
- data/sig/errors.rbs +6 -0
- data/sig/loggable.rbs +2 -0
- data/sig/options.rbs +7 -0
- data/sig/plugins/aws_sigv4.rbs +8 -2
- data/sig/plugins/content_digest.rbs +51 -0
- data/sig/plugins/cookies/cookie.rbs +9 -0
- data/sig/plugins/grpc/call.rbs +4 -0
- data/sig/plugins/persistent.rbs +4 -1
- data/sig/plugins/proxy/socks5.rbs +11 -3
- data/sig/plugins/proxy.rbs +18 -11
- data/sig/plugins/push_promise.rbs +3 -0
- data/sig/plugins/rate_limiter.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/ssrf_filter.rbs +26 -0
- data/sig/plugins/webdav.rbs +23 -0
- data/sig/plugins/xml.rbs +37 -0
- data/sig/pool.rbs +25 -33
- data/sig/request/body.rbs +5 -9
- data/sig/resolver/multi.rbs +26 -1
- data/sig/resolver/native.rbs +2 -2
- data/sig/resolver/resolver.rbs +21 -2
- data/sig/resolver.rbs +5 -1
- data/sig/response/buffer.rbs +1 -1
- data/sig/selector.rbs +30 -4
- data/sig/session.rbs +47 -18
- data/sig/transcoder/body.rbs +2 -4
- data/sig/transcoder/chunker.rbs +1 -1
- data/sig/transcoder/deflate.rbs +1 -0
- data/sig/transcoder/form.rbs +8 -0
- data/sig/transcoder/gzip.rbs +4 -1
- data/sig/transcoder/utils/body_reader.rbs +3 -3
- data/sig/transcoder/utils/deflater.rbs +3 -3
- metadata +12 -4
- data/lib/httpx/transcoder/xml.rb +0 -52
- data/sig/transcoder/xml.rbs +0 -22
data/lib/httpx/pool.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "forwardable"
|
4
3
|
require "httpx/selector"
|
5
4
|
require "httpx/connection"
|
6
5
|
require "httpx/resolver"
|
@@ -8,110 +7,31 @@ require "httpx/resolver"
|
|
8
7
|
module HTTPX
|
9
8
|
class Pool
|
10
9
|
using ArrayExtensions::FilterMap
|
11
|
-
|
10
|
+
using URIExtensions
|
12
11
|
|
13
|
-
|
12
|
+
POOL_TIMEOUT = 5
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
# Sets up the connection pool with the given +options+, which can be the following:
|
15
|
+
#
|
16
|
+
# :max_connections_per_origin :: the maximum number of connections held in the pool pointing to a given origin.
|
17
|
+
# :pool_timeout :: the number of seconds to wait for a connection to a given origin (before raising HTTPX::PoolTimeoutError)
|
18
|
+
#
|
19
|
+
def initialize(options)
|
20
|
+
@max_connections_per_origin = options.fetch(:max_connections_per_origin, Float::INFINITY)
|
21
|
+
@pool_timeout = options.fetch(:pool_timeout, POOL_TIMEOUT)
|
22
|
+
@resolvers = Hash.new { |hs, resolver_type| hs[resolver_type] = [] }
|
23
|
+
@resolver_mtx = Thread::Mutex.new
|
24
24
|
@connections = []
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
ensure
|
29
|
-
@connections.unshift(*connections)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def empty?
|
34
|
-
@connections.empty?
|
35
|
-
end
|
36
|
-
|
37
|
-
def next_tick
|
38
|
-
catch(:jump_tick) do
|
39
|
-
timeout = next_timeout
|
40
|
-
if timeout && timeout.negative?
|
41
|
-
@timers.fire
|
42
|
-
throw(:jump_tick)
|
43
|
-
end
|
44
|
-
|
45
|
-
begin
|
46
|
-
@selector.select(timeout, &:call)
|
47
|
-
@timers.fire
|
48
|
-
rescue TimeoutError => e
|
49
|
-
@timers.fire(e)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
rescue StandardError => e
|
53
|
-
@connections.each do |connection|
|
54
|
-
connection.emit(:error, e)
|
55
|
-
end
|
56
|
-
rescue Exception # rubocop:disable Lint/RescueException
|
57
|
-
@connections.each(&:force_reset)
|
58
|
-
raise
|
59
|
-
end
|
60
|
-
|
61
|
-
def close(connections = @connections)
|
62
|
-
return if connections.empty?
|
63
|
-
|
64
|
-
connections = connections.reject(&:inflight?)
|
65
|
-
connections.each(&:terminate)
|
66
|
-
next_tick until connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
67
|
-
|
68
|
-
# close resolvers
|
69
|
-
outstanding_connections = @connections
|
70
|
-
resolver_connections = @resolvers.each_value.flat_map(&:connections).compact
|
71
|
-
outstanding_connections -= resolver_connections
|
72
|
-
|
73
|
-
return unless outstanding_connections.empty?
|
74
|
-
|
75
|
-
@resolvers.each_value do |resolver|
|
76
|
-
resolver.close unless resolver.closed?
|
77
|
-
end
|
78
|
-
# for https resolver
|
79
|
-
resolver_connections.each(&:terminate)
|
80
|
-
next_tick until resolver_connections.none? { |c| c.state != :idle && @connections.include?(c) }
|
81
|
-
end
|
82
|
-
|
83
|
-
def init_connection(connection, _options)
|
84
|
-
connection.timers = @timers
|
85
|
-
connection.on(:activate) do
|
86
|
-
select_connection(connection)
|
87
|
-
end
|
88
|
-
connection.on(:exhausted) do
|
89
|
-
case connection.state
|
90
|
-
when :closed
|
91
|
-
connection.idling
|
92
|
-
@connections << connection
|
93
|
-
select_connection(connection)
|
94
|
-
when :closing
|
95
|
-
connection.once(:close) do
|
96
|
-
connection.idling
|
97
|
-
@connections << connection
|
98
|
-
select_connection(connection)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
connection.on(:close) do
|
103
|
-
unregister_connection(connection)
|
104
|
-
end
|
105
|
-
connection.on(:terminate) do
|
106
|
-
unregister_connection(connection, true)
|
107
|
-
end
|
108
|
-
resolve_connection(connection) unless connection.family
|
25
|
+
@connection_mtx = Thread::Mutex.new
|
26
|
+
@origin_counters = Hash.new(0)
|
27
|
+
@origin_conds = Hash.new { |hs, orig| hs[orig] = ConditionVariable.new }
|
109
28
|
end
|
110
29
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
30
|
+
def pop_connection
|
31
|
+
@connection_mtx.synchronize do
|
32
|
+
conn = @connections.shift
|
33
|
+
@origin_conds.delete(conn.origin) if conn && (@origin_counters[conn.origin.to_s] -= 1).zero?
|
34
|
+
conn
|
115
35
|
end
|
116
36
|
end
|
117
37
|
|
@@ -119,185 +39,94 @@ module HTTPX
|
|
119
39
|
# Many hostnames are reachable through the same IP, so we try to
|
120
40
|
# maximize pipelining by opening as few connections as possible.
|
121
41
|
#
|
122
|
-
def
|
123
|
-
|
124
|
-
connection.match?(uri, options)
|
125
|
-
end
|
42
|
+
def checkout_connection(uri, options)
|
43
|
+
return checkout_new_connection(uri, options) if options.io
|
126
44
|
|
127
|
-
|
45
|
+
@connection_mtx.synchronize do
|
46
|
+
acquire_connection(uri, options) || begin
|
47
|
+
if @origin_counters[uri.origin] == @max_connections_per_origin
|
128
48
|
|
129
|
-
|
130
|
-
when :closed
|
131
|
-
conn.idling
|
132
|
-
select_connection(conn)
|
133
|
-
when :closing
|
134
|
-
conn.once(:close) do
|
135
|
-
conn.idling
|
136
|
-
select_connection(conn)
|
137
|
-
end
|
138
|
-
end
|
49
|
+
@origin_conds[uri.origin].wait(@connection_mtx, @pool_timeout)
|
139
50
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
private
|
144
|
-
|
145
|
-
def resolve_connection(connection)
|
146
|
-
@connections << connection unless @connections.include?(connection)
|
147
|
-
|
148
|
-
if connection.addresses || connection.open?
|
149
|
-
#
|
150
|
-
# there are two cases in which we want to activate initialization of
|
151
|
-
# connection immediately:
|
152
|
-
#
|
153
|
-
# 1. when the connection already has addresses, i.e. it doesn't need to
|
154
|
-
# resolve a name (not the same as name being an IP, yet)
|
155
|
-
# 2. when the connection is initialized with an external already open IO.
|
156
|
-
#
|
157
|
-
connection.once(:connect_error, &connection.method(:handle_error))
|
158
|
-
on_resolver_connection(connection)
|
159
|
-
return
|
160
|
-
end
|
51
|
+
return acquire_connection(uri, options) || raise(PoolTimeoutError.new(uri.origin, @pool_timeout))
|
52
|
+
end
|
161
53
|
|
162
|
-
|
163
|
-
resolver << try_clone_connection(connection, resolver.family)
|
164
|
-
next if resolver.empty?
|
54
|
+
@origin_counters[uri.origin] += 1
|
165
55
|
|
166
|
-
|
56
|
+
checkout_new_connection(uri, options)
|
57
|
+
end
|
167
58
|
end
|
168
59
|
end
|
169
60
|
|
170
|
-
def
|
171
|
-
connection.
|
172
|
-
|
173
|
-
return connection if connection.family == family
|
174
|
-
|
175
|
-
new_connection = connection.class.new(connection.origin, connection.options)
|
176
|
-
new_connection.family = family
|
61
|
+
def checkin_connection(connection)
|
62
|
+
return if connection.options.io
|
177
63
|
|
178
|
-
|
179
|
-
|
180
|
-
if new_connection.connecting?
|
181
|
-
new_connection.merge(connection)
|
182
|
-
connection.emit(:cloned, new_connection)
|
183
|
-
connection.force_reset
|
184
|
-
else
|
185
|
-
connection.__send__(:handle_error, err)
|
186
|
-
end
|
187
|
-
end
|
64
|
+
@connection_mtx.synchronize do
|
65
|
+
@connections << connection
|
188
66
|
|
189
|
-
|
190
|
-
if new_conn != connection
|
191
|
-
new_conn.merge(connection)
|
192
|
-
connection.force_reset
|
193
|
-
end
|
194
|
-
end
|
195
|
-
new_connection.once(:connect_error) do |err|
|
196
|
-
if connection.connecting?
|
197
|
-
# main connection has the requests
|
198
|
-
connection.merge(new_connection)
|
199
|
-
new_connection.emit(:cloned, connection)
|
200
|
-
new_connection.force_reset
|
201
|
-
else
|
202
|
-
new_connection.__send__(:handle_error, err)
|
203
|
-
end
|
67
|
+
@origin_conds[connection.origin.to_s].signal
|
204
68
|
end
|
205
|
-
|
206
|
-
init_connection(new_connection, connection.options)
|
207
|
-
new_connection
|
208
69
|
end
|
209
70
|
|
210
|
-
def
|
211
|
-
|
212
|
-
found_connection = @connections.find do |ch|
|
213
|
-
ch != connection && ch.mergeable?(connection)
|
214
|
-
end
|
215
|
-
return register_connection(connection) unless found_connection
|
71
|
+
def checkout_mergeable_connection(connection)
|
72
|
+
return if connection.options.io
|
216
73
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
else
|
221
|
-
found_connection.once(:open) do
|
222
|
-
coalesce_connections(found_connection, connection)
|
74
|
+
@connection_mtx.synchronize do
|
75
|
+
idx = @connections.find_index do |ch|
|
76
|
+
ch != connection && ch.mergeable?(connection)
|
223
77
|
end
|
78
|
+
@connections.delete_at(idx) if idx
|
224
79
|
end
|
225
80
|
end
|
226
81
|
|
227
|
-
def
|
228
|
-
|
229
|
-
|
230
|
-
connection.emit(:error, error)
|
82
|
+
def reset_resolvers
|
83
|
+
@resolver_mtx.synchronize { @resolvers.clear }
|
231
84
|
end
|
232
85
|
|
233
|
-
def
|
234
|
-
resolver_type =
|
235
|
-
|
86
|
+
def checkout_resolver(options)
|
87
|
+
resolver_type = options.resolver_class
|
88
|
+
resolver_type = Resolver.resolver_for(resolver_type)
|
236
89
|
|
237
|
-
@
|
90
|
+
@resolver_mtx.synchronize do
|
91
|
+
resolvers = @resolvers[resolver_type]
|
238
92
|
|
239
|
-
|
240
|
-
|
93
|
+
idx = resolvers.find_index do |res|
|
94
|
+
res.options == options
|
95
|
+
end
|
96
|
+
resolvers.delete_at(idx) if idx
|
97
|
+
end || checkout_new_resolver(resolver_type, options)
|
241
98
|
end
|
242
99
|
|
243
|
-
def
|
244
|
-
|
245
|
-
|
100
|
+
def checkin_resolver(resolver)
|
101
|
+
@resolver_mtx.synchronize do
|
102
|
+
resolvers = @resolvers[resolver.class]
|
246
103
|
|
247
|
-
|
248
|
-
@connections.delete(connection) if cleanup
|
249
|
-
deselect_connection(connection)
|
250
|
-
end
|
104
|
+
resolver = resolver.multi
|
251
105
|
|
252
|
-
|
253
|
-
|
106
|
+
resolvers << resolver unless resolvers.include?(resolver)
|
107
|
+
end
|
254
108
|
end
|
255
109
|
|
256
|
-
|
257
|
-
@selector.deregister(connection)
|
258
|
-
end
|
110
|
+
private
|
259
111
|
|
260
|
-
def
|
261
|
-
|
112
|
+
def acquire_connection(uri, options)
|
113
|
+
idx = @connections.find_index do |connection|
|
114
|
+
connection.match?(uri, options)
|
115
|
+
end
|
262
116
|
|
263
|
-
|
264
|
-
conn1.merge(conn2)
|
265
|
-
@connections.delete(conn2)
|
117
|
+
@connections.delete_at(idx) if idx
|
266
118
|
end
|
267
119
|
|
268
|
-
def
|
269
|
-
|
270
|
-
@timers.wait_interval,
|
271
|
-
*@resolvers.values.reject(&:closed?).filter_map(&:timeout),
|
272
|
-
*@connections.filter_map(&:timeout),
|
273
|
-
].compact.min
|
120
|
+
def checkout_new_connection(uri, options)
|
121
|
+
options.connection_class.new(uri, options)
|
274
122
|
end
|
275
123
|
|
276
|
-
def
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
@resolvers[resolver_type] ||= begin
|
282
|
-
resolver_manager = if resolver_type.multi?
|
283
|
-
Resolver::Multi.new(resolver_type, connection_options)
|
284
|
-
else
|
285
|
-
resolver_type.new(connection_options)
|
286
|
-
end
|
287
|
-
resolver_manager.on(:resolve, &method(:on_resolver_connection))
|
288
|
-
resolver_manager.on(:error, &method(:on_resolver_error))
|
289
|
-
resolver_manager.on(:close, &method(:on_resolver_close))
|
290
|
-
resolver_manager
|
291
|
-
end
|
292
|
-
|
293
|
-
manager = @resolvers[resolver_type]
|
294
|
-
|
295
|
-
(manager.is_a?(Resolver::Multi) && manager.early_resolve(connection)) || manager.resolvers.each do |resolver|
|
296
|
-
resolver.pool = self
|
297
|
-
yield resolver
|
124
|
+
def checkout_new_resolver(resolver_type, options)
|
125
|
+
if resolver_type.multi?
|
126
|
+
Resolver::Multi.new(resolver_type, options)
|
127
|
+
else
|
128
|
+
resolver_type.new(options)
|
298
129
|
end
|
299
|
-
|
300
|
-
manager
|
301
130
|
end
|
302
131
|
end
|
303
132
|
end
|
data/lib/httpx/request/body.rb
CHANGED
@@ -25,20 +25,11 @@ module HTTPX
|
|
25
25
|
# ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
|
26
26
|
# ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
|
27
27
|
# ..., form: { body: "bla") }) #=> raw data encoder
|
28
|
-
def initialize(
|
29
|
-
@headers =
|
28
|
+
def initialize(h, options, **params)
|
29
|
+
@headers = h
|
30
|
+
@body = self.class.initialize_body(params)
|
30
31
|
@options = options.merge(params)
|
31
32
|
|
32
|
-
@body = if body
|
33
|
-
Transcoder::Body.encode(body)
|
34
|
-
elsif form
|
35
|
-
Transcoder::Form.encode(form)
|
36
|
-
elsif json
|
37
|
-
Transcoder::JSON.encode(json)
|
38
|
-
elsif xml
|
39
|
-
Transcoder::Xml.encode(xml)
|
40
|
-
end
|
41
|
-
|
42
33
|
if @body
|
43
34
|
if @options.compress_request_body && @headers.key?("content-encoding")
|
44
35
|
|
@@ -61,7 +52,11 @@ module HTTPX
|
|
61
52
|
|
62
53
|
body = stream(@body)
|
63
54
|
if body.respond_to?(:read)
|
64
|
-
|
55
|
+
while (chunk = body.read(16_384))
|
56
|
+
block.call(chunk)
|
57
|
+
end
|
58
|
+
# TODO: use copy_stream once bug is resolved: https://bugs.ruby-lang.org/issues/21131
|
59
|
+
# ::IO.copy_stream(body, ProcIO.new(block))
|
65
60
|
elsif body.respond_to?(:each)
|
66
61
|
body.each(&block)
|
67
62
|
else
|
@@ -69,6 +64,10 @@ module HTTPX
|
|
69
64
|
end
|
70
65
|
end
|
71
66
|
|
67
|
+
def close
|
68
|
+
@body.close if @body.respond_to?(:close)
|
69
|
+
end
|
70
|
+
|
72
71
|
# if the +@body+ is rewindable, it rewinnds it.
|
73
72
|
def rewind
|
74
73
|
return if empty?
|
@@ -123,6 +122,19 @@ module HTTPX
|
|
123
122
|
# :nocov:
|
124
123
|
|
125
124
|
class << self
|
125
|
+
def initialize_body(params)
|
126
|
+
if (body = params.delete(:body))
|
127
|
+
# @type var body: bodyIO
|
128
|
+
Transcoder::Body.encode(body)
|
129
|
+
elsif (form = params.delete(:form))
|
130
|
+
# @type var form: Transcoder::urlencoded_input
|
131
|
+
Transcoder::Form.encode(form)
|
132
|
+
elsif (json = params.delete(:json))
|
133
|
+
# @type var body: _ToJson
|
134
|
+
Transcoder::JSON.encode(json)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
126
138
|
# returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
|
127
139
|
def initialize_deflater_body(body, encoding)
|
128
140
|
case encoding
|
@@ -138,17 +150,4 @@ module HTTPX
|
|
138
150
|
end
|
139
151
|
end
|
140
152
|
end
|
141
|
-
|
142
|
-
# Wrapper yielder which can be used with functions which expect an IO writer.
|
143
|
-
class ProcIO
|
144
|
-
def initialize(block)
|
145
|
-
@block = block
|
146
|
-
end
|
147
|
-
|
148
|
-
# Implementation the IO write protocol, which yield the given chunk to +@block+.
|
149
|
-
def write(data)
|
150
|
-
@block.call(data.dup)
|
151
|
-
data.bytesize
|
152
|
-
end
|
153
|
-
end
|
154
153
|
end
|
data/lib/httpx/request.rb
CHANGED
@@ -11,8 +11,10 @@ module HTTPX
|
|
11
11
|
include Callbacks
|
12
12
|
using URIExtensions
|
13
13
|
|
14
|
+
ALLOWED_URI_SCHEMES = %w[https http].freeze
|
15
|
+
|
14
16
|
# default value used for "user-agent" header, when not overridden.
|
15
|
-
USER_AGENT = "httpx.rb/#{VERSION}"
|
17
|
+
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
16
18
|
|
17
19
|
# the upcased string HTTP verb for this request.
|
18
20
|
attr_reader :verb
|
@@ -92,6 +94,8 @@ module HTTPX
|
|
92
94
|
@uri = origin.merge("#{base_path}#{@uri}")
|
93
95
|
end
|
94
96
|
|
97
|
+
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
98
|
+
|
95
99
|
@state = :idle
|
96
100
|
@response = nil
|
97
101
|
@peer_address = nil
|
@@ -263,6 +267,8 @@ module HTTPX
|
|
263
267
|
return unless @state == :body
|
264
268
|
when :done
|
265
269
|
return if @state == :expect
|
270
|
+
|
271
|
+
@body.close
|
266
272
|
end
|
267
273
|
@state = nextstate
|
268
274
|
emit(@state, self)
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -27,7 +27,7 @@ module HTTPX
|
|
27
27
|
use_get: false,
|
28
28
|
}.freeze
|
29
29
|
|
30
|
-
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate
|
30
|
+
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close, :terminate, :inflight?
|
31
31
|
|
32
32
|
def initialize(_, options)
|
33
33
|
super
|
@@ -43,7 +43,7 @@ module HTTPX
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def <<(connection)
|
46
|
-
return if @uri.origin == connection.
|
46
|
+
return if @uri.origin == connection.peer.to_s
|
47
47
|
|
48
48
|
@uri_addresses ||= HTTPX::Resolver.nolookup_resolve(@uri.host) || @resolver.getaddresses(@uri.host)
|
49
49
|
|
@@ -66,30 +66,25 @@ module HTTPX
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def resolver_connection
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
# only explicity emit addresses if connection didn't pre-resolve, i.e. it's not an IP.
|
74
|
-
catch(:coalesced) do
|
75
|
-
@building_connection = false
|
76
|
-
emit_addresses(connection, @family, @uri_addresses) unless connection.addresses
|
77
|
-
connection
|
78
|
-
end
|
69
|
+
# TODO: leaks connection object into the pool
|
70
|
+
@resolver_connection ||= @current_session.find_connection(@uri, @current_selector,
|
71
|
+
@options.merge(ssl: { alpn_protocols: %w[h2] })).tap do |conn|
|
72
|
+
emit_addresses(conn, @family, @uri_addresses) unless conn.addresses
|
79
73
|
end
|
80
74
|
end
|
81
75
|
|
82
76
|
private
|
83
77
|
|
84
78
|
def resolve(connection = @connections.first, hostname = nil)
|
85
|
-
return if @building_connection
|
86
79
|
return unless connection
|
87
80
|
|
88
81
|
hostname ||= @queries.key(connection)
|
89
82
|
|
90
83
|
if hostname.nil?
|
91
|
-
hostname = connection.
|
92
|
-
log
|
84
|
+
hostname = connection.peer.host
|
85
|
+
log do
|
86
|
+
"resolver #{FAMILY_TYPES[@record_type]}: resolve IDN #{connection.peer.non_ascii_hostname} as #{hostname}"
|
87
|
+
end if connection.peer.non_ascii_hostname
|
93
88
|
|
94
89
|
hostname = @resolver.generate_candidates(hostname).each do |name|
|
95
90
|
@queries[name.to_s] = connection
|
@@ -97,7 +92,7 @@ module HTTPX
|
|
97
92
|
else
|
98
93
|
@queries[hostname] = connection
|
99
94
|
end
|
100
|
-
log { "resolver
|
95
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
101
96
|
|
102
97
|
begin
|
103
98
|
request = build_request(hostname)
|
@@ -108,7 +103,7 @@ module HTTPX
|
|
108
103
|
@connections << connection
|
109
104
|
rescue ResolveError, Resolv::DNS::EncodeError => e
|
110
105
|
reset_hostname(hostname)
|
111
|
-
emit_resolve_error(connection, connection.
|
106
|
+
emit_resolve_error(connection, connection.peer.host, e)
|
112
107
|
end
|
113
108
|
end
|
114
109
|
|
@@ -117,7 +112,7 @@ module HTTPX
|
|
117
112
|
rescue StandardError => e
|
118
113
|
hostname = @requests.delete(request)
|
119
114
|
connection = reset_hostname(hostname)
|
120
|
-
emit_resolve_error(connection, connection.
|
115
|
+
emit_resolve_error(connection, connection.peer.host, e)
|
121
116
|
else
|
122
117
|
# @type var response: HTTPX::Response
|
123
118
|
parse(request, response)
|
@@ -156,7 +151,7 @@ module HTTPX
|
|
156
151
|
when :decode_error
|
157
152
|
host = @requests.delete(request)
|
158
153
|
connection = reset_hostname(host)
|
159
|
-
emit_resolve_error(connection, connection.
|
154
|
+
emit_resolve_error(connection, connection.peer.host, result)
|
160
155
|
end
|
161
156
|
end
|
162
157
|
|
@@ -176,7 +171,7 @@ module HTTPX
|
|
176
171
|
alias_address = answers[address["alias"]]
|
177
172
|
if alias_address.nil?
|
178
173
|
reset_hostname(address["name"])
|
179
|
-
if
|
174
|
+
if early_resolve(connection, hostname: address["alias"])
|
180
175
|
@connections.delete(connection)
|
181
176
|
else
|
182
177
|
resolve(connection, address["alias"])
|
data/lib/httpx/resolver/multi.rb
CHANGED
@@ -8,27 +8,45 @@ module HTTPX
|
|
8
8
|
include Callbacks
|
9
9
|
using ArrayExtensions::FilterMap
|
10
10
|
|
11
|
-
attr_reader :resolvers
|
11
|
+
attr_reader :resolvers, :options
|
12
12
|
|
13
13
|
def initialize(resolver_type, options)
|
14
|
+
@current_selector = nil
|
15
|
+
@current_session = nil
|
14
16
|
@options = options
|
15
17
|
@resolver_options = @options.resolver_options
|
16
18
|
|
17
19
|
@resolvers = options.ip_families.map do |ip_family|
|
18
20
|
resolver = resolver_type.new(ip_family, options)
|
19
|
-
resolver.
|
20
|
-
resolver.on(:error, &method(:on_resolver_error))
|
21
|
-
resolver.on(:close) { on_resolver_close(resolver) }
|
21
|
+
resolver.multi = self
|
22
22
|
resolver
|
23
23
|
end
|
24
24
|
|
25
25
|
@errors = Hash.new { |hs, k| hs[k] = [] }
|
26
26
|
end
|
27
27
|
|
28
|
+
def current_selector=(s)
|
29
|
+
@current_selector = s
|
30
|
+
@resolvers.each { |r| r.__send__(__method__, s) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def current_session=(s)
|
34
|
+
@current_session = s
|
35
|
+
@resolvers.each { |r| r.__send__(__method__, s) }
|
36
|
+
end
|
37
|
+
|
28
38
|
def closed?
|
29
39
|
@resolvers.all?(&:closed?)
|
30
40
|
end
|
31
41
|
|
42
|
+
def empty?
|
43
|
+
@resolvers.all?(&:empty?)
|
44
|
+
end
|
45
|
+
|
46
|
+
def inflight?
|
47
|
+
@resolvers.any(&:inflight?)
|
48
|
+
end
|
49
|
+
|
32
50
|
def timeout
|
33
51
|
@resolvers.filter_map(&:timeout).min
|
34
52
|
end
|
@@ -42,10 +60,11 @@ module HTTPX
|
|
42
60
|
end
|
43
61
|
|
44
62
|
def early_resolve(connection)
|
45
|
-
hostname = connection.
|
63
|
+
hostname = connection.peer.host
|
46
64
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
47
|
-
return unless addresses
|
65
|
+
return false unless addresses
|
48
66
|
|
67
|
+
resolved = false
|
49
68
|
addresses.group_by(&:family).sort { |(f1, _), (f2, _)| f2 <=> f1 }.each do |family, addrs|
|
50
69
|
# try to match the resolver by family. However, there are cases where that's not possible, as when
|
51
70
|
# the system does not have IPv6 connectivity, but it does support IPv6 via loopback/link-local.
|
@@ -55,21 +74,20 @@ module HTTPX
|
|
55
74
|
|
56
75
|
# it does not matter which resolver it is, as early-resolve code is shared.
|
57
76
|
resolver.emit_addresses(connection, family, addrs, true)
|
58
|
-
end
|
59
|
-
end
|
60
77
|
|
61
|
-
|
78
|
+
resolved = true
|
79
|
+
end
|
62
80
|
|
63
|
-
|
64
|
-
emit(:resolve, connection)
|
81
|
+
resolved
|
65
82
|
end
|
66
83
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
84
|
+
def lazy_resolve(connection)
|
85
|
+
@resolvers.each do |resolver|
|
86
|
+
resolver << @current_session.try_clone_connection(connection, @current_selector, resolver.family)
|
87
|
+
next if resolver.empty?
|
70
88
|
|
71
|
-
|
72
|
-
|
89
|
+
@current_session.select_resolver(resolver, @current_selector)
|
90
|
+
end
|
73
91
|
end
|
74
92
|
end
|
75
93
|
end
|