httpx 1.7.2 → 1.7.6
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/README.md +3 -1
- data/doc/release_notes/1_7_3.md +29 -0
- data/doc/release_notes/1_7_4.md +42 -0
- data/doc/release_notes/1_7_5.md +10 -0
- data/doc/release_notes/1_7_6.md +24 -0
- data/lib/httpx/adapters/datadog.rb +37 -64
- data/lib/httpx/adapters/webmock.rb +3 -4
- data/lib/httpx/altsvc.rb +4 -2
- data/lib/httpx/connection/http1.rb +26 -18
- data/lib/httpx/connection/http2.rb +53 -33
- data/lib/httpx/connection.rb +152 -63
- data/lib/httpx/io/ssl.rb +20 -8
- data/lib/httpx/io/tcp.rb +18 -12
- data/lib/httpx/io/unix.rb +13 -9
- data/lib/httpx/options.rb +23 -7
- data/lib/httpx/parser/http1.rb +14 -4
- data/lib/httpx/plugins/auth/digest.rb +2 -1
- data/lib/httpx/plugins/auth.rb +23 -9
- data/lib/httpx/plugins/brotli.rb +33 -5
- data/lib/httpx/plugins/cookies/cookie.rb +34 -11
- data/lib/httpx/plugins/cookies/jar.rb +93 -18
- data/lib/httpx/plugins/cookies.rb +7 -3
- data/lib/httpx/plugins/expect.rb +33 -3
- data/lib/httpx/plugins/fiber_concurrency.rb +2 -4
- data/lib/httpx/plugins/follow_redirects.rb +7 -1
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +15 -8
- data/lib/httpx/plugins/proxy.rb +10 -2
- data/lib/httpx/plugins/rate_limiter.rb +19 -19
- data/lib/httpx/plugins/retries.rb +17 -9
- data/lib/httpx/plugins/ssrf_filter.rb +1 -0
- data/lib/httpx/plugins/stream_bidi.rb +6 -0
- data/lib/httpx/plugins/tracing.rb +137 -0
- data/lib/httpx/pool.rb +7 -9
- data/lib/httpx/request.rb +15 -3
- data/lib/httpx/resolver/multi.rb +1 -8
- data/lib/httpx/resolver/native.rb +2 -2
- data/lib/httpx/resolver/resolver.rb +21 -2
- data/lib/httpx/resolver/system.rb +3 -1
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +19 -16
- data/lib/httpx/session.rb +34 -44
- data/lib/httpx/timers.rb +4 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +2 -0
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +3 -1
- data/sig/connection/http2.rbs +11 -4
- data/sig/connection.rbs +16 -2
- data/sig/io/ssl.rbs +1 -0
- data/sig/io/tcp.rbs +2 -2
- data/sig/options.rbs +8 -3
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth.rbs +5 -2
- data/sig/plugins/brotli.rbs +11 -6
- data/sig/plugins/cookies/cookie.rbs +3 -2
- data/sig/plugins/cookies/jar.rbs +11 -0
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/expect.rbs +21 -2
- data/sig/plugins/fiber_concurrency.rbs +2 -2
- data/sig/plugins/proxy/socks4.rbs +4 -0
- data/sig/plugins/rate_limiter.rbs +2 -2
- data/sig/plugins/response_cache.rbs +3 -3
- data/sig/plugins/retries.rbs +17 -13
- data/sig/plugins/tracing.rbs +41 -0
- data/sig/pool.rbs +1 -1
- data/sig/request.rbs +4 -0
- data/sig/resolver/native.rbs +2 -0
- data/sig/resolver/resolver.rbs +4 -2
- data/sig/resolver/system.rbs +0 -2
- data/sig/response/body.rbs +1 -1
- data/sig/selector.rbs +7 -2
- data/sig/session.rbs +2 -0
- data/sig/timers.rbs +2 -0
- data/sig/transcoder/gzip.rbs +1 -1
- data/sig/transcoder.rbs +0 -2
- metadata +13 -3
|
@@ -35,7 +35,10 @@ module HTTPX
|
|
|
35
35
|
request.headers["proxy-authorization"] =
|
|
36
36
|
options.proxy.authenticate(request, response.headers["proxy-authenticate"])
|
|
37
37
|
send_request(request, selector, options)
|
|
38
|
-
|
|
38
|
+
|
|
39
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
40
|
+
# verify retriability again.
|
|
41
|
+
return fetch_response(request, selector, options)
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
response
|
|
@@ -46,7 +49,7 @@ module HTTPX
|
|
|
46
49
|
def force_close(*)
|
|
47
50
|
if @state == :connecting
|
|
48
51
|
# proxy connect related requests should not be reenqueed
|
|
49
|
-
@parser.reset
|
|
52
|
+
@parser.reset
|
|
50
53
|
@inflight -= @parser.pending.size
|
|
51
54
|
@parser.pending.clear
|
|
52
55
|
end
|
|
@@ -67,18 +70,16 @@ module HTTPX
|
|
|
67
70
|
return unless @io.connected?
|
|
68
71
|
|
|
69
72
|
@parser || begin
|
|
70
|
-
@parser = parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
|
|
71
|
-
parser = @parser
|
|
73
|
+
@parser = parser = parser_type(@io.protocol).new(@write_buffer, @options.merge(max_concurrent_requests: 1))
|
|
72
74
|
parser.extend(ProxyParser)
|
|
73
75
|
parser.on(:response, &method(:__http_on_connect))
|
|
74
76
|
parser.on(:close) do
|
|
75
77
|
next unless @parser
|
|
76
78
|
|
|
77
79
|
reset
|
|
78
|
-
disconnect
|
|
79
80
|
end
|
|
80
81
|
parser.on(:reset) do
|
|
81
|
-
if parser.empty?
|
|
82
|
+
if parser.pending.empty? && parser.empty?
|
|
82
83
|
reset
|
|
83
84
|
else
|
|
84
85
|
enqueue_pending_requests_from_parser(parser)
|
|
@@ -94,17 +95,23 @@ module HTTPX
|
|
|
94
95
|
# keep parser state around due to proxy auth protocol;
|
|
95
96
|
# intermediate authenticated request is already inside
|
|
96
97
|
# the parser
|
|
97
|
-
parser = nil
|
|
98
|
+
connect_request = parser = nil
|
|
98
99
|
|
|
99
100
|
if initial_state == :connecting
|
|
100
101
|
parser = @parser
|
|
101
102
|
@parser.reset
|
|
103
|
+
if @pending.first.is_a?(ConnectRequest)
|
|
104
|
+
connect_request = @pending.shift # this happened when reenqueing
|
|
105
|
+
end
|
|
102
106
|
end
|
|
103
107
|
|
|
104
108
|
idling
|
|
105
109
|
|
|
106
110
|
@parser = parser
|
|
107
|
-
|
|
111
|
+
if connect_request
|
|
112
|
+
@inflight += 1
|
|
113
|
+
parser.send(connect_request)
|
|
114
|
+
end
|
|
108
115
|
transition(:connecting)
|
|
109
116
|
end
|
|
110
117
|
end
|
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -202,7 +202,10 @@ module HTTPX
|
|
|
202
202
|
log { "failed connecting to proxy, trying next..." }
|
|
203
203
|
request.transition(:idle)
|
|
204
204
|
send_request(request, selector, options)
|
|
205
|
-
|
|
205
|
+
|
|
206
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
207
|
+
# verify retriability again.
|
|
208
|
+
return fetch_response(request, selector, options)
|
|
206
209
|
end
|
|
207
210
|
response
|
|
208
211
|
rescue ProxyError
|
|
@@ -320,7 +323,12 @@ module HTTPX
|
|
|
320
323
|
|
|
321
324
|
def purge_after_closed
|
|
322
325
|
super
|
|
323
|
-
|
|
326
|
+
|
|
327
|
+
while @io.respond_to?(:proxy_io)
|
|
328
|
+
@io = @io.proxy_io
|
|
329
|
+
|
|
330
|
+
super
|
|
331
|
+
end
|
|
324
332
|
end
|
|
325
333
|
end
|
|
326
334
|
|
|
@@ -16,25 +16,7 @@ module HTTPX
|
|
|
16
16
|
|
|
17
17
|
class << self
|
|
18
18
|
def load_dependencies(klass)
|
|
19
|
-
klass.plugin(:retries
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Servers send the "Retry-After" header field to indicate how long the
|
|
23
|
-
# user agent ought to wait before making a follow-up request. When
|
|
24
|
-
# sent with a 503 (Service Unavailable) response, Retry-After indicates
|
|
25
|
-
# how long the service is expected to be unavailable to the client.
|
|
26
|
-
# When sent with any 3xx (Redirection) response, Retry-After indicates
|
|
27
|
-
# the minimum time that the user agent is asked to wait before issuing
|
|
28
|
-
# the redirected request.
|
|
29
|
-
#
|
|
30
|
-
def retry_after_rate_limit(_, response)
|
|
31
|
-
return unless response.is_a?(Response)
|
|
32
|
-
|
|
33
|
-
retry_after = response.headers["retry-after"]
|
|
34
|
-
|
|
35
|
-
return unless retry_after
|
|
36
|
-
|
|
37
|
-
Utils.parse_retry_after(retry_after)
|
|
19
|
+
klass.plugin(:retries)
|
|
38
20
|
end
|
|
39
21
|
end
|
|
40
22
|
|
|
@@ -52,6 +34,24 @@ module HTTPX
|
|
|
52
34
|
def rate_limit_error?(response)
|
|
53
35
|
response.is_a?(Response) && RATE_LIMIT_CODES.include?(response.status)
|
|
54
36
|
end
|
|
37
|
+
|
|
38
|
+
# Servers send the "Retry-After" header field to indicate how long the
|
|
39
|
+
# user agent ought to wait before making a follow-up request. When
|
|
40
|
+
# sent with a 503 (Service Unavailable) response, Retry-After indicates
|
|
41
|
+
# how long the service is expected to be unavailable to the client.
|
|
42
|
+
# When sent with any 3xx (Redirection) response, Retry-After indicates
|
|
43
|
+
# the minimum time that the user agent is asked to wait before issuing
|
|
44
|
+
# the redirected request.
|
|
45
|
+
#
|
|
46
|
+
def when_to_retry(_, response, options)
|
|
47
|
+
return super unless response.is_a?(Response)
|
|
48
|
+
|
|
49
|
+
retry_after = response.headers["retry-after"]
|
|
50
|
+
|
|
51
|
+
return super unless retry_after
|
|
52
|
+
|
|
53
|
+
Utils.parse_retry_after(retry_after)
|
|
54
|
+
end
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
|
|
@@ -148,10 +148,7 @@ module HTTPX
|
|
|
148
148
|
log { "failed to get response, #{request.retries} tries to go..." }
|
|
149
149
|
prepare_to_retry(request, response)
|
|
150
150
|
|
|
151
|
-
retry_after = options.
|
|
152
|
-
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
|
153
|
-
|
|
154
|
-
if retry_after
|
|
151
|
+
if (retry_after = when_to_retry(request, response, options)) && retry_after.positive?
|
|
155
152
|
# apply jitter
|
|
156
153
|
if (jitter = request.options.retry_jitter)
|
|
157
154
|
retry_after = jitter.call(retry_after)
|
|
@@ -169,11 +166,15 @@ module HTTPX
|
|
|
169
166
|
send_request(request, selector, options)
|
|
170
167
|
end
|
|
171
168
|
end
|
|
169
|
+
|
|
170
|
+
return
|
|
172
171
|
else
|
|
173
172
|
send_request(request, selector, options)
|
|
174
|
-
end
|
|
175
173
|
|
|
176
|
-
|
|
174
|
+
# recalling itself, in case an error was triggered by the above, and we can
|
|
175
|
+
# verify retriability again.
|
|
176
|
+
return fetch_response(request, selector, options)
|
|
177
|
+
end
|
|
177
178
|
end
|
|
178
179
|
response
|
|
179
180
|
end
|
|
@@ -201,6 +202,12 @@ module HTTPX
|
|
|
201
202
|
request.transition(:idle)
|
|
202
203
|
end
|
|
203
204
|
|
|
205
|
+
def when_to_retry(request, response, options)
|
|
206
|
+
retry_after = options.retry_after
|
|
207
|
+
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
|
208
|
+
retry_after
|
|
209
|
+
end
|
|
210
|
+
|
|
204
211
|
#
|
|
205
212
|
# Attempt to set the request to perform a partial range request.
|
|
206
213
|
# This happens if the peer server accepts byte-range requests, and
|
|
@@ -237,14 +244,15 @@ module HTTPX
|
|
|
237
244
|
def initialize(*args)
|
|
238
245
|
super
|
|
239
246
|
@retries = @options.max_retries
|
|
247
|
+
@partial_response = nil
|
|
240
248
|
end
|
|
241
249
|
|
|
242
250
|
def response=(response)
|
|
243
|
-
if @partial_response
|
|
251
|
+
if (partial_response = @partial_response)
|
|
244
252
|
if response.is_a?(Response) && response.status == 206
|
|
245
|
-
response.from_partial_response(
|
|
253
|
+
response.from_partial_response(partial_response)
|
|
246
254
|
else
|
|
247
|
-
|
|
255
|
+
partial_response.close
|
|
248
256
|
end
|
|
249
257
|
@partial_response = nil
|
|
250
258
|
end
|
|
@@ -106,6 +106,7 @@ module HTTPX
|
|
|
106
106
|
error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
|
|
107
107
|
error.set_backtrace(caller)
|
|
108
108
|
response = ErrorResponse.new(request, error)
|
|
109
|
+
request.response = response
|
|
109
110
|
request.emit(:response, response)
|
|
110
111
|
response
|
|
111
112
|
end
|
|
@@ -189,6 +189,10 @@ module HTTPX
|
|
|
189
189
|
!@closed
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
def force_close(*)
|
|
193
|
+
terminate
|
|
194
|
+
end
|
|
195
|
+
|
|
192
196
|
def terminate
|
|
193
197
|
return if @closed
|
|
194
198
|
|
|
@@ -202,6 +206,8 @@ module HTTPX
|
|
|
202
206
|
terminate
|
|
203
207
|
end
|
|
204
208
|
|
|
209
|
+
alias_method :on_io_error, :on_error
|
|
210
|
+
|
|
205
211
|
# noop (the owner connection will take of it)
|
|
206
212
|
def handle_socket_timeout(interval); end
|
|
207
213
|
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX::Plugins
|
|
4
|
+
#
|
|
5
|
+
# This plugin adds a simple interface to integrate request tracing SDKs.
|
|
6
|
+
#
|
|
7
|
+
# An example of such an integration is the datadog adapter.
|
|
8
|
+
#
|
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Tracing
|
|
10
|
+
#
|
|
11
|
+
module Tracing
|
|
12
|
+
class Wrapper
|
|
13
|
+
attr_reader :tracers
|
|
14
|
+
protected :tracers
|
|
15
|
+
|
|
16
|
+
def initialize(*tracers)
|
|
17
|
+
@tracers = tracers.flat_map do |tracer|
|
|
18
|
+
case tracer
|
|
19
|
+
when Wrapper
|
|
20
|
+
tracer.tracers
|
|
21
|
+
else
|
|
22
|
+
tracer
|
|
23
|
+
end
|
|
24
|
+
end.uniq
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def merge(tracer)
|
|
28
|
+
case tracer
|
|
29
|
+
when Wrapper
|
|
30
|
+
Wrapper.new(*@tracers, *tracer.tracers)
|
|
31
|
+
else
|
|
32
|
+
Wrapper.new(*@tracers, tracer)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def freeze
|
|
37
|
+
@tracers.each(&:freeze).freeze
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
%i[start finish reset enabled?].each do |callback|
|
|
42
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
43
|
+
# proxies ##{callback} calls to wrapper tracers.
|
|
44
|
+
def #{callback}(*args) # def start(*args)
|
|
45
|
+
@tracers.each { |t| t.#{callback}(*args) } # @tracers.each { |t| t.start(*args) }
|
|
46
|
+
end # end
|
|
47
|
+
OUT
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# adds support for the following options:
|
|
52
|
+
#
|
|
53
|
+
# :tracer :: object which responds to #start, #finish and #reset.
|
|
54
|
+
module OptionsMethods
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def option_tracer(tracer)
|
|
58
|
+
unless tracer.respond_to?(:start) &&
|
|
59
|
+
tracer.respond_to?(:finish) &&
|
|
60
|
+
tracer.respond_to?(:reset) &&
|
|
61
|
+
tracer.respond_to?(:enabled?)
|
|
62
|
+
raise TypeError, "#{tracer} must to respond to `#start(r)`, `#finish` and `#reset` and `#enabled?"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
tracer = Wrapper.new(@tracer, tracer) if @tracer
|
|
66
|
+
tracer
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module RequestMethods
|
|
71
|
+
attr_accessor :init_time
|
|
72
|
+
|
|
73
|
+
# intercepts request initialization to inject the tracing logic.
|
|
74
|
+
def initialize(*)
|
|
75
|
+
super
|
|
76
|
+
|
|
77
|
+
@init_time = nil
|
|
78
|
+
|
|
79
|
+
tracer = @options.tracer
|
|
80
|
+
|
|
81
|
+
return unless tracer && tracer.enabled?(self)
|
|
82
|
+
|
|
83
|
+
on(:idle) do
|
|
84
|
+
tracer.reset(self)
|
|
85
|
+
|
|
86
|
+
# request is reset when it's retried.
|
|
87
|
+
@init_time = nil
|
|
88
|
+
end
|
|
89
|
+
on(:headers) do
|
|
90
|
+
# the usual request init time (when not including the connection handshake)
|
|
91
|
+
# should be the time the request is buffered the first time.
|
|
92
|
+
@init_time ||= ::Time.now.utc
|
|
93
|
+
|
|
94
|
+
tracer.start(self)
|
|
95
|
+
end
|
|
96
|
+
on(:response) { |response| tracer.finish(self, response) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def response=(*)
|
|
100
|
+
# init_time should be set when it's send to a connection.
|
|
101
|
+
# However, there are situations where connection initialization fails.
|
|
102
|
+
# Example is the :ssrf_filter plugin, which raises an error on
|
|
103
|
+
# initialize if the host is an IP which matches against the known set.
|
|
104
|
+
# in such cases, we'll just set here right here.
|
|
105
|
+
@init_time ||= ::Time.now.utc
|
|
106
|
+
|
|
107
|
+
super
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Connection mixin
|
|
112
|
+
module ConnectionMethods
|
|
113
|
+
def initialize(*)
|
|
114
|
+
super
|
|
115
|
+
|
|
116
|
+
@init_time = ::Time.now.utc
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def send(request)
|
|
120
|
+
# request init time is only the same as the connection init time
|
|
121
|
+
# if the connection is going through the connection handshake.
|
|
122
|
+
request.init_time ||= @init_time unless open?
|
|
123
|
+
|
|
124
|
+
super
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def idling
|
|
128
|
+
super
|
|
129
|
+
|
|
130
|
+
# time of initial request(s) is accounted from the moment
|
|
131
|
+
# the connection is back to :idle, and ready to connect again.
|
|
132
|
+
@init_time = ::Time.now.utc
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
register_plugin :tracing, Tracing
|
|
137
|
+
end
|
data/lib/httpx/pool.rb
CHANGED
|
@@ -122,12 +122,6 @@ 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
|
|
131
125
|
end
|
|
132
126
|
end
|
|
133
127
|
|
|
@@ -153,16 +147,20 @@ module HTTPX
|
|
|
153
147
|
resolvers = @resolvers[resolver_type]
|
|
154
148
|
|
|
155
149
|
idx = resolvers.find_index do |res|
|
|
156
|
-
res.options
|
|
150
|
+
res.options.resolver_options_match?(options)
|
|
157
151
|
end
|
|
158
152
|
resolvers.delete_at(idx) if idx
|
|
159
153
|
end || checkout_new_resolver(resolver_type, options)
|
|
160
154
|
end
|
|
161
155
|
|
|
162
156
|
def checkin_resolver(resolver)
|
|
163
|
-
|
|
157
|
+
if resolver.is_a?(Resolver::Multi)
|
|
158
|
+
resolver_class = resolver.resolvers.first.class
|
|
159
|
+
else
|
|
160
|
+
resolver_class = resolver.class
|
|
164
161
|
|
|
165
|
-
|
|
162
|
+
resolver = resolver.multi
|
|
163
|
+
end
|
|
166
164
|
|
|
167
165
|
# a multi requires all sub-resolvers being closed in order to be
|
|
168
166
|
# correctly checked back in.
|
data/lib/httpx/request.rb
CHANGED
|
@@ -42,6 +42,9 @@ module HTTPX
|
|
|
42
42
|
# The IP address from the peer server.
|
|
43
43
|
attr_accessor :peer_address
|
|
44
44
|
|
|
45
|
+
# the connection the request is currently being sent to (none if before or after transaction)
|
|
46
|
+
attr_writer :connection
|
|
47
|
+
|
|
45
48
|
attr_writer :persistent
|
|
46
49
|
|
|
47
50
|
attr_reader :active_timeouts
|
|
@@ -91,7 +94,7 @@ module HTTPX
|
|
|
91
94
|
raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
|
|
92
95
|
|
|
93
96
|
@state = :idle
|
|
94
|
-
@response = @peer_address = @informational_status = nil
|
|
97
|
+
@connection = @response = @drainer = @peer_address = @informational_status = nil
|
|
95
98
|
@ping = false
|
|
96
99
|
@persistent = @options.persistent
|
|
97
100
|
@active_timeouts = []
|
|
@@ -274,8 +277,7 @@ module HTTPX
|
|
|
274
277
|
when :idle
|
|
275
278
|
@body.rewind
|
|
276
279
|
@ping = false
|
|
277
|
-
@response = nil
|
|
278
|
-
@drainer = nil
|
|
280
|
+
@response = @drainer = nil
|
|
279
281
|
@active_timeouts.clear
|
|
280
282
|
when :headers
|
|
281
283
|
return unless @state == :idle
|
|
@@ -321,6 +323,16 @@ module HTTPX
|
|
|
321
323
|
callbacks(event).delete(clb)
|
|
322
324
|
end
|
|
323
325
|
end
|
|
326
|
+
|
|
327
|
+
def handle_error(error)
|
|
328
|
+
if (connection = @connection)
|
|
329
|
+
connection.on_error(error, self)
|
|
330
|
+
else
|
|
331
|
+
response = ErrorResponse.new(self, error)
|
|
332
|
+
self.response = response
|
|
333
|
+
emit(:response, response)
|
|
334
|
+
end
|
|
335
|
+
end
|
|
324
336
|
end
|
|
325
337
|
end
|
|
326
338
|
|
data/lib/httpx/resolver/multi.rb
CHANGED
|
@@ -71,14 +71,7 @@ module HTTPX
|
|
|
71
71
|
|
|
72
72
|
def lazy_resolve(connection)
|
|
73
73
|
@resolvers.each do |resolver|
|
|
74
|
-
|
|
75
|
-
resolver << conn_to_resolve
|
|
76
|
-
|
|
77
|
-
next if resolver.empty?
|
|
78
|
-
|
|
79
|
-
# both the resolver and the connection it's resolving must be pineed to the session
|
|
80
|
-
@current_session.pin(conn_to_resolve, @current_selector)
|
|
81
|
-
@current_session.select_resolver(resolver, @current_selector)
|
|
74
|
+
resolver.lazy_resolve(connection)
|
|
82
75
|
end
|
|
83
76
|
end
|
|
84
77
|
|
|
@@ -66,7 +66,7 @@ module HTTPX
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def closed?
|
|
69
|
-
@state == :closed
|
|
69
|
+
@state == :idle || @state == :closed
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def to_io
|
|
@@ -529,7 +529,7 @@ module HTTPX
|
|
|
529
529
|
|
|
530
530
|
resolve if @queries.empty? && !@connections.empty?
|
|
531
531
|
when :closed
|
|
532
|
-
return
|
|
532
|
+
return if @state == :closed
|
|
533
533
|
|
|
534
534
|
@io.close if @io
|
|
535
535
|
@start_timeout = nil
|
|
@@ -119,6 +119,11 @@ module HTTPX
|
|
|
119
119
|
end
|
|
120
120
|
end
|
|
121
121
|
|
|
122
|
+
def on_io_error(e)
|
|
123
|
+
on_error(e)
|
|
124
|
+
force_close(true)
|
|
125
|
+
end
|
|
126
|
+
|
|
122
127
|
def on_error(error)
|
|
123
128
|
handle_error(error)
|
|
124
129
|
disconnect
|
|
@@ -138,6 +143,19 @@ module HTTPX
|
|
|
138
143
|
true
|
|
139
144
|
end
|
|
140
145
|
|
|
146
|
+
def lazy_resolve(connection)
|
|
147
|
+
return unless @current_session && @current_selector
|
|
148
|
+
|
|
149
|
+
conn_to_resolve = @current_session.try_clone_connection(connection, @current_selector, @family)
|
|
150
|
+
self << conn_to_resolve
|
|
151
|
+
|
|
152
|
+
return if empty?
|
|
153
|
+
|
|
154
|
+
# both the resolver and the connection it's resolving must be pinned to the session
|
|
155
|
+
@current_session.pin(conn_to_resolve, @current_selector)
|
|
156
|
+
@current_session.select_resolver(self, @current_selector)
|
|
157
|
+
end
|
|
158
|
+
|
|
141
159
|
private
|
|
142
160
|
|
|
143
161
|
def emit_resolved_connection(connection, addresses, early_resolve)
|
|
@@ -181,9 +199,10 @@ module HTTPX
|
|
|
181
199
|
end
|
|
182
200
|
|
|
183
201
|
def disconnect
|
|
184
|
-
return if closed?
|
|
185
|
-
|
|
186
202
|
close
|
|
203
|
+
|
|
204
|
+
return unless closed?
|
|
205
|
+
|
|
187
206
|
@current_session.deselect_resolver(self, @current_selector)
|
|
188
207
|
end
|
|
189
208
|
end
|
|
@@ -116,7 +116,9 @@ module HTTPX
|
|
|
116
116
|
@current_session.select_resolver(self, @current_selector)
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
def early_resolve(
|
|
119
|
+
def early_resolve(_, **) # rubocop:disable Naming/PredicateMethod
|
|
120
|
+
false
|
|
121
|
+
end
|
|
120
122
|
|
|
121
123
|
def handle_socket_timeout(interval)
|
|
122
124
|
error = HTTPX::ResolveTimeoutError.new(interval, "timed out while waiting on select")
|
data/lib/httpx/response.rb
CHANGED
|
@@ -112,6 +112,7 @@ module HTTPX
|
|
|
112
112
|
def finish!
|
|
113
113
|
@finished = true
|
|
114
114
|
@headers.freeze
|
|
115
|
+
@request.connection = nil
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
# returns whether the response contains body payload.
|
|
@@ -282,6 +283,7 @@ module HTTPX
|
|
|
282
283
|
@error = error
|
|
283
284
|
@options = request.options
|
|
284
285
|
log_exception(@error)
|
|
286
|
+
finish!
|
|
285
287
|
end
|
|
286
288
|
|
|
287
289
|
# returns the exception full message.
|
|
@@ -299,7 +301,9 @@ module HTTPX
|
|
|
299
301
|
true
|
|
300
302
|
end
|
|
301
303
|
|
|
302
|
-
def finish
|
|
304
|
+
def finish!
|
|
305
|
+
@request.connection = nil
|
|
306
|
+
end
|
|
303
307
|
|
|
304
308
|
# raises the wrapped exception.
|
|
305
309
|
def raise_for_status
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -29,7 +29,7 @@ module HTTPX
|
|
|
29
29
|
|
|
30
30
|
def_delegator :@timers, :after
|
|
31
31
|
|
|
32
|
-
def_delegator :@selectables, :
|
|
32
|
+
def_delegator :@selectables, :each
|
|
33
33
|
|
|
34
34
|
def initialize
|
|
35
35
|
@timers = Timers.new
|
|
@@ -37,8 +37,8 @@ module HTTPX
|
|
|
37
37
|
@is_timer_interval = false
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
def
|
|
41
|
-
@selectables.
|
|
40
|
+
def empty?
|
|
41
|
+
@selectables.empty? && @timers.empty?
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def next_tick
|
|
@@ -81,7 +81,8 @@ module HTTPX
|
|
|
81
81
|
|
|
82
82
|
def find_resolver(options)
|
|
83
83
|
res = @selectables.find do |c|
|
|
84
|
-
c.is_a?(Resolver::Resolver) &&
|
|
84
|
+
c.is_a?(Resolver::Resolver) &&
|
|
85
|
+
options.resolver_options_match?(c.options)
|
|
85
86
|
end
|
|
86
87
|
|
|
87
88
|
res.multi if res
|
|
@@ -148,16 +149,18 @@ module HTTPX
|
|
|
148
149
|
|
|
149
150
|
next(is_closed) if is_closed
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
if interests
|
|
153
|
+
io.log(level: 2) do
|
|
154
|
+
"[#{io.state}] registering in selector##{object_id} for select (#{interests})#{" for #{interval} seconds" unless interval.nil?}"
|
|
155
|
+
end
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
if READABLE.include?(interests)
|
|
158
|
+
r = r.nil? ? io : (Array(r) << io)
|
|
159
|
+
end
|
|
158
160
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
if WRITABLE.include?(interests)
|
|
162
|
+
w = w.nil? ? io : (Array(w) << io)
|
|
163
|
+
end
|
|
161
164
|
end
|
|
162
165
|
|
|
163
166
|
is_closed
|
|
@@ -204,8 +207,7 @@ module HTTPX
|
|
|
204
207
|
rescue IOError => e
|
|
205
208
|
(Array(r) + Array(w)).each do |sel|
|
|
206
209
|
# TODO: is there a way to cheaply find the IO associated with the error?
|
|
207
|
-
sel.
|
|
208
|
-
sel.force_close(true)
|
|
210
|
+
sel.on_io_error(e)
|
|
209
211
|
end
|
|
210
212
|
rescue StandardError => e
|
|
211
213
|
(Array(r) + Array(w)).each do |sel|
|
|
@@ -249,8 +251,9 @@ module HTTPX
|
|
|
249
251
|
when :rw then rw_wait(io, interval)
|
|
250
252
|
end
|
|
251
253
|
rescue IOError => e
|
|
252
|
-
io.
|
|
253
|
-
|
|
254
|
+
io.on_io_error(e)
|
|
255
|
+
|
|
256
|
+
return
|
|
254
257
|
rescue StandardError => e
|
|
255
258
|
io.on_error(e)
|
|
256
259
|
|