httpx 1.6.2 → 1.6.3
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_6_3.md +47 -0
- data/lib/httpx/adapters/sentry.rb +1 -1
- data/lib/httpx/connection/http1.rb +9 -9
- data/lib/httpx/connection/http2.rb +14 -15
- data/lib/httpx/connection.rb +115 -102
- data/lib/httpx/extensions.rb +0 -14
- data/lib/httpx/io/ssl.rb +1 -1
- data/lib/httpx/loggable.rb +12 -2
- data/lib/httpx/options.rb +20 -0
- data/lib/httpx/plugins/callbacks.rb +15 -1
- data/lib/httpx/plugins/digest_auth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +37 -9
- data/lib/httpx/plugins/response_cache/file_store.rb +1 -0
- data/lib/httpx/plugins/response_cache.rb +13 -2
- data/lib/httpx/plugins/stream_bidi.rb +15 -6
- data/lib/httpx/pool.rb +53 -19
- data/lib/httpx/request.rb +3 -13
- data/lib/httpx/resolver/https.rb +35 -19
- data/lib/httpx/resolver/multi.rb +8 -27
- data/lib/httpx/resolver/native.rb +46 -38
- data/lib/httpx/resolver/resolver.rb +45 -28
- data/lib/httpx/resolver/system.rb +63 -39
- data/lib/httpx/selector.rb +35 -20
- data/lib/httpx/session.rb +18 -28
- data/lib/httpx/transcoder/deflate.rb +13 -8
- data/lib/httpx/transcoder/utils/body_reader.rb +1 -2
- data/lib/httpx/transcoder/utils/deflater.rb +1 -2
- data/lib/httpx/version.rb +1 -1
- data/sig/connection.rbs +12 -3
- data/sig/loggable.rbs +5 -1
- data/sig/options.rbs +5 -1
- data/sig/plugins/callbacks.rbs +3 -0
- data/sig/plugins/stream_bidi.rbs +3 -5
- data/sig/resolver/https.rbs +2 -0
- data/sig/resolver/multi.rbs +0 -9
- data/sig/resolver/native.rbs +0 -2
- data/sig/resolver/resolver.rbs +9 -8
- data/sig/resolver/system.rbs +4 -2
- data/sig/selector.rbs +2 -0
- data/sig/session.rbs +5 -3
- metadata +3 -1
data/lib/httpx/loggable.rb
CHANGED
|
@@ -34,7 +34,7 @@ module HTTPX
|
|
|
34
34
|
klass = klass.superclass
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
message = +"(pid:#{Process.pid}, " \
|
|
37
|
+
message = +"(time:#{Time.now.utc}, pid:#{Process.pid}, " \
|
|
38
38
|
"tid:#{Thread.current.object_id}, " \
|
|
39
39
|
"fid:#{Fiber.current.object_id}, " \
|
|
40
40
|
"self:#{class_name}##{object_id}) "
|
|
@@ -47,7 +47,17 @@ module HTTPX
|
|
|
47
47
|
log(level: level, color: color, debug_level: debug_level, debug: debug) { ex.full_message }
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def
|
|
50
|
+
def log_redact_headers(text)
|
|
51
|
+
log_redact(text, @options.debug_redact == :headers)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def log_redact_body(text)
|
|
55
|
+
log_redact(text, @options.debug_redact == :body)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def log_redact(text, should_redact)
|
|
59
|
+
should_redact ||= @options.debug_redact == true
|
|
60
|
+
|
|
51
61
|
return text.to_s unless should_redact
|
|
52
62
|
|
|
53
63
|
"[REDACTED]"
|
data/lib/httpx/options.rb
CHANGED
|
@@ -13,6 +13,9 @@ module HTTPX
|
|
|
13
13
|
CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
|
|
14
14
|
REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
|
|
15
15
|
|
|
16
|
+
# default value used for "user-agent" header, when not overridden.
|
|
17
|
+
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
|
18
|
+
|
|
16
19
|
@options_names = []
|
|
17
20
|
|
|
18
21
|
class << self
|
|
@@ -144,6 +147,7 @@ module HTTPX
|
|
|
144
147
|
instance_variable_set(:"@#{k}", value)
|
|
145
148
|
end
|
|
146
149
|
|
|
150
|
+
do_initialize
|
|
147
151
|
freeze
|
|
148
152
|
end
|
|
149
153
|
|
|
@@ -369,6 +373,8 @@ module HTTPX
|
|
|
369
373
|
end
|
|
370
374
|
|
|
371
375
|
def option_headers(value)
|
|
376
|
+
value = value.dup if value.frozen?
|
|
377
|
+
|
|
372
378
|
headers_class.new(value)
|
|
373
379
|
end
|
|
374
380
|
|
|
@@ -395,6 +401,20 @@ module HTTPX
|
|
|
395
401
|
Array(value)
|
|
396
402
|
end
|
|
397
403
|
|
|
404
|
+
# called after all options are initialized
|
|
405
|
+
def do_initialize
|
|
406
|
+
hs = @headers
|
|
407
|
+
|
|
408
|
+
# initialized default request headers
|
|
409
|
+
hs["user-agent"] = USER_AGENT unless hs.key?("user-agent")
|
|
410
|
+
hs["accept"] = "*/*" unless hs.key?("accept")
|
|
411
|
+
if hs.key?("range")
|
|
412
|
+
hs.delete("accept-encoding")
|
|
413
|
+
else
|
|
414
|
+
hs["accept-encoding"] = supported_compression_formats unless hs.key?("accept-encoding")
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
398
418
|
def access_option(obj, k, ivar_map)
|
|
399
419
|
case obj
|
|
400
420
|
when Hash
|
|
@@ -64,7 +64,7 @@ module HTTPX
|
|
|
64
64
|
|
|
65
65
|
emit_or_callback_error(:connection_opened, connection.origin, connection.io.socket)
|
|
66
66
|
end
|
|
67
|
-
connection.on(:
|
|
67
|
+
connection.on(:callback_connection_closed) do
|
|
68
68
|
next unless connection.current_session == self
|
|
69
69
|
|
|
70
70
|
emit_or_callback_error(:connection_closed, connection.origin) if connection.used?
|
|
@@ -121,6 +121,20 @@ module HTTPX
|
|
|
121
121
|
raise e.cause
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
|
+
|
|
125
|
+
module ConnectionMethods
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def disconnect
|
|
129
|
+
return if @exhausted
|
|
130
|
+
|
|
131
|
+
return unless @current_session && @current_selector
|
|
132
|
+
|
|
133
|
+
emit(:callback_connection_closed)
|
|
134
|
+
|
|
135
|
+
super
|
|
136
|
+
end
|
|
137
|
+
end
|
|
124
138
|
end
|
|
125
139
|
register_plugin :callbacks, Callbacks
|
|
126
140
|
end
|
|
@@ -48,7 +48,7 @@ module HTTPX
|
|
|
48
48
|
|
|
49
49
|
probe_response = wrap { super(request).first }
|
|
50
50
|
|
|
51
|
-
return probe_response unless probe_response.is_a?(Response)
|
|
51
|
+
return ([probe_response] * requests.size) unless probe_response.is_a?(Response)
|
|
52
52
|
|
|
53
53
|
if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
54
54
|
request.transition(:idle)
|
|
@@ -47,6 +47,17 @@ module HTTPX
|
|
|
47
47
|
super || @state == :connecting || @state == :connected
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def force_close(*)
|
|
51
|
+
if @state == :connecting
|
|
52
|
+
# proxy connect related requests should not be reenqueed
|
|
53
|
+
@parser.reset!
|
|
54
|
+
@inflight -= @parser.pending.size
|
|
55
|
+
@parser.pending.clear
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
super
|
|
59
|
+
end
|
|
60
|
+
|
|
50
61
|
private
|
|
51
62
|
|
|
52
63
|
def handle_transition(nextstate)
|
|
@@ -64,23 +75,40 @@ module HTTPX
|
|
|
64
75
|
parser = @parser
|
|
65
76
|
parser.extend(ProxyParser)
|
|
66
77
|
parser.on(:response, &method(:__http_on_connect))
|
|
67
|
-
parser.on(:close) do
|
|
78
|
+
parser.on(:close) do
|
|
68
79
|
next unless @parser
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
emit(:terminate)
|
|
73
|
-
end
|
|
81
|
+
reset
|
|
82
|
+
disconnect
|
|
74
83
|
end
|
|
75
84
|
parser.on(:reset) do
|
|
76
85
|
if parser.empty?
|
|
77
86
|
reset
|
|
78
87
|
else
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
enqueue_pending_requests_from_parser(parser)
|
|
89
|
+
|
|
90
|
+
initial_state = @state
|
|
91
|
+
|
|
92
|
+
reset
|
|
93
|
+
|
|
94
|
+
if @pending.empty?
|
|
95
|
+
@parser = nil
|
|
96
|
+
next
|
|
97
|
+
end
|
|
98
|
+
# keep parser state around due to proxy auth protocol;
|
|
99
|
+
# intermediate authenticated request is already inside
|
|
100
|
+
# the parser
|
|
101
|
+
parser = nil
|
|
102
|
+
|
|
103
|
+
if initial_state == :connecting
|
|
104
|
+
parser = @parser
|
|
105
|
+
@parser.reset
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
idling
|
|
109
|
+
|
|
110
|
+
@parser = parser
|
|
81
111
|
|
|
82
|
-
parser.reset if @parser
|
|
83
|
-
transition(:idle)
|
|
84
112
|
transition(:connecting)
|
|
85
113
|
end
|
|
86
114
|
end
|
|
@@ -130,6 +130,7 @@ module HTTPX::Plugins
|
|
|
130
130
|
response = request.options.response_class.new(request, status, version, response_headers)
|
|
131
131
|
response.original_request = original_request
|
|
132
132
|
response.finish!
|
|
133
|
+
response.mark_as_cached!
|
|
133
134
|
|
|
134
135
|
IO.copy_stream(f, response.body)
|
|
135
136
|
|
|
@@ -118,7 +118,10 @@ module HTTPX
|
|
|
118
118
|
|
|
119
119
|
response.copy_from_cached!
|
|
120
120
|
elsif request.cacheable_verb? && ResponseCache.cacheable_response?(response)
|
|
121
|
-
|
|
121
|
+
unless response.cached?
|
|
122
|
+
log { "caching response for #{request.uri}..." }
|
|
123
|
+
request.options.response_cache_store.set(request, response)
|
|
124
|
+
end
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
response
|
|
@@ -204,7 +207,7 @@ module HTTPX
|
|
|
204
207
|
# returns a unique cache key as a String identifying this request
|
|
205
208
|
def response_cache_key
|
|
206
209
|
@response_cache_key ||= begin
|
|
207
|
-
keys = [@verb, @uri]
|
|
210
|
+
keys = [@verb, @uri.merge(path)]
|
|
208
211
|
|
|
209
212
|
@options.supported_vary_headers.each do |field|
|
|
210
213
|
value = @headers[field]
|
|
@@ -327,6 +330,14 @@ module HTTPX
|
|
|
327
330
|
Time.now
|
|
328
331
|
end
|
|
329
332
|
end
|
|
333
|
+
|
|
334
|
+
module ResponseBodyMethods
|
|
335
|
+
def decode_chunk(chunk)
|
|
336
|
+
return chunk if @response.cached?
|
|
337
|
+
|
|
338
|
+
super
|
|
339
|
+
end
|
|
340
|
+
end
|
|
330
341
|
end
|
|
331
342
|
register_plugin :response_cache, ResponseCache
|
|
332
343
|
end
|
|
@@ -16,7 +16,7 @@ module HTTPX
|
|
|
16
16
|
# The streams keeps send DATA frames while there's data; when they're ain't,
|
|
17
17
|
# the stream is kept open; it must be explicitly closed by the end user.
|
|
18
18
|
#
|
|
19
|
-
|
|
19
|
+
module HTTP2Methods
|
|
20
20
|
def initialize(*)
|
|
21
21
|
super
|
|
22
22
|
@lock = Thread::Mutex.new
|
|
@@ -117,8 +117,11 @@ module HTTPX
|
|
|
117
117
|
# which allows it to be registered in the selector alongside actual HTTP-based
|
|
118
118
|
# HTTPX::Connection objects.
|
|
119
119
|
class Signal
|
|
120
|
+
attr_reader :error
|
|
121
|
+
|
|
120
122
|
def initialize
|
|
121
123
|
@closed = false
|
|
124
|
+
@error = nil
|
|
122
125
|
@pipe_read, @pipe_write = IO.pipe
|
|
123
126
|
end
|
|
124
127
|
|
|
@@ -159,6 +162,11 @@ module HTTPX
|
|
|
159
162
|
@closed = true
|
|
160
163
|
end
|
|
161
164
|
|
|
165
|
+
def on_error(error)
|
|
166
|
+
@error = error
|
|
167
|
+
terminate
|
|
168
|
+
end
|
|
169
|
+
|
|
162
170
|
# noop (the owner connection will take of it)
|
|
163
171
|
def handle_socket_timeout(interval); end
|
|
164
172
|
end
|
|
@@ -193,6 +201,7 @@ module HTTPX
|
|
|
193
201
|
|
|
194
202
|
def deselect_connection(connection, *)
|
|
195
203
|
super
|
|
204
|
+
|
|
196
205
|
connection.signal = nil
|
|
197
206
|
end
|
|
198
207
|
end
|
|
@@ -294,14 +303,14 @@ module HTTPX
|
|
|
294
303
|
super
|
|
295
304
|
end
|
|
296
305
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def parser_type(protocol)
|
|
300
|
-
return HTTP2Bidi if protocol == "h2"
|
|
306
|
+
def call
|
|
307
|
+
return super unless (error = @signal.error)
|
|
301
308
|
|
|
302
|
-
|
|
309
|
+
on_error(error)
|
|
303
310
|
end
|
|
304
311
|
|
|
312
|
+
private
|
|
313
|
+
|
|
305
314
|
def set_parser_callbacks(parser)
|
|
306
315
|
super
|
|
307
316
|
parser.on(:flush_buffer) do
|
data/lib/httpx/pool.rb
CHANGED
|
@@ -8,7 +8,6 @@ require "httpx/resolver"
|
|
|
8
8
|
|
|
9
9
|
module HTTPX
|
|
10
10
|
class Pool
|
|
11
|
-
using ArrayExtensions::FilterMap
|
|
12
11
|
using URIExtensions
|
|
13
12
|
|
|
14
13
|
POOL_TIMEOUT = 5
|
|
@@ -51,32 +50,53 @@ module HTTPX
|
|
|
51
50
|
acquire_connection(uri, options) || begin
|
|
52
51
|
if @connections_counter == @max_connections
|
|
53
52
|
# this takes precedence over per-origin
|
|
54
|
-
@max_connections_cond.wait(@connection_mtx, @pool_timeout)
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
return conn
|
|
58
|
-
end
|
|
54
|
+
expires_at = Utils.now + @pool_timeout
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
# this means that all of them are persistent or being used, so raise a timeout error.
|
|
63
|
-
conn = @connections.find { |c| c.state == :closed }
|
|
56
|
+
loop do
|
|
57
|
+
@max_connections_cond.wait(@connection_mtx, @pool_timeout)
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
if (conn = acquire_connection(uri, options))
|
|
60
|
+
return conn
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# if one can afford to create a new connection, do it
|
|
64
|
+
break unless @connections_counter == @max_connections
|
|
65
|
+
|
|
66
|
+
# if no matching usable connection was found, the pool will make room and drop a closed connection.
|
|
67
|
+
if (conn = @connections.find { |c| c.state == :closed })
|
|
68
|
+
drop_connection(conn)
|
|
69
|
+
break
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# happens when a condition was signalled, but another thread snatched the available connection before
|
|
73
|
+
# context was passed back here.
|
|
74
|
+
next if Utils.now < expires_at
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
raise PoolTimeoutError.new(@pool_timeout,
|
|
77
|
+
"Timed out after #{@pool_timeout} seconds while waiting for a connection")
|
|
69
78
|
end
|
|
70
79
|
|
|
71
80
|
end
|
|
72
81
|
|
|
73
82
|
if @origin_counters[uri.origin] == @max_connections_per_origin
|
|
74
83
|
|
|
75
|
-
|
|
84
|
+
expires_at = Utils.now + @pool_timeout
|
|
85
|
+
|
|
86
|
+
loop do
|
|
87
|
+
@origin_conds[uri.origin].wait(@connection_mtx, @pool_timeout)
|
|
88
|
+
|
|
89
|
+
if (conn = acquire_connection(uri, options))
|
|
90
|
+
return conn
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# happens when a condition was signalled, but another thread snatched the available connection before
|
|
94
|
+
# context was passed back here.
|
|
95
|
+
next if Utils.now < expires_at
|
|
76
96
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
raise(PoolTimeoutError.new(@pool_timeout,
|
|
98
|
+
"Timed out after #{@pool_timeout} seconds while waiting for a connection to #{uri.origin}"))
|
|
99
|
+
end
|
|
80
100
|
end
|
|
81
101
|
|
|
82
102
|
@connections_counter += 1
|
|
@@ -91,6 +111,13 @@ module HTTPX
|
|
|
91
111
|
return if connection.options.io
|
|
92
112
|
|
|
93
113
|
@connection_mtx.synchronize do
|
|
114
|
+
if connection.coalesced? || connection.state == :idle
|
|
115
|
+
# when connections coalesce
|
|
116
|
+
drop_connection(connection)
|
|
117
|
+
|
|
118
|
+
return
|
|
119
|
+
end
|
|
120
|
+
|
|
94
121
|
@connections << connection
|
|
95
122
|
|
|
96
123
|
@max_connections_cond.signal
|
|
@@ -128,10 +155,16 @@ module HTTPX
|
|
|
128
155
|
end
|
|
129
156
|
|
|
130
157
|
def checkin_resolver(resolver)
|
|
131
|
-
|
|
132
|
-
|
|
158
|
+
resolver_class = resolver.class
|
|
159
|
+
|
|
160
|
+
resolver = resolver.multi
|
|
133
161
|
|
|
134
|
-
|
|
162
|
+
# a multi requires all sub-resolvers being closed in order to be
|
|
163
|
+
# correctly checked back in.
|
|
164
|
+
return unless resolver.closed?
|
|
165
|
+
|
|
166
|
+
@resolver_mtx.synchronize do
|
|
167
|
+
resolvers = @resolvers[resolver_class]
|
|
135
168
|
|
|
136
169
|
resolvers << resolver unless resolvers.include?(resolver)
|
|
137
170
|
end
|
|
@@ -140,6 +173,7 @@ module HTTPX
|
|
|
140
173
|
# :nocov:
|
|
141
174
|
def inspect
|
|
142
175
|
"#<#{self.class}:#{object_id} " \
|
|
176
|
+
"@max_connections=#{@max_connections} " \
|
|
143
177
|
"@max_connections_per_origin=#{@max_connections_per_origin} " \
|
|
144
178
|
"@pool_timeout=#{@pool_timeout} " \
|
|
145
179
|
"@connections=#{@connections.size}>"
|
data/lib/httpx/request.rb
CHANGED
|
@@ -14,9 +14,6 @@ module HTTPX
|
|
|
14
14
|
|
|
15
15
|
ALLOWED_URI_SCHEMES = %w[https http].freeze
|
|
16
16
|
|
|
17
|
-
# default value used for "user-agent" header, when not overridden.
|
|
18
|
-
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
|
19
|
-
|
|
20
17
|
# the upcased string HTTP verb for this request.
|
|
21
18
|
attr_reader :verb
|
|
22
19
|
|
|
@@ -75,16 +72,6 @@ module HTTPX
|
|
|
75
72
|
@headers = options.headers.dup
|
|
76
73
|
merge_headers(params.delete(:headers)) if params.key?(:headers)
|
|
77
74
|
|
|
78
|
-
@headers["user-agent"] ||= USER_AGENT
|
|
79
|
-
@headers["accept"] ||= "*/*"
|
|
80
|
-
|
|
81
|
-
# forego compression in the Range request case
|
|
82
|
-
if @headers.key?("range")
|
|
83
|
-
@headers.delete("accept-encoding")
|
|
84
|
-
else
|
|
85
|
-
@headers["accept-encoding"] ||= options.supported_compression_formats
|
|
86
|
-
end
|
|
87
|
-
|
|
88
75
|
@query_params = params.delete(:params) if params.key?(:params)
|
|
89
76
|
|
|
90
77
|
@body = options.request_body_class.new(@headers, options, **params)
|
|
@@ -166,6 +153,9 @@ module HTTPX
|
|
|
166
153
|
# merges +h+ into the instance of HTTPX::Headers of the request.
|
|
167
154
|
def merge_headers(h)
|
|
168
155
|
@headers = @headers.merge(h)
|
|
156
|
+
return unless @headers.key?("range")
|
|
157
|
+
|
|
158
|
+
@headers.delete("accept-encoding")
|
|
169
159
|
end
|
|
170
160
|
|
|
171
161
|
# the URI scheme of the request +uri+.
|
data/lib/httpx/resolver/https.rb
CHANGED
|
@@ -30,7 +30,8 @@ module HTTPX
|
|
|
30
30
|
use_get: false,
|
|
31
31
|
}.freeze
|
|
32
32
|
|
|
33
|
-
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close,
|
|
33
|
+
def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close,
|
|
34
|
+
:closed?, :deactivate, :terminate, :inflight?, :handle_socket_timeout
|
|
34
35
|
|
|
35
36
|
def initialize(_, options)
|
|
36
37
|
super
|
|
@@ -52,29 +53,24 @@ module HTTPX
|
|
|
52
53
|
if @uri_addresses.empty?
|
|
53
54
|
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
|
54
55
|
ex.set_backtrace(caller)
|
|
55
|
-
connection.
|
|
56
|
+
connection.force_close
|
|
56
57
|
throw(:resolve_error, ex)
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
resolve(connection)
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
# This is already indirectly monitored bt the HTTP connection. In order to skip
|
|
63
|
-
# monitoring, this method returns <tt>true</tt>.
|
|
64
|
-
def closed?
|
|
65
|
-
true
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def empty?
|
|
69
|
-
true
|
|
70
|
-
end
|
|
71
|
-
|
|
72
63
|
def resolver_connection
|
|
73
64
|
# TODO: leaks connection object into the pool
|
|
74
|
-
@resolver_connection ||=
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
@resolver_connection ||=
|
|
66
|
+
@current_session.find_connection(
|
|
67
|
+
@uri,
|
|
68
|
+
@current_selector,
|
|
69
|
+
@options.merge(resolver_class: :system, ssl: { alpn_protocols: %w[h2] })
|
|
70
|
+
).tap do |conn|
|
|
71
|
+
emit_addresses(conn, @family, @uri_addresses) unless conn.addresses
|
|
72
|
+
conn.on(:force_closed, &method(:force_close))
|
|
73
|
+
end
|
|
78
74
|
end
|
|
79
75
|
|
|
80
76
|
private
|
|
@@ -111,7 +107,9 @@ module HTTPX
|
|
|
111
107
|
@connections << connection
|
|
112
108
|
rescue ResolveError, Resolv::DNS::EncodeError => e
|
|
113
109
|
reset_hostname(hostname)
|
|
110
|
+
throw(:resolve_error, e) if connection.pending.empty?
|
|
114
111
|
emit_resolve_error(connection, connection.peer.host, e)
|
|
112
|
+
close_or_resolve
|
|
115
113
|
end
|
|
116
114
|
end
|
|
117
115
|
|
|
@@ -121,6 +119,7 @@ module HTTPX
|
|
|
121
119
|
hostname = @requests.delete(request)
|
|
122
120
|
connection = reset_hostname(hostname)
|
|
123
121
|
emit_resolve_error(connection, connection.peer.host, e)
|
|
122
|
+
close_or_resolve
|
|
124
123
|
else
|
|
125
124
|
# @type var response: HTTPX::Response
|
|
126
125
|
parse(request, response)
|
|
@@ -147,6 +146,7 @@ module HTTPX
|
|
|
147
146
|
|
|
148
147
|
unless @queries.value?(connection)
|
|
149
148
|
emit_resolve_error(connection)
|
|
149
|
+
close_or_resolve
|
|
150
150
|
return
|
|
151
151
|
end
|
|
152
152
|
|
|
@@ -156,10 +156,12 @@ module HTTPX
|
|
|
156
156
|
connection = reset_hostname(host)
|
|
157
157
|
|
|
158
158
|
emit_resolve_error(connection)
|
|
159
|
+
close_or_resolve
|
|
159
160
|
when :decode_error
|
|
160
161
|
host = @requests.delete(request)
|
|
161
162
|
connection = reset_hostname(host)
|
|
162
163
|
emit_resolve_error(connection, connection.peer.host, result)
|
|
164
|
+
close_or_resolve
|
|
163
165
|
end
|
|
164
166
|
end
|
|
165
167
|
|
|
@@ -169,6 +171,7 @@ module HTTPX
|
|
|
169
171
|
host = @requests.delete(request)
|
|
170
172
|
connection = reset_hostname(host)
|
|
171
173
|
emit_resolve_error(connection)
|
|
174
|
+
close_or_resolve
|
|
172
175
|
return
|
|
173
176
|
|
|
174
177
|
else
|
|
@@ -207,9 +210,7 @@ module HTTPX
|
|
|
207
210
|
catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) }) }
|
|
208
211
|
end
|
|
209
212
|
end
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
resolve
|
|
213
|
+
close_or_resolve(true)
|
|
213
214
|
end
|
|
214
215
|
|
|
215
216
|
def build_request(hostname)
|
|
@@ -252,5 +253,20 @@ module HTTPX
|
|
|
252
253
|
|
|
253
254
|
connection
|
|
254
255
|
end
|
|
256
|
+
|
|
257
|
+
def close_or_resolve(should_deactivate = false)
|
|
258
|
+
# drop already closed connections
|
|
259
|
+
@connections.shift until @connections.empty? || @connections.first.state != :closed
|
|
260
|
+
|
|
261
|
+
if (@connections - @queries.values).empty?
|
|
262
|
+
if should_deactivate
|
|
263
|
+
deactivate
|
|
264
|
+
else
|
|
265
|
+
disconnect
|
|
266
|
+
end
|
|
267
|
+
else
|
|
268
|
+
resolve
|
|
269
|
+
end
|
|
270
|
+
end
|
|
255
271
|
end
|
|
256
272
|
end
|
data/lib/httpx/resolver/multi.rb
CHANGED
|
@@ -5,9 +5,6 @@ require "resolv"
|
|
|
5
5
|
|
|
6
6
|
module HTTPX
|
|
7
7
|
class Resolver::Multi
|
|
8
|
-
include Callbacks
|
|
9
|
-
using ArrayExtensions::FilterMap
|
|
10
|
-
|
|
11
8
|
attr_reader :resolvers, :options
|
|
12
9
|
|
|
13
10
|
def initialize(resolver_type, options)
|
|
@@ -28,42 +25,22 @@ module HTTPX
|
|
|
28
25
|
|
|
29
26
|
def current_selector=(s)
|
|
30
27
|
@current_selector = s
|
|
31
|
-
@resolvers.each { |r| r.
|
|
28
|
+
@resolvers.each { |r| r.current_selector = s }
|
|
32
29
|
end
|
|
33
30
|
|
|
34
31
|
def current_session=(s)
|
|
35
32
|
@current_session = s
|
|
36
|
-
@resolvers.each { |r| r.
|
|
33
|
+
@resolvers.each { |r| r.current_session = s }
|
|
37
34
|
end
|
|
38
35
|
|
|
39
36
|
def log(*args, **kwargs, &blk)
|
|
40
|
-
@resolvers.each { |r| r.
|
|
37
|
+
@resolvers.each { |r| r.log(*args, **kwargs, &blk) }
|
|
41
38
|
end
|
|
42
39
|
|
|
43
40
|
def closed?
|
|
44
41
|
@resolvers.all?(&:closed?)
|
|
45
42
|
end
|
|
46
43
|
|
|
47
|
-
def empty?
|
|
48
|
-
@resolvers.all?(&:empty?)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def inflight?
|
|
52
|
-
@resolvers.any(&:inflight?)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def timeout
|
|
56
|
-
@resolvers.filter_map(&:timeout).min
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def close
|
|
60
|
-
@resolvers.each(&:close)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def connections
|
|
64
|
-
@resolvers.filter_map { |r| r.resolver_connection if r.respond_to?(:resolver_connection) }
|
|
65
|
-
end
|
|
66
|
-
|
|
67
44
|
def early_resolve(connection)
|
|
68
45
|
hostname = connection.peer.host
|
|
69
46
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
|
@@ -92,9 +69,13 @@ module HTTPX
|
|
|
92
69
|
|
|
93
70
|
def lazy_resolve(connection)
|
|
94
71
|
@resolvers.each do |resolver|
|
|
95
|
-
|
|
72
|
+
conn_to_resolve = @current_session.try_clone_connection(connection, @current_selector, resolver.family)
|
|
73
|
+
resolver << conn_to_resolve
|
|
74
|
+
|
|
96
75
|
next if resolver.empty?
|
|
97
76
|
|
|
77
|
+
# both the resolver and the connection it's resolving must be pineed to the session
|
|
78
|
+
@current_session.pin(conn_to_resolve, @current_selector)
|
|
98
79
|
@current_session.select_resolver(resolver, @current_selector)
|
|
99
80
|
end
|
|
100
81
|
end
|