httpx 1.8.0 → 1.8.2
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_7_1.md +1 -2
- data/doc/release_notes/1_8_1.md +27 -0
- data/doc/release_notes/1_8_2.md +30 -0
- data/lib/httpx/adapters/datadog.rb +9 -0
- data/lib/httpx/adapters/faraday.rb +2 -2
- data/lib/httpx/adapters/webmock.rb +8 -1
- data/lib/httpx/buffer.rb +5 -0
- data/lib/httpx/connection/http1.rb +6 -12
- data/lib/httpx/connection/http2.rb +38 -10
- data/lib/httpx/connection.rb +86 -27
- data/lib/httpx/errors.rb +3 -0
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +26 -17
- data/lib/httpx/io/tcp.rb +11 -4
- data/lib/httpx/io/unix.rb +2 -2
- data/lib/httpx/loggable.rb +11 -4
- data/lib/httpx/options.rb +6 -2
- data/lib/httpx/plugins/auth/digest.rb +1 -1
- data/lib/httpx/plugins/auth.rb +17 -7
- data/lib/httpx/plugins/callbacks.rb +12 -0
- data/lib/httpx/plugins/digest_auth.rb +4 -0
- data/lib/httpx/plugins/fiber_concurrency.rb +25 -1
- data/lib/httpx/plugins/follow_redirects.rb +21 -9
- data/lib/httpx/plugins/oauth.rb +4 -2
- data/lib/httpx/plugins/persistent.rb +33 -2
- data/lib/httpx/plugins/proxy.rb +9 -3
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/retries.rb +15 -8
- data/lib/httpx/plugins/server_sent_events.rb +1 -1
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +2 -2
- data/lib/httpx/plugins/stream_bidi.rb +2 -0
- data/lib/httpx/plugins/tracing.rb +17 -4
- data/lib/httpx/pool.rb +2 -2
- data/lib/httpx/request/body.rb +10 -7
- data/lib/httpx/request.rb +27 -12
- data/lib/httpx/resolver/cache/base.rb +1 -8
- data/lib/httpx/resolver/https.rb +20 -20
- data/lib/httpx/resolver/native.rb +11 -0
- data/lib/httpx/resolver/resolver.rb +4 -0
- data/lib/httpx/resolver/system.rb +5 -2
- data/lib/httpx/response/body.rb +2 -2
- data/lib/httpx/response.rb +6 -7
- data/lib/httpx/selector.rb +6 -1
- data/lib/httpx/session.rb +11 -3
- data/lib/httpx/session_extensions.rb +2 -2
- data/lib/httpx/timers.rb +3 -0
- data/lib/httpx/transcoder/json.rb +1 -2
- data/lib/httpx/transcoder/utils/deflater.rb +1 -2
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http2.rbs +3 -0
- data/sig/connection.rbs +8 -3
- data/sig/errors.rbs +2 -2
- data/sig/io/ssl.rbs +5 -0
- data/sig/loggable.rbs +4 -0
- data/sig/plugins/auth.rbs +7 -3
- data/sig/plugins/persistent.rbs +7 -0
- data/sig/plugins/retries.rbs +4 -2
- data/sig/plugins/tracing.rbs +1 -1
- data/sig/request.rbs +2 -3
- data/sig/resolver/system.rbs +3 -0
- data/sig/selector.rbs +2 -0
- data/sig/timers.rbs +2 -0
- metadata +6 -2
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -6,22 +6,22 @@ module HTTPX
|
|
|
6
6
|
TLSError = OpenSSL::SSL::SSLError
|
|
7
7
|
|
|
8
8
|
class SSL < TCP
|
|
9
|
-
|
|
10
|
-
TLS_OPTIONS = { alpn_protocols: %w[h2 http/1.1].freeze }
|
|
11
|
-
# https://github.com/jruby/jruby-openssl/issues/284
|
|
9
|
+
tls_options = { alpn_protocols: %w[h2 http/1.1].freeze }
|
|
12
10
|
# TODO: remove when dropping support for jruby-openssl < 0.15.4
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
TLS_OPTIONS.freeze
|
|
11
|
+
# https://github.com/jruby/jruby-openssl/issues/284
|
|
12
|
+
tls_options[:verify_hostname] = true if RUBY_ENGINE == "jruby" && JOpenSSL::VERSION < "0.15.4"
|
|
13
|
+
TLS_OPTIONS = tls_options.freeze
|
|
16
14
|
|
|
17
15
|
attr_writer :ssl_session
|
|
18
16
|
|
|
19
17
|
def initialize(_, _, options)
|
|
20
18
|
super
|
|
21
19
|
|
|
22
|
-
@ssl_session = nil
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
@ssl_session = @session_new_cb = nil
|
|
21
|
+
|
|
22
|
+
ctx_options = TLS_OPTIONS
|
|
23
|
+
ctx_options = ctx_options.merge(options.ssl) if options.ssl && !options.ssl.empty?
|
|
24
|
+
@sni_hostname = (ctx_options.delete(:hostname) if ctx_options.key?(:hostname)) || @hostname
|
|
25
25
|
|
|
26
26
|
if @keep_open && @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
27
27
|
# externally initiated ssl socket
|
|
@@ -29,11 +29,12 @@ module HTTPX
|
|
|
29
29
|
@state = :negotiated
|
|
30
30
|
else
|
|
31
31
|
@ctx = OpenSSL::SSL::SSLContext.new
|
|
32
|
-
@ctx.set_params(ctx_options)
|
|
32
|
+
@ctx.set_params(ctx_options)
|
|
33
33
|
unless @ctx.session_cache_mode.nil? # a dummy method on JRuby
|
|
34
34
|
@ctx.session_cache_mode =
|
|
35
35
|
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
|
36
36
|
end
|
|
37
|
+
init_session_new_cb
|
|
37
38
|
|
|
38
39
|
yield(self) if block_given?
|
|
39
40
|
end
|
|
@@ -42,33 +43,41 @@ module HTTPX
|
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
|
|
46
|
+
# sets the ssl session callback to be picked up by the ssl context.
|
|
45
47
|
def session_new_cb(&pr)
|
|
46
|
-
@
|
|
48
|
+
@session_new_cb = pr
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# sets the ssl context's new session callback, which points at @session_new_cb when available.
|
|
52
|
+
def init_session_new_cb
|
|
53
|
+
@ctx.session_new_cb = proc { |_, sess| @session_new_cb&.call(sess) }
|
|
47
54
|
end
|
|
48
55
|
else
|
|
49
56
|
# session_new_cb not implemented under JRuby
|
|
50
57
|
def session_new_cb; end
|
|
58
|
+
|
|
59
|
+
def init_session_new_cb; end
|
|
51
60
|
end
|
|
52
61
|
|
|
62
|
+
private :init_session_new_cb
|
|
63
|
+
|
|
53
64
|
def protocol
|
|
54
|
-
|
|
65
|
+
return super unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
66
|
+
|
|
55
67
|
@io.alpn_protocol || super
|
|
56
|
-
rescue StandardError
|
|
57
|
-
super
|
|
58
68
|
end
|
|
59
69
|
|
|
60
70
|
if RUBY_ENGINE == "jruby"
|
|
61
71
|
# in jruby, alpn_protocol may return ""
|
|
62
72
|
# https://github.com/jruby/jruby-openssl/issues/287
|
|
63
73
|
def protocol
|
|
64
|
-
|
|
74
|
+
return super unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
75
|
+
|
|
65
76
|
proto = @io.alpn_protocol
|
|
66
77
|
|
|
67
78
|
return super if proto.nil? || proto.empty?
|
|
68
79
|
|
|
69
80
|
proto
|
|
70
|
-
rescue StandardError
|
|
71
|
-
super
|
|
72
81
|
end
|
|
73
82
|
end
|
|
74
83
|
|
data/lib/httpx/io/tcp.rb
CHANGED
|
@@ -182,11 +182,18 @@ module HTTPX
|
|
|
182
182
|
end
|
|
183
183
|
|
|
184
184
|
def close
|
|
185
|
-
return if @keep_open
|
|
185
|
+
return if @keep_open
|
|
186
|
+
|
|
187
|
+
# mark tcp as closed, so that it can be disconnected.
|
|
188
|
+
# this bypasses the state machine API as not not allow the transition
|
|
189
|
+
# from idle to closed in normal circumstances.
|
|
190
|
+
@state = :closed if @state == :idle
|
|
191
|
+
|
|
192
|
+
return if closed?
|
|
186
193
|
|
|
187
194
|
begin
|
|
188
195
|
@io.close
|
|
189
|
-
rescue
|
|
196
|
+
rescue IOError => e
|
|
190
197
|
log { "error closing socket" }
|
|
191
198
|
log { e.full_message(highlight: false) }
|
|
192
199
|
ensure
|
|
@@ -212,7 +219,7 @@ module HTTPX
|
|
|
212
219
|
@state == :idle || @state == :closed
|
|
213
220
|
end
|
|
214
221
|
|
|
215
|
-
# :
|
|
222
|
+
# simplecov:disable
|
|
216
223
|
def inspect
|
|
217
224
|
"#<#{self.class}:#{object_id} " \
|
|
218
225
|
"#{@ip}:#{@port} " \
|
|
@@ -221,7 +228,7 @@ module HTTPX
|
|
|
221
228
|
"@addresses=#{@addresses} " \
|
|
222
229
|
"@state=#{@state}>"
|
|
223
230
|
end
|
|
224
|
-
# :
|
|
231
|
+
# simplecov:enable
|
|
225
232
|
|
|
226
233
|
private
|
|
227
234
|
|
data/lib/httpx/io/unix.rb
CHANGED
data/lib/httpx/loggable.rb
CHANGED
|
@@ -17,6 +17,12 @@ module HTTPX
|
|
|
17
17
|
|
|
18
18
|
USE_DEBUG_LOG = ENV.key?("HTTPX_DEBUG")
|
|
19
19
|
|
|
20
|
+
def self.log_identifiers
|
|
21
|
+
"pid=#{Process.pid} " \
|
|
22
|
+
"tid=#{Thread.current.object_id} " \
|
|
23
|
+
"fid=#{Fiber.current.object_id}"
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
def log(
|
|
21
27
|
level: @options.debug_level,
|
|
22
28
|
color: nil,
|
|
@@ -36,10 +42,9 @@ module HTTPX
|
|
|
36
42
|
klass = klass.superclass
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
message = +"(time
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"self:#{class_name}##{object_id}) "
|
|
45
|
+
message = +"(time=#{Time.now.utc} " \
|
|
46
|
+
"#{Loggable.log_identifiers} " \
|
|
47
|
+
"self=#{class_name}##{object_id}) "
|
|
43
48
|
message << msg.call << "\n"
|
|
44
49
|
message = "\e[#{COLORS[color]}m#{message}\e[0m" if color && debug_stream.respond_to?(:isatty) && debug_stream.isatty
|
|
45
50
|
debug_stream << message
|
|
@@ -49,6 +54,8 @@ module HTTPX
|
|
|
49
54
|
log(level: level, color: color, debug_level: debug_level, debug: debug) { ex.full_message }
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
private
|
|
58
|
+
|
|
52
59
|
def log_redact_headers(text)
|
|
53
60
|
log_redact(text, @options.debug_redact == :headers)
|
|
54
61
|
end
|
data/lib/httpx/options.rb
CHANGED
|
@@ -257,12 +257,16 @@ module HTTPX
|
|
|
257
257
|
|
|
258
258
|
other_opts = opts_names
|
|
259
259
|
else
|
|
260
|
-
other_opts = other
|
|
260
|
+
other_opts = other #: Hash[Symbol, untyped]
|
|
261
261
|
other_opts = Hash[other] unless other.is_a?(Hash)
|
|
262
262
|
|
|
263
263
|
return self if other_opts.empty?
|
|
264
264
|
|
|
265
|
-
return self if other_opts.all?
|
|
265
|
+
return self if other_opts.all? do |opt, v|
|
|
266
|
+
raise Error, "unknown option: #{opt}" unless respond_to?(opt)
|
|
267
|
+
|
|
268
|
+
public_send(opt) == v
|
|
269
|
+
end
|
|
266
270
|
end
|
|
267
271
|
|
|
268
272
|
opts = dup
|
|
@@ -24,7 +24,7 @@ module HTTPX
|
|
|
24
24
|
|
|
25
25
|
def authenticate(request, authenticate)
|
|
26
26
|
"Digest #{generate_header(request.verb, request.path, authenticate)}"
|
|
27
|
-
rescue
|
|
27
|
+
rescue HTTPX::Error => e
|
|
28
28
|
response = ErrorResponse.new(request, e)
|
|
29
29
|
request.response = response
|
|
30
30
|
request.emit_response(response)
|
data/lib/httpx/plugins/auth.rb
CHANGED
|
@@ -19,6 +19,8 @@ module HTTPX
|
|
|
19
19
|
# adds support for the following options:
|
|
20
20
|
#
|
|
21
21
|
# :auth_header_value :: the token to use as a string, or a callable which returns a string when called.
|
|
22
|
+
# the callable is called a request, and a boolean: when true, the user must regenerate a token, otherwise it
|
|
23
|
+
# may probe for token freshness and return the same token.
|
|
22
24
|
# :auth_header_type :: the authentication type to use in the "authorization" header value (i.e. "Bearer", "Digest"...)
|
|
23
25
|
# :auth_header_expires_at :: timestamp at which the auth header will be discarded. should be a callable (like a proc)
|
|
24
26
|
# receiving the request as an argument, and should return either a Time object, or an integer (UNIX time).
|
|
@@ -96,9 +98,8 @@ module HTTPX
|
|
|
96
98
|
auth_header_value = @auth_header_value_mtx.synchronize do
|
|
97
99
|
try_invalidate_auth_header_value
|
|
98
100
|
|
|
99
|
-
@auth_header_value ||=
|
|
101
|
+
@auth_header_value ||= generate_auth_token(request, false).tap do
|
|
100
102
|
set_auth_header_expires_at(request)
|
|
101
|
-
generate_auth_token
|
|
102
103
|
end
|
|
103
104
|
end
|
|
104
105
|
|
|
@@ -115,10 +116,10 @@ module HTTPX
|
|
|
115
116
|
@auth_header_value = @auth_header_expires_at = nil
|
|
116
117
|
end
|
|
117
118
|
|
|
118
|
-
def generate_auth_token
|
|
119
|
+
def generate_auth_token(request, should_regenerate)
|
|
119
120
|
return unless (auth_value = @options.auth_header_value)
|
|
120
121
|
|
|
121
|
-
auth_value = auth_value.call(
|
|
122
|
+
auth_value = auth_value.call(request, should_regenerate) if dynamic_auth_token?(auth_value)
|
|
122
123
|
|
|
123
124
|
auth_value
|
|
124
125
|
end
|
|
@@ -127,8 +128,8 @@ module HTTPX
|
|
|
127
128
|
@auth_header_expires_at = if (expires_in = request.options.auth_header_expires_in)
|
|
128
129
|
Time.now.to_i + expires_in
|
|
129
130
|
elsif (expires_at = request.options.auth_header_expires_at)
|
|
130
|
-
if expires_at.respond_to?(:call)
|
|
131
|
-
expires_at = expires_at.
|
|
131
|
+
if expires_at.respond_to?(:call) && (expires_at = expires_at.call(request))
|
|
132
|
+
expires_at = expires_at.to_f
|
|
132
133
|
raise Error, "`:auth_header_expires_at` must be positive" unless expires_at.positive?
|
|
133
134
|
|
|
134
135
|
expires_at
|
|
@@ -196,7 +197,7 @@ module HTTPX
|
|
|
196
197
|
# use whatever was generated for it.
|
|
197
198
|
@auth_header_value_mtx.synchronize do
|
|
198
199
|
if request.auth_token_value == @auth_header_value
|
|
199
|
-
@auth_header_value = generate_auth_token
|
|
200
|
+
@auth_header_value = generate_auth_token(request, true)
|
|
200
201
|
set_auth_header_expires_at(request)
|
|
201
202
|
end
|
|
202
203
|
end
|
|
@@ -204,6 +205,15 @@ module HTTPX
|
|
|
204
205
|
request.unauthorize!
|
|
205
206
|
end
|
|
206
207
|
|
|
208
|
+
def when_to_retry(request, response, *)
|
|
209
|
+
return super unless response.is_a?(Response)
|
|
210
|
+
|
|
211
|
+
# retry immediately if authentication no longer valid.
|
|
212
|
+
return if response.status == 401
|
|
213
|
+
|
|
214
|
+
super
|
|
215
|
+
end
|
|
216
|
+
|
|
207
217
|
def auth_error?(response, options)
|
|
208
218
|
response.is_a?(Response) && response.status == 401 && dynamic_auth_token?(options.auth_header_value)
|
|
209
219
|
end
|
|
@@ -122,6 +122,18 @@ module HTTPX
|
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
+
module RequestMethods
|
|
126
|
+
def drain_body
|
|
127
|
+
super.tap do |chunk|
|
|
128
|
+
emit(:body_chunk, chunk) if chunk
|
|
129
|
+
rescue StandardError => e
|
|
130
|
+
# in case an error occurs in callback code
|
|
131
|
+
@drain_error = e
|
|
132
|
+
nil
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
125
137
|
module ConnectionMethods
|
|
126
138
|
private
|
|
127
139
|
|
|
@@ -51,9 +51,13 @@ module HTTPX
|
|
|
51
51
|
|
|
52
52
|
if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
53
53
|
request.transition(:idle)
|
|
54
|
+
|
|
54
55
|
if (auth_header = digest.authenticate(request, probe_response.headers["www-authenticate"]))
|
|
55
56
|
request.authorize(auth_header)
|
|
56
57
|
end
|
|
58
|
+
|
|
59
|
+
next(request.response) if request.response
|
|
60
|
+
|
|
57
61
|
super(request)
|
|
58
62
|
else
|
|
59
63
|
probe_response
|
|
@@ -70,6 +70,12 @@ module HTTPX
|
|
|
70
70
|
)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
def initial_call
|
|
74
|
+
return unless current_context?
|
|
75
|
+
|
|
76
|
+
super
|
|
77
|
+
end
|
|
78
|
+
|
|
73
79
|
def interests
|
|
74
80
|
return if connecting? && @pending.none?(&:current_context?)
|
|
75
81
|
|
|
@@ -193,6 +199,12 @@ module HTTPX
|
|
|
193
199
|
end
|
|
194
200
|
|
|
195
201
|
module ResolverNativeMethods
|
|
202
|
+
def initial_call
|
|
203
|
+
return unless @queries.values.any?(&:current_context?) || @connections.any?(&:current_context?)
|
|
204
|
+
|
|
205
|
+
super
|
|
206
|
+
end
|
|
207
|
+
|
|
196
208
|
def calculate_interests
|
|
197
209
|
return if @queries.empty?
|
|
198
210
|
|
|
@@ -218,11 +230,23 @@ module HTTPX
|
|
|
218
230
|
end
|
|
219
231
|
|
|
220
232
|
module ResolverSystemMethods
|
|
233
|
+
def initial_call
|
|
234
|
+
return unless current_context?
|
|
235
|
+
|
|
236
|
+
super
|
|
237
|
+
end
|
|
238
|
+
|
|
221
239
|
def interests
|
|
222
|
-
return unless
|
|
240
|
+
return unless current_context?
|
|
223
241
|
|
|
224
242
|
super
|
|
225
243
|
end
|
|
244
|
+
|
|
245
|
+
private
|
|
246
|
+
|
|
247
|
+
def current_context?
|
|
248
|
+
@queries.any? { |_, conn| conn.current_context? }
|
|
249
|
+
end
|
|
226
250
|
end
|
|
227
251
|
|
|
228
252
|
module FiberConcurrencyH2C
|
|
@@ -135,9 +135,9 @@ module HTTPX
|
|
|
135
135
|
return ErrorResponse.new(request, error)
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
-
|
|
138
|
+
redirect_request = build_request(redirect_method, redirect_uri, redirect_params, options)
|
|
139
139
|
|
|
140
|
-
request.redirect_request =
|
|
140
|
+
request.redirect_request = redirect_request
|
|
141
141
|
|
|
142
142
|
redirect_after = response.headers["retry-after"]
|
|
143
143
|
|
|
@@ -151,24 +151,24 @@ module HTTPX
|
|
|
151
151
|
redirect_after = Utils.parse_retry_after(redirect_after)
|
|
152
152
|
|
|
153
153
|
retry_start = Utils.now
|
|
154
|
-
log { "redirecting after #{redirect_after} secs..." }
|
|
154
|
+
redirect_request.log { "redirecting after #{redirect_after} secs..." }
|
|
155
155
|
selector.after(redirect_after) do
|
|
156
156
|
if (response = request.response)
|
|
157
157
|
response.finish!
|
|
158
|
-
|
|
158
|
+
redirect_request.response = response
|
|
159
159
|
# request has terminated abruptly meanwhile
|
|
160
|
-
|
|
160
|
+
redirect_request.emit_response(response)
|
|
161
161
|
else
|
|
162
|
-
log { "redirecting (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
|
163
|
-
send_request(
|
|
162
|
+
redirect_request.log { "redirecting (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
|
163
|
+
send_request(redirect_request, selector, options)
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
else
|
|
167
|
-
send_request(
|
|
167
|
+
send_request(redirect_request, selector, options)
|
|
168
168
|
|
|
169
169
|
# recalling itself, in case an error was triggered by the above, and we can
|
|
170
170
|
# verify retriability again.
|
|
171
|
-
return fetch_response(
|
|
171
|
+
return fetch_response(redirect_request, selector, options)
|
|
172
172
|
end
|
|
173
173
|
nil
|
|
174
174
|
end
|
|
@@ -231,6 +231,18 @@ module HTTPX
|
|
|
231
231
|
@redirect_request.response
|
|
232
232
|
end
|
|
233
233
|
|
|
234
|
+
def response=(response)
|
|
235
|
+
return super unless @redirect_request && @response.nil? # rubocop:disable Lint/ReturnInVoidContext
|
|
236
|
+
|
|
237
|
+
@redirect_request.response = response
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def emit_response(response)
|
|
241
|
+
return super unless @redirect_request && @response.nil?
|
|
242
|
+
|
|
243
|
+
@redirect_request.emit_response(response)
|
|
244
|
+
end
|
|
245
|
+
|
|
234
246
|
def max_redirects
|
|
235
247
|
@options.max_redirects || MAX_REDIRECTS
|
|
236
248
|
end
|
data/lib/httpx/plugins/oauth.rb
CHANGED
|
@@ -296,9 +296,11 @@ module HTTPX
|
|
|
296
296
|
|
|
297
297
|
private
|
|
298
298
|
|
|
299
|
-
def generate_auth_token
|
|
300
|
-
return unless @oauth_session
|
|
299
|
+
def generate_auth_token(*)
|
|
300
|
+
return super unless @oauth_session
|
|
301
301
|
|
|
302
|
+
# should_regenerate arg ignored, as there's no way to check
|
|
303
|
+
# for token expiration yet.
|
|
302
304
|
@oauth_session.fetch_access_token(self)
|
|
303
305
|
end
|
|
304
306
|
|
|
@@ -18,6 +18,12 @@ module HTTPX
|
|
|
18
18
|
# https://gitlab.com/os85/httpx/wikis/Persistent
|
|
19
19
|
#
|
|
20
20
|
module Persistent
|
|
21
|
+
SAFE_REONNECTABLE_ERRORS = [
|
|
22
|
+
PingTimeoutError,
|
|
23
|
+
Connection::HTTP2::GoawayError,
|
|
24
|
+
Connection::HTTP2::PingError,
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
21
27
|
class << self
|
|
22
28
|
def load_dependencies(klass)
|
|
23
29
|
klass.plugin(:fiber_concurrency)
|
|
@@ -55,13 +61,38 @@ module HTTPX
|
|
|
55
61
|
|
|
56
62
|
private
|
|
57
63
|
|
|
64
|
+
def reconnectable_error?(error)
|
|
65
|
+
Retries::RECONNECTABLE_ERRORS.any? { |klass| error.is_a?(klass) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# whether the error can be safely retried without booking threshold attempts.
|
|
69
|
+
def safe_reconnectable_error?(error)
|
|
70
|
+
SAFE_REONNECTABLE_ERRORS.any? { |klass| error.is_a?(klass) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def can_reconnect?(_, response)
|
|
74
|
+
super || (response.is_a?(ErrorResponse) && safe_reconnectable_error?(response.error))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def when_to_retry(request, response, *)
|
|
78
|
+
return super unless response.is_a?(ErrorResponse)
|
|
79
|
+
|
|
80
|
+
error = response.error
|
|
81
|
+
# allow request to be retried immediately if the request failed right after the keep alive timeout.
|
|
82
|
+
# the chances are, the request failed because the connect has been dropped by the peer server, so it's
|
|
83
|
+
# fine to reopen.
|
|
84
|
+
return if request.ping? && reconnectable_error?(error)
|
|
85
|
+
|
|
86
|
+
super
|
|
87
|
+
end
|
|
88
|
+
|
|
58
89
|
def retryable_request?(request, response, *)
|
|
59
90
|
super || begin
|
|
60
|
-
return false unless response
|
|
91
|
+
return false unless response.is_a?(ErrorResponse)
|
|
61
92
|
|
|
62
93
|
error = response.error
|
|
63
94
|
|
|
64
|
-
|
|
95
|
+
reconnectable_error?(error)
|
|
65
96
|
end
|
|
66
97
|
end
|
|
67
98
|
|
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -182,9 +182,11 @@ module HTTPX
|
|
|
182
182
|
if (no_proxy = proxy.no_proxy)
|
|
183
183
|
no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
unless no_proxy != "*" && # NO_PROXY=* bypasses proxy use
|
|
186
|
+
URI::Generic.use_proxy?(request_uri.host, next_proxy.host, next_proxy.port, no_proxy)
|
|
187
|
+
# TODO: setting proxy to nil leaks the connection object in the pool
|
|
188
|
+
return super(request_uri, selector, options.merge(proxy: nil))
|
|
189
|
+
end
|
|
188
190
|
end
|
|
189
191
|
|
|
190
192
|
super(request_uri, selector, options.merge(proxy: proxy))
|
|
@@ -341,6 +343,10 @@ module HTTPX
|
|
|
341
343
|
module InstanceMethods
|
|
342
344
|
private
|
|
343
345
|
|
|
346
|
+
def proxy_error?(request, response, _)
|
|
347
|
+
super && !request.retries.positive?
|
|
348
|
+
end
|
|
349
|
+
|
|
344
350
|
def retryable_error?(ex, *)
|
|
345
351
|
super || ex.is_a?(ProxyConnectionError)
|
|
346
352
|
end
|
|
@@ -44,9 +44,9 @@ module HTTPX
|
|
|
44
44
|
|
|
45
45
|
def __on_promise_request(parser, stream, h)
|
|
46
46
|
log(level: 1, color: :yellow) do
|
|
47
|
-
# :
|
|
47
|
+
# simplecov:disable
|
|
48
48
|
h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
|
|
49
|
-
# :
|
|
49
|
+
# simplecov:enable
|
|
50
50
|
end
|
|
51
51
|
headers = @options.headers_class.new(h)
|
|
52
52
|
path = headers[":path"]
|
|
@@ -29,7 +29,10 @@ module HTTPX
|
|
|
29
29
|
Errno::ETIMEDOUT,
|
|
30
30
|
ConnectionError,
|
|
31
31
|
TLSError,
|
|
32
|
-
|
|
32
|
+
Zlib::BufError,
|
|
33
|
+
PingTimeoutError,
|
|
34
|
+
Connection::HTTP2::GoawayError,
|
|
35
|
+
Connection::HTTP2::PingError,
|
|
33
36
|
].freeze
|
|
34
37
|
|
|
35
38
|
RETRYABLE_ERRORS = (RECONNECTABLE_ERRORS + [
|
|
@@ -148,10 +151,13 @@ module HTTPX
|
|
|
148
151
|
retryable_response?(response, options)
|
|
149
152
|
try_partial_retry(request, response)
|
|
150
153
|
log { "failed to get response, #{request.retries} tries to go..." }
|
|
151
|
-
prepare_to_retry(request, response)
|
|
152
154
|
|
|
153
|
-
|
|
155
|
+
# retry-after must be calculated before prepare_to_retry, as it relies on
|
|
156
|
+
# state changed byit.
|
|
157
|
+
retry_after = when_to_retry(request, response, options)
|
|
158
|
+
prepare_to_retry(request, response)
|
|
154
159
|
|
|
160
|
+
if retry_after&.positive?
|
|
155
161
|
retry_start = Utils.now
|
|
156
162
|
log { "retrying after #{retry_after} secs..." }
|
|
157
163
|
selector.after(retry_after) do
|
|
@@ -191,13 +197,14 @@ module HTTPX
|
|
|
191
197
|
RETRYABLE_ERRORS.any? { |klass| ex.is_a?(klass) } && !ex.is_a?(TotalRequestTimeoutError)
|
|
192
198
|
end
|
|
193
199
|
|
|
194
|
-
def
|
|
195
|
-
|
|
200
|
+
def prepare_to_retry(request, response)
|
|
201
|
+
request.retries -= 1 unless can_reconnect?(request, response)
|
|
202
|
+
request.transition(:idle)
|
|
196
203
|
end
|
|
197
204
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
request.
|
|
205
|
+
# do not exhaust retries on connection liveness probes
|
|
206
|
+
def can_reconnect?(request, _)
|
|
207
|
+
request.ping?
|
|
201
208
|
end
|
|
202
209
|
|
|
203
210
|
def when_to_retry(request, response, options)
|
|
@@ -146,7 +146,7 @@ module HTTPX
|
|
|
146
146
|
def when_to_retry(request, *)
|
|
147
147
|
retry_after = request.last_server_sent_message&.retry_after
|
|
148
148
|
|
|
149
|
-
retry_after / 1_000.0 if retry_after # original in milliseconds
|
|
149
|
+
retry_after.to_i / 1_000.0 if retry_after # original in milliseconds
|
|
150
150
|
|
|
151
151
|
request.last_server_sent_message&.retry_after && super
|
|
152
152
|
end
|
|
@@ -149,7 +149,7 @@ module HTTPX
|
|
|
149
149
|
SsrfFilter.unsafe_ip_address?(ipaddr) || @options.extra_unsafe_ranges&.any? { |r| r.include?(ipaddr) }
|
|
150
150
|
end
|
|
151
151
|
|
|
152
|
-
raise ServerSideRequestForgeryError, "#{@origin.host} has no
|
|
152
|
+
raise ServerSideRequestForgeryError, "#{@origin.host} has no allowed IP addresses" if addrs.empty?
|
|
153
153
|
|
|
154
154
|
super
|
|
155
155
|
end
|
data/lib/httpx/plugins/stream.rb
CHANGED