httpx 1.6.3 → 1.7.0
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/0_11_0.md +3 -3
- data/doc/release_notes/1_6_3.md +2 -2
- data/doc/release_notes/1_7_0.md +149 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/altsvc.rb +3 -1
- data/lib/httpx/connection/http1.rb +5 -6
- data/lib/httpx/connection/http2.rb +2 -0
- data/lib/httpx/connection.rb +3 -8
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/loggable.rb +2 -0
- data/lib/httpx/options.rb +40 -17
- data/lib/httpx/plugins/auth/digest.rb +44 -4
- data/lib/httpx/plugins/auth.rb +87 -4
- data/lib/httpx/plugins/aws_sdk_authentication.rb +0 -1
- data/lib/httpx/plugins/cookies/cookie.rb +1 -0
- data/lib/httpx/plugins/digest_auth.rb +4 -5
- data/lib/httpx/plugins/fiber_concurrency.rb +16 -1
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +1 -1
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/internal_telemetry.rb +1 -1
- data/lib/httpx/plugins/ntlm_auth.rb +5 -3
- data/lib/httpx/plugins/oauth.rb +162 -56
- data/lib/httpx/plugins/rate_limiter.rb +2 -2
- data/lib/httpx/plugins/response_cache.rb +3 -7
- data/lib/httpx/plugins/retries.rb +55 -16
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +59 -8
- data/lib/httpx/plugins/stream_bidi.rb +73 -17
- data/lib/httpx/pool.rb +12 -2
- data/lib/httpx/request.rb +10 -1
- data/lib/httpx/resolver/https.rb +67 -17
- data/lib/httpx/resolver/multi.rb +4 -0
- data/lib/httpx/resolver/native.rb +26 -4
- data/lib/httpx/resolver/resolver.rb +2 -2
- data/lib/httpx/resolver.rb +97 -29
- data/lib/httpx/response/body.rb +2 -0
- data/lib/httpx/response.rb +22 -6
- data/lib/httpx/selector.rb +9 -0
- data/lib/httpx/session.rb +6 -6
- data/lib/httpx/transcoder/body.rb +1 -1
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/transcoder/multipart/decoder.rb +4 -4
- data/lib/httpx/transcoder/multipart/encoder.rb +1 -1
- data/lib/httpx/transcoder/multipart.rb +16 -8
- data/lib/httpx/transcoder.rb +4 -6
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +3 -0
- data/sig/chainable.rbs +3 -3
- data/sig/connection.rbs +1 -3
- data/sig/options.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +6 -0
- data/sig/plugins/auth.rbs +28 -4
- data/sig/plugins/basic_auth.rbs +3 -3
- data/sig/plugins/digest_auth.rbs +2 -4
- data/sig/plugins/fiber_concurrency.rbs +6 -0
- data/sig/plugins/ntlm_auth.rbs +2 -2
- data/sig/plugins/oauth.rbs +46 -15
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache/file_store.rbs +2 -0
- data/sig/plugins/response_cache.rbs +4 -0
- data/sig/plugins/retries.rbs +8 -2
- data/sig/plugins/stream.rbs +13 -3
- data/sig/plugins/stream_bidi.rbs +2 -2
- data/sig/pool.rbs +1 -1
- data/sig/resolver/https.rbs +5 -0
- data/sig/resolver/multi.rbs +2 -0
- data/sig/resolver/native.rbs +2 -0
- data/sig/resolver.rbs +12 -3
- data/sig/response.rbs +3 -0
- data/sig/session.rbs +3 -5
- data/sig/transcoder/multipart.rbs +4 -2
- data/sig/transcoder.rbs +5 -1
- metadata +3 -1
|
@@ -26,6 +26,8 @@ module HTTPX
|
|
|
26
26
|
class_eval(<<-METH, __FILE__, __LINE__ + 1)
|
|
27
27
|
# lock.aware version of +#{lock_meth}+
|
|
28
28
|
def #{lock_meth}(*) # def close(*)
|
|
29
|
+
return super unless @options.stream
|
|
30
|
+
|
|
29
31
|
return super if @lock.owned?
|
|
30
32
|
|
|
31
33
|
# small race condition between
|
|
@@ -43,6 +45,8 @@ module HTTPX
|
|
|
43
45
|
class_eval(<<-METH, __FILE__, __LINE__ + 1)
|
|
44
46
|
# lock.aware version of +#{lock_meth}+
|
|
45
47
|
private def #{lock_meth}(*) # private def join_headers(*)
|
|
48
|
+
return super unless @options.stream
|
|
49
|
+
|
|
46
50
|
return super if @lock.owned?
|
|
47
51
|
|
|
48
52
|
# small race condition between
|
|
@@ -55,6 +59,8 @@ module HTTPX
|
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def handle_stream(stream, request)
|
|
62
|
+
return super unless @options.stream
|
|
63
|
+
|
|
58
64
|
request.on(:body) do
|
|
59
65
|
next unless request.headers_sent
|
|
60
66
|
|
|
@@ -67,6 +73,8 @@ module HTTPX
|
|
|
67
73
|
|
|
68
74
|
# when there ain't more chunks, it makes the buffer as full.
|
|
69
75
|
def send_chunk(request, stream, chunk, next_chunk)
|
|
76
|
+
return super unless @options.stream
|
|
77
|
+
|
|
70
78
|
super
|
|
71
79
|
|
|
72
80
|
return if next_chunk
|
|
@@ -77,39 +85,60 @@ module HTTPX
|
|
|
77
85
|
|
|
78
86
|
# sets end-stream flag when the request is closed.
|
|
79
87
|
def end_stream?(request, next_chunk)
|
|
88
|
+
return super unless @options.stream
|
|
89
|
+
|
|
80
90
|
request.closed? && next_chunk.nil?
|
|
81
91
|
end
|
|
82
92
|
end
|
|
83
93
|
|
|
84
|
-
# BidiBuffer is a Buffer which can
|
|
85
|
-
#
|
|
94
|
+
# BidiBuffer is a thread-safe Buffer which can receive data from any thread.
|
|
95
|
+
#
|
|
96
|
+
# It uses a dual-buffer strategy with mutex protection:
|
|
97
|
+
# - +@buffer+ is the main buffer, protected by +@buffer_mutex+
|
|
98
|
+
# - +@oob_buffer+ receives data when +@buffer_mutex+ is contended
|
|
86
99
|
#
|
|
87
|
-
#
|
|
88
|
-
# is reconciled to the main internal +@buffer+.
|
|
100
|
+
# This allows non-blocking writes from any thread while maintaining thread safety.
|
|
89
101
|
class BidiBuffer < Buffer
|
|
90
102
|
def initialize(*)
|
|
91
103
|
super
|
|
92
|
-
@
|
|
104
|
+
@buffer_mutex = Thread::Mutex.new
|
|
93
105
|
@oob_mutex = Thread::Mutex.new
|
|
94
106
|
@oob_buffer = "".b
|
|
95
107
|
end
|
|
96
108
|
|
|
97
|
-
# buffers the +chunk+ to be sent
|
|
109
|
+
# buffers the +chunk+ to be sent (thread-safe, non-blocking)
|
|
98
110
|
def <<(chunk)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
if @buffer_mutex.try_lock
|
|
112
|
+
begin
|
|
113
|
+
super
|
|
114
|
+
ensure
|
|
115
|
+
@buffer_mutex.unlock
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
# another thread holds the lock, use OOB buffer to avoid blocking
|
|
119
|
+
@oob_mutex.synchronize { @oob_buffer << chunk }
|
|
120
|
+
end
|
|
102
121
|
end
|
|
103
122
|
|
|
104
|
-
# reconciles the main and secondary buffer (
|
|
123
|
+
# reconciles the main and secondary buffer (thread-safe, callable from any thread).
|
|
105
124
|
def rebuffer
|
|
106
|
-
|
|
125
|
+
@buffer_mutex.synchronize do
|
|
126
|
+
@oob_mutex.synchronize do
|
|
127
|
+
return if @oob_buffer.empty?
|
|
107
128
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
@buffer << @oob_buffer
|
|
130
|
+
@oob_buffer.clear
|
|
131
|
+
end
|
|
111
132
|
end
|
|
112
133
|
end
|
|
134
|
+
|
|
135
|
+
Buffer.instance_methods - Object.instance_methods - %i[<<].each do |meth|
|
|
136
|
+
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
|
137
|
+
def #{meth}(*) # def empty?
|
|
138
|
+
@buffer_mutex.synchronize { super }
|
|
139
|
+
end
|
|
140
|
+
MOD
|
|
141
|
+
end
|
|
113
142
|
end
|
|
114
143
|
|
|
115
144
|
# Proxy to wake up the session main loop when one
|
|
@@ -156,7 +185,13 @@ module HTTPX
|
|
|
156
185
|
|
|
157
186
|
def timeout; end
|
|
158
187
|
|
|
188
|
+
def inflight?
|
|
189
|
+
!@closed
|
|
190
|
+
end
|
|
191
|
+
|
|
159
192
|
def terminate
|
|
193
|
+
return if @closed
|
|
194
|
+
|
|
160
195
|
@pipe_write.close
|
|
161
196
|
@pipe_read.close
|
|
162
197
|
@closed = true
|
|
@@ -190,16 +225,20 @@ module HTTPX
|
|
|
190
225
|
def close(selector = Selector.new)
|
|
191
226
|
@signal.terminate
|
|
192
227
|
selector.deregister(@signal)
|
|
193
|
-
super
|
|
228
|
+
super
|
|
194
229
|
end
|
|
195
230
|
|
|
196
231
|
def select_connection(connection, selector)
|
|
232
|
+
return super unless connection.options.stream
|
|
233
|
+
|
|
197
234
|
super
|
|
198
235
|
selector.register(@signal)
|
|
199
236
|
connection.signal = @signal
|
|
200
237
|
end
|
|
201
238
|
|
|
202
239
|
def deselect_connection(connection, *)
|
|
240
|
+
return super unless connection.options.stream
|
|
241
|
+
|
|
203
242
|
super
|
|
204
243
|
|
|
205
244
|
connection.signal = nil
|
|
@@ -219,10 +258,14 @@ module HTTPX
|
|
|
219
258
|
end
|
|
220
259
|
|
|
221
260
|
def closed?
|
|
261
|
+
return super unless @options.stream
|
|
262
|
+
|
|
222
263
|
@closed
|
|
223
264
|
end
|
|
224
265
|
|
|
225
266
|
def can_buffer?
|
|
267
|
+
return super unless @options.stream
|
|
268
|
+
|
|
226
269
|
super && @state != :waiting_for_chunk
|
|
227
270
|
end
|
|
228
271
|
|
|
@@ -230,6 +273,8 @@ module HTTPX
|
|
|
230
273
|
# +:waiting_for_chunk+ state, which the request transitions to once payload
|
|
231
274
|
# is buffered.
|
|
232
275
|
def transition(nextstate)
|
|
276
|
+
return super unless @options.stream
|
|
277
|
+
|
|
233
278
|
headers_sent = @headers_sent
|
|
234
279
|
|
|
235
280
|
case nextstate
|
|
@@ -264,6 +309,8 @@ module HTTPX
|
|
|
264
309
|
end
|
|
265
310
|
|
|
266
311
|
def close
|
|
312
|
+
return super unless @options.stream
|
|
313
|
+
|
|
267
314
|
@mutex.synchronize do
|
|
268
315
|
return if @closed
|
|
269
316
|
|
|
@@ -278,10 +325,12 @@ module HTTPX
|
|
|
278
325
|
module RequestBodyMethods
|
|
279
326
|
def initialize(*, **)
|
|
280
327
|
super
|
|
281
|
-
@headers.delete("content-length")
|
|
328
|
+
@headers.delete("content-length") if @options.stream
|
|
282
329
|
end
|
|
283
330
|
|
|
284
331
|
def empty?
|
|
332
|
+
return super unless @options.stream
|
|
333
|
+
|
|
285
334
|
false
|
|
286
335
|
end
|
|
287
336
|
end
|
|
@@ -293,18 +342,23 @@ module HTTPX
|
|
|
293
342
|
|
|
294
343
|
def initialize(*)
|
|
295
344
|
super
|
|
345
|
+
|
|
346
|
+
return unless @options.stream
|
|
347
|
+
|
|
296
348
|
@write_buffer = BidiBuffer.new(@options.buffer_size)
|
|
297
349
|
end
|
|
298
350
|
|
|
299
351
|
# rebuffers the +@write_buffer+ before calculating interests.
|
|
300
352
|
def interests
|
|
353
|
+
return super unless @options.stream
|
|
354
|
+
|
|
301
355
|
@write_buffer.rebuffer
|
|
302
356
|
|
|
303
357
|
super
|
|
304
358
|
end
|
|
305
359
|
|
|
306
360
|
def call
|
|
307
|
-
return super unless (error = @signal.error)
|
|
361
|
+
return super unless @options.stream && (error = @signal.error)
|
|
308
362
|
|
|
309
363
|
on_error(error)
|
|
310
364
|
end
|
|
@@ -312,6 +366,8 @@ module HTTPX
|
|
|
312
366
|
private
|
|
313
367
|
|
|
314
368
|
def set_parser_callbacks(parser)
|
|
369
|
+
return super unless @options.stream
|
|
370
|
+
|
|
315
371
|
super
|
|
316
372
|
parser.on(:flush_buffer) do
|
|
317
373
|
@signal.wakeup if @signal
|
data/lib/httpx/pool.rb
CHANGED
|
@@ -122,6 +122,12 @@ module HTTPX
|
|
|
122
122
|
|
|
123
123
|
@max_connections_cond.signal
|
|
124
124
|
@origin_conds[connection.origin.to_s].signal
|
|
125
|
+
|
|
126
|
+
# Observed situations where a session handling multiple requests in a loop
|
|
127
|
+
# across multiple threads checks the same connection in and out, while another
|
|
128
|
+
# thread which is waiting on the same connection never gets the chance to pick
|
|
129
|
+
# it up, because ruby's thread scheduler never switched on to it in the process.
|
|
130
|
+
Thread.pass
|
|
125
131
|
end
|
|
126
132
|
end
|
|
127
133
|
|
|
@@ -193,15 +199,19 @@ module HTTPX
|
|
|
193
199
|
end
|
|
194
200
|
|
|
195
201
|
def checkout_new_connection(uri, options)
|
|
196
|
-
options.connection_class.new(uri, options)
|
|
202
|
+
connection = options.connection_class.new(uri, options)
|
|
203
|
+
connection.log(level: 2) { "created connection##{connection.object_id} in pool##{object_id}" }
|
|
204
|
+
connection
|
|
197
205
|
end
|
|
198
206
|
|
|
199
207
|
def checkout_new_resolver(resolver_type, options)
|
|
200
|
-
if resolver_type.multi?
|
|
208
|
+
resolver = if resolver_type.multi?
|
|
201
209
|
Resolver::Multi.new(resolver_type, options)
|
|
202
210
|
else
|
|
203
211
|
resolver_type.new(options)
|
|
204
212
|
end
|
|
213
|
+
resolver.log(level: 2) { "created resolver##{resolver.object_id} in pool##{object_id}" }
|
|
214
|
+
resolver
|
|
205
215
|
end
|
|
206
216
|
|
|
207
217
|
# drops and returns the +connection+ from the connection pool; if +connection+ is <tt>nil</tt> (default),
|
data/lib/httpx/request.rb
CHANGED
|
@@ -10,6 +10,7 @@ module HTTPX
|
|
|
10
10
|
extend Forwardable
|
|
11
11
|
include Loggable
|
|
12
12
|
include Callbacks
|
|
13
|
+
|
|
13
14
|
using URIExtensions
|
|
14
15
|
|
|
15
16
|
ALLOWED_URI_SCHEMES = %w[https http].freeze
|
|
@@ -90,12 +91,20 @@ module HTTPX
|
|
|
90
91
|
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
|
91
92
|
|
|
92
93
|
@state = :idle
|
|
93
|
-
@response = @peer_address = @
|
|
94
|
+
@response = @peer_address = @informational_status = nil
|
|
94
95
|
@ping = false
|
|
95
96
|
@persistent = @options.persistent
|
|
96
97
|
@active_timeouts = []
|
|
97
98
|
end
|
|
98
99
|
|
|
100
|
+
# dupped initialization
|
|
101
|
+
def initialize_dup(orig)
|
|
102
|
+
super
|
|
103
|
+
@uri = orig.instance_variable_get(:@uri).dup
|
|
104
|
+
@headers = orig.instance_variable_get(:@headers).dup
|
|
105
|
+
@body = orig.instance_variable_get(:@body).dup
|
|
106
|
+
end
|
|
107
|
+
|
|
99
108
|
def complete!(response = @response)
|
|
100
109
|
emit(:complete, response)
|
|
101
110
|
end
|
data/lib/httpx/resolver/https.rb
CHANGED
|
@@ -12,6 +12,7 @@ module HTTPX
|
|
|
12
12
|
#
|
|
13
13
|
class Resolver::HTTPS < Resolver::Resolver
|
|
14
14
|
extend Forwardable
|
|
15
|
+
|
|
15
16
|
using URIExtensions
|
|
16
17
|
|
|
17
18
|
module DNSExtensions
|
|
@@ -38,10 +39,12 @@ module HTTPX
|
|
|
38
39
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
|
39
40
|
@queries = {}
|
|
40
41
|
@requests = {}
|
|
42
|
+
@_timeouts = Array(@resolver_options[:timeouts])
|
|
43
|
+
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
|
41
44
|
@uri = URI(@resolver_options[:uri])
|
|
42
|
-
@uri_addresses = nil
|
|
45
|
+
@name = @uri_addresses = nil
|
|
43
46
|
@resolver = Resolv::DNS.new
|
|
44
|
-
@resolver.timeouts = @
|
|
47
|
+
@resolver.timeouts = @_timeouts.empty? ? Resolver::RESOLVE_TIMEOUT : @_timeouts
|
|
45
48
|
@resolver.lazy_initialize
|
|
46
49
|
end
|
|
47
50
|
|
|
@@ -96,21 +99,26 @@ module HTTPX
|
|
|
96
99
|
else
|
|
97
100
|
@queries[hostname] = connection
|
|
98
101
|
end
|
|
102
|
+
|
|
103
|
+
@name = hostname
|
|
104
|
+
|
|
99
105
|
log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
send_request(hostname, connection)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def send_request(hostname, connection)
|
|
111
|
+
request = build_request(hostname)
|
|
112
|
+
request.on(:response, &method(:on_response).curry(2)[request])
|
|
113
|
+
request.on(:promise, &method(:on_promise))
|
|
114
|
+
@requests[request] = hostname
|
|
115
|
+
resolver_connection.send(request)
|
|
116
|
+
@connections << connection
|
|
117
|
+
rescue ResolveError, Resolv::DNS::EncodeError => e
|
|
118
|
+
reset_hostname(hostname)
|
|
119
|
+
throw(:resolve_error, e) if connection.pending.empty?
|
|
120
|
+
emit_resolve_error(connection, connection.peer.host, e)
|
|
121
|
+
close_or_resolve
|
|
114
122
|
end
|
|
115
123
|
|
|
116
124
|
def on_response(request, response)
|
|
@@ -122,6 +130,18 @@ module HTTPX
|
|
|
122
130
|
close_or_resolve
|
|
123
131
|
else
|
|
124
132
|
# @type var response: HTTPX::Response
|
|
133
|
+
if response.status.between?(300, 399) && response.headers.key?("location")
|
|
134
|
+
hostname = @requests[request]
|
|
135
|
+
connection = @queries[hostname]
|
|
136
|
+
location_uri = URI(response.headers["location"])
|
|
137
|
+
location_uri = response.uri.merge(location_uri) if location_uri.relative?
|
|
138
|
+
|
|
139
|
+
# we assume that the DNS server URI changed permanently and move on
|
|
140
|
+
@uri = location_uri
|
|
141
|
+
send_request(hostname, connection)
|
|
142
|
+
return
|
|
143
|
+
end
|
|
144
|
+
|
|
125
145
|
parse(request, response)
|
|
126
146
|
ensure
|
|
127
147
|
@requests.delete(request)
|
|
@@ -133,6 +153,10 @@ module HTTPX
|
|
|
133
153
|
end
|
|
134
154
|
|
|
135
155
|
def parse(request, response)
|
|
156
|
+
hostname = @name
|
|
157
|
+
|
|
158
|
+
@name = nil
|
|
159
|
+
|
|
136
160
|
code, result = decode_response_body(response)
|
|
137
161
|
|
|
138
162
|
case code
|
|
@@ -151,6 +175,23 @@ module HTTPX
|
|
|
151
175
|
end
|
|
152
176
|
|
|
153
177
|
resolve
|
|
178
|
+
when :retriable_error
|
|
179
|
+
timeouts = @timeouts[hostname]
|
|
180
|
+
|
|
181
|
+
unless timeouts.empty?
|
|
182
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: failed, but will retry..." }
|
|
183
|
+
|
|
184
|
+
connection = @queries[hostname]
|
|
185
|
+
|
|
186
|
+
resolve(connection, hostname)
|
|
187
|
+
return
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
host = @requests.delete(request)
|
|
191
|
+
connection = reset_hostname(host)
|
|
192
|
+
|
|
193
|
+
emit_resolve_error(connection)
|
|
194
|
+
close_or_resolve
|
|
154
195
|
when :dns_error
|
|
155
196
|
host = @requests.delete(request)
|
|
156
197
|
connection = reset_hostname(host)
|
|
@@ -217,15 +258,18 @@ module HTTPX
|
|
|
217
258
|
uri = @uri.dup
|
|
218
259
|
rklass = @options.request_class
|
|
219
260
|
payload = Resolver.encode_dns_query(hostname, type: @record_type)
|
|
261
|
+
timeouts = @timeouts[hostname]
|
|
262
|
+
request_timeout = timeouts.shift
|
|
263
|
+
options = @options.merge(timeout: { request_timeout: request_timeout })
|
|
220
264
|
|
|
221
265
|
if @resolver_options[:use_get]
|
|
222
266
|
params = URI.decode_www_form(uri.query.to_s)
|
|
223
267
|
params << ["type", FAMILY_TYPES[@record_type]]
|
|
224
268
|
params << ["dns", Base64.urlsafe_encode64(payload, padding: false)]
|
|
225
269
|
uri.query = URI.encode_www_form(params)
|
|
226
|
-
request = rklass.new("GET", uri,
|
|
270
|
+
request = rklass.new("GET", uri, options)
|
|
227
271
|
else
|
|
228
|
-
request = rklass.new("POST", uri,
|
|
272
|
+
request = rklass.new("POST", uri, options, body: [payload])
|
|
229
273
|
request.headers["content-type"] = "application/dns-message"
|
|
230
274
|
end
|
|
231
275
|
request.headers["accept"] = "application/dns-message"
|
|
@@ -243,6 +287,7 @@ module HTTPX
|
|
|
243
287
|
end
|
|
244
288
|
|
|
245
289
|
def reset_hostname(hostname, reset_candidates: true)
|
|
290
|
+
@timeouts.delete(hostname)
|
|
246
291
|
connection = @queries.delete(hostname)
|
|
247
292
|
|
|
248
293
|
return connection unless connection && reset_candidates
|
|
@@ -250,6 +295,8 @@ module HTTPX
|
|
|
250
295
|
# eliminate other candidates
|
|
251
296
|
candidates = @queries.select { |_, conn| connection == conn }.keys
|
|
252
297
|
@queries.delete_if { |h, _| candidates.include?(h) }
|
|
298
|
+
# reset timeouts
|
|
299
|
+
@timeouts.delete_if { |h, _| candidates.include?(h) }
|
|
253
300
|
|
|
254
301
|
connection
|
|
255
302
|
end
|
|
@@ -259,6 +306,9 @@ module HTTPX
|
|
|
259
306
|
@connections.shift until @connections.empty? || @connections.first.state != :closed
|
|
260
307
|
|
|
261
308
|
if (@connections - @queries.values).empty?
|
|
309
|
+
# the same resolver connection may be serving different https resolvers (AAAA and A).
|
|
310
|
+
return if inflight?
|
|
311
|
+
|
|
262
312
|
if should_deactivate
|
|
263
313
|
deactivate
|
|
264
314
|
else
|
data/lib/httpx/resolver/multi.rb
CHANGED
|
@@ -9,6 +9,7 @@ module HTTPX
|
|
|
9
9
|
#
|
|
10
10
|
class Resolver::Native < Resolver::Resolver
|
|
11
11
|
extend Forwardable
|
|
12
|
+
|
|
12
13
|
using URIExtensions
|
|
13
14
|
|
|
14
15
|
DEFAULTS = {
|
|
@@ -298,16 +299,14 @@ module HTTPX
|
|
|
298
299
|
end
|
|
299
300
|
|
|
300
301
|
def parse(buffer)
|
|
301
|
-
@timer.cancel
|
|
302
|
-
|
|
303
|
-
@timer = @name = nil
|
|
304
|
-
|
|
305
302
|
code, result = Resolver.decode_dns_answer(buffer)
|
|
306
303
|
|
|
307
304
|
case code
|
|
308
305
|
when :ok
|
|
306
|
+
reset_query
|
|
309
307
|
parse_addresses(result)
|
|
310
308
|
when :no_domain_found
|
|
309
|
+
reset_query
|
|
311
310
|
# Indicates no such domain was found.
|
|
312
311
|
hostname, connection = @queries.first
|
|
313
312
|
reset_hostname(hostname, reset_candidates: false)
|
|
@@ -324,6 +323,7 @@ module HTTPX
|
|
|
324
323
|
close_or_resolve
|
|
325
324
|
end
|
|
326
325
|
when :message_truncated
|
|
326
|
+
reset_query
|
|
327
327
|
# TODO: what to do if it's already tcp??
|
|
328
328
|
return if @socket_type == :tcp
|
|
329
329
|
|
|
@@ -332,13 +332,29 @@ module HTTPX
|
|
|
332
332
|
hostname, _ = @queries.first
|
|
333
333
|
reset_hostname(hostname)
|
|
334
334
|
transition(:closed)
|
|
335
|
+
when :retriable_error
|
|
336
|
+
if @name && @timer
|
|
337
|
+
log { "resolver #{FAMILY_TYPES[@record_type]}: failed, but will retry..." }
|
|
338
|
+
return
|
|
339
|
+
end
|
|
340
|
+
# retry now!
|
|
341
|
+
# connection = @queries[@name].shift
|
|
342
|
+
# @timer.fire
|
|
343
|
+
reset_query
|
|
344
|
+
hostname, connection = @queries.first
|
|
345
|
+
reset_hostname(hostname)
|
|
346
|
+
@connections.delete(connection)
|
|
347
|
+
ex = NativeResolveError.new(connection, connection.peer.host, "unknown DNS error (error code #{result})")
|
|
348
|
+
raise ex
|
|
335
349
|
when :dns_error
|
|
350
|
+
reset_query
|
|
336
351
|
hostname, connection = @queries.first
|
|
337
352
|
reset_hostname(hostname)
|
|
338
353
|
@connections.delete(connection)
|
|
339
354
|
ex = NativeResolveError.new(connection, connection.peer.host, "unknown DNS error (error code #{result})")
|
|
340
355
|
raise ex
|
|
341
356
|
when :decode_error
|
|
357
|
+
reset_query
|
|
342
358
|
hostname, connection = @queries.first
|
|
343
359
|
reset_hostname(hostname)
|
|
344
360
|
@connections.delete(connection)
|
|
@@ -529,6 +545,12 @@ module HTTPX
|
|
|
529
545
|
on_error(e)
|
|
530
546
|
end
|
|
531
547
|
|
|
548
|
+
def reset_query
|
|
549
|
+
@timer.cancel
|
|
550
|
+
|
|
551
|
+
@timer = @name = nil
|
|
552
|
+
end
|
|
553
|
+
|
|
532
554
|
def reset_hostname(hostname, connection: @queries.delete(hostname), reset_candidates: true)
|
|
533
555
|
@timeouts.delete(hostname)
|
|
534
556
|
|
|
@@ -75,7 +75,7 @@ module HTTPX
|
|
|
75
75
|
|
|
76
76
|
# double emission check, but allow early resolution to work
|
|
77
77
|
conn_addrs = connection.addresses
|
|
78
|
-
return if !early_resolve && conn_addrs &&
|
|
78
|
+
return if !early_resolve && conn_addrs && !conn_addrs.empty? && !addresses.intersect?(conn_addrs)
|
|
79
79
|
|
|
80
80
|
log do
|
|
81
81
|
"resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: " \
|
|
@@ -124,7 +124,7 @@ module HTTPX
|
|
|
124
124
|
disconnect
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
-
def early_resolve(connection, hostname: connection.peer.host)
|
|
127
|
+
def early_resolve(connection, hostname: connection.peer.host) # rubocop:disable Naming/PredicateMethod
|
|
128
128
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
|
129
129
|
|
|
130
130
|
return false unless addresses
|