httpx 1.8.1 → 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_8_2.md +30 -0
- data/lib/httpx/adapters/webmock.rb +2 -1
- data/lib/httpx/buffer.rb +5 -0
- data/lib/httpx/connection/http1.rb +1 -1
- data/lib/httpx/connection/http2.rb +5 -4
- data/lib/httpx/connection.rb +42 -12
- data/lib/httpx/errors.rb +3 -0
- data/lib/httpx/io/ssl.rb +18 -8
- data/lib/httpx/options.rb +6 -2
- data/lib/httpx/plugins/auth.rb +6 -5
- data/lib/httpx/plugins/follow_redirects.rb +21 -9
- data/lib/httpx/plugins/oauth.rb +4 -2
- data/lib/httpx/plugins/persistent.rb +15 -0
- data/lib/httpx/plugins/proxy.rb +9 -3
- data/lib/httpx/plugins/retries.rb +15 -8
- data/lib/httpx/plugins/server_sent_events.rb +1 -1
- data/lib/httpx/plugins/tracing.rb +17 -4
- data/lib/httpx/request/body.rb +8 -5
- data/lib/httpx/request.rb +3 -3
- data/lib/httpx/resolver/native.rb +4 -0
- data/lib/httpx/response.rb +3 -3
- data/lib/httpx/selector.rb +1 -1
- data/lib/httpx/session.rb +5 -2
- 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.rbs +3 -2
- data/sig/errors.rbs +2 -2
- data/sig/io/ssl.rbs +5 -0
- data/sig/plugins/auth.rbs +3 -3
- data/sig/plugins/persistent.rbs +4 -0
- data/sig/plugins/retries.rbs +4 -2
- data/sig/plugins/tracing.rbs +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c34100ea496b94c4dbcfcd95330a5c8fa21dd5997d49dff54f62ced363589f7f
|
|
4
|
+
data.tar.gz: 24ef436a40b17cf4df3b2a9722c36437704c0c2ed1f5a1afc59b16801c36dbb4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7796301721da3f362b4ec13bf5bffcdec864819dddf404dfb7c7ffadde69508de32e52b85e163b144d4ade6b3579aa256107680332a264b27cbe67fdd9e23875
|
|
7
|
+
data.tar.gz: 63f47f3e7c9e50b720c8637dabc506fe30e79fb403e6d658646ff6fb894dc02774c8e1c2491d938392f1184d8f26f681361f9ffd1dec6675a622c03ba0441342
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 1.8.2
|
|
2
|
+
|
|
3
|
+
## Improvements
|
|
4
|
+
|
|
5
|
+
* `:proxy` plugin: allow setting `:no_proxy` option to `"*"` (or `["*"]`) to allow bypassing proxy for all requests.
|
|
6
|
+
* Extending sessions with unknown options will now raise an error, instead of silently ignoring them (consistent with the initialization case now):
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
session = HTTPX.with(unknown_option: 1) # will raise a HTTPX::Error: unknown option: unknown_option
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
* Use `HTTP2::Connection#closed?` internally (instead of `#state`) to check for connection state; this will, in a future release of `http-2`, seamlessly support the new `:closing` state.
|
|
13
|
+
* Add missing `response` attribute reader to `HTTPX::RequestTimeoutError`.
|
|
14
|
+
* Improve correctness of calling `#dup` on requests and connections.
|
|
15
|
+
|
|
16
|
+
## Bugfixes
|
|
17
|
+
|
|
18
|
+
* `:auth` plugin: generate token first, then set the "expires_at" timestamp (bearer tokens, specially if JWT, may carry information about when it expires).
|
|
19
|
+
* `:auth` plugin: do not check for token freshness on a retry, instead force token regeneration.
|
|
20
|
+
* `:retries` plugin: do not apply "retry after" delay after a ping timeout error (the ping timeout already implies a delay, so retry on a new connection should be immediate).
|
|
21
|
+
* `:retries` plugin: do not retry on any HTTP/2 frame error (only on GOAWAY or PING frame errors).
|
|
22
|
+
* `:persistent` plugin: do not decrement request remaining retries on ping or GOAWAY frame errors.
|
|
23
|
+
* `:tracing` plugin: set connection initialization time to nil no termination (so that it doesn't get reused on persistent connections after reuse).
|
|
24
|
+
* `:follow_redirects` plugin: fixed issue when a timeout error would be raised but only bubble up to the root request, not properly closing the in-flight follow up request, and leaving the request handling phase in a broken state.
|
|
25
|
+
* datadog adapter: the connection initialization time is now set only on connect or first request received, the reason, making it more accurate.
|
|
26
|
+
* webmock adapter: `.to_timeout` helper will make the request raise a `HTTPX::RequestTimeoutError` instead of a plain `TimeoutError` (otherwise, if using persistent sessions, it'd trigger a retry instead and mess with webmock request count bookkeeping).
|
|
27
|
+
* native resolver: do not transition to `:open` if the socket was closed during resolution (which happens when an error is raised while the query bytes are buffered).
|
|
28
|
+
* fix for when setting a new session callback on an ssl context object which has been frozen after the first connection attempt (done by binding the ssl context callback to an internal ivar which can be changed).
|
|
29
|
+
* connection: protect against `Errno::ETIMEDOUT` errors on socket close by forcing transition to closed.
|
|
30
|
+
* do not set a ping timer for HTTP/1.1 on keep alive flow, as there's no ping and the connection is reestablished.
|
|
@@ -34,7 +34,8 @@ module WebMock
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def build_from_webmock_response(request, webmock_response)
|
|
37
|
-
return build_error_response(request,
|
|
37
|
+
return build_error_response(request,
|
|
38
|
+
HTTPX::RequestTimeoutError.new(request, webmock_response, 1)) if webmock_response.should_timeout
|
|
38
39
|
|
|
39
40
|
return build_error_response(request, webmock_response.exception) if webmock_response.exception
|
|
40
41
|
|
data/lib/httpx/buffer.rb
CHANGED
|
@@ -31,6 +31,7 @@ module HTTPX
|
|
|
31
31
|
attr_reader :streams, :pending
|
|
32
32
|
|
|
33
33
|
def initialize(buffer, options)
|
|
34
|
+
@callbacks = nil
|
|
34
35
|
@options = options
|
|
35
36
|
@settings = @options.http2_settings
|
|
36
37
|
@pending = []
|
|
@@ -53,7 +54,7 @@ module HTTPX
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def interests
|
|
56
|
-
if @connection.
|
|
57
|
+
if @connection.closed?
|
|
57
58
|
return unless @handshake_completed
|
|
58
59
|
|
|
59
60
|
return if @buffer.empty?
|
|
@@ -94,7 +95,7 @@ module HTTPX
|
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
def close
|
|
97
|
-
unless @connection.
|
|
98
|
+
unless @connection.closed?
|
|
98
99
|
@connection.goaway
|
|
99
100
|
emit(:timeout, @options.timeout[:close_handshake_timeout])
|
|
100
101
|
end
|
|
@@ -102,7 +103,7 @@ module HTTPX
|
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
def empty?
|
|
105
|
-
@connection.
|
|
106
|
+
@connection.closed? || @streams.empty?
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
def exhausted?
|
|
@@ -432,7 +433,7 @@ module HTTPX
|
|
|
432
433
|
end
|
|
433
434
|
|
|
434
435
|
def on_close(_last_frame, error, _payload)
|
|
435
|
-
is_connection_closed = @connection.
|
|
436
|
+
is_connection_closed = @connection.closed?
|
|
436
437
|
if error
|
|
437
438
|
@buffer.clear if is_connection_closed
|
|
438
439
|
case error
|
data/lib/httpx/connection.rb
CHANGED
|
@@ -36,19 +36,20 @@ module HTTPX
|
|
|
36
36
|
|
|
37
37
|
def_delegator :@write_buffer, :empty?
|
|
38
38
|
|
|
39
|
-
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling
|
|
39
|
+
attr_reader :type, :io, :origin, :origins, :state, :pending, :options, :ssl_session, :sibling,
|
|
40
|
+
:read_buffer, :write_buffer
|
|
40
41
|
|
|
41
42
|
attr_writer :current_selector
|
|
42
43
|
|
|
43
44
|
attr_accessor :current_session, :family
|
|
44
45
|
|
|
45
|
-
protected :ssl_session, :sibling
|
|
46
|
+
protected :ssl_session, :sibling, :read_buffer, :write_buffer
|
|
46
47
|
|
|
47
48
|
def initialize(uri, options)
|
|
48
49
|
@current_session = @current_selector = @max_concurrent_requests =
|
|
49
50
|
@parser = @sibling = @coalesced_connection = @altsvc_connection =
|
|
50
|
-
@
|
|
51
|
-
|
|
51
|
+
@callbacks = @ping_timer = @family =
|
|
52
|
+
@io = @ssl_session = @timeout = @connected_at = @response_received_at = nil
|
|
52
53
|
|
|
53
54
|
@exhausted = @cloned = @main_sibling = false
|
|
54
55
|
|
|
@@ -76,6 +77,29 @@ module HTTPX
|
|
|
76
77
|
self.addresses = @options.addresses if @options.addresses
|
|
77
78
|
end
|
|
78
79
|
|
|
80
|
+
# dupped initialization
|
|
81
|
+
def initialize_dup(orig)
|
|
82
|
+
super
|
|
83
|
+
@callbacks = @parser = @sibling = @coalesced_connection = @altsvc_connection = nil
|
|
84
|
+
@origins = orig.origins.dup
|
|
85
|
+
@read_buffer = orig.read_buffer.dup
|
|
86
|
+
@write_buffer = orig.write_buffer.dup
|
|
87
|
+
@inflight = 0
|
|
88
|
+
@pending = []
|
|
89
|
+
transition(:idle)
|
|
90
|
+
|
|
91
|
+
if @io
|
|
92
|
+
# initialize new IO object with the same set of addresses
|
|
93
|
+
addresses = @io.addresses
|
|
94
|
+
@io = nil
|
|
95
|
+
self.addresses = addresses
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
return unless @current_session && @current_selector
|
|
99
|
+
|
|
100
|
+
@current_session.pin(self, @current_selector)
|
|
101
|
+
end
|
|
102
|
+
|
|
79
103
|
def peer
|
|
80
104
|
@origin
|
|
81
105
|
end
|
|
@@ -148,11 +172,11 @@ module HTTPX
|
|
|
148
172
|
|
|
149
173
|
def merge(connection)
|
|
150
174
|
@origins |= connection.instance_variable_get(:@origins)
|
|
151
|
-
if @ssl_session.nil? && connection.ssl_session
|
|
152
|
-
@ssl_session =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
175
|
+
if @ssl_session.nil? && (ssl_session = connection.ssl_session)
|
|
176
|
+
@ssl_session = ssl_session
|
|
177
|
+
# the socket only needs the merged session if it can still resume it,
|
|
178
|
+
# i.e. if TLS hasn't been negotiated yet.
|
|
179
|
+
@io.ssl_session = ssl_session if @io.is_a?(SSL) && !@io.connected?
|
|
156
180
|
end
|
|
157
181
|
connection.purge_pending do |req|
|
|
158
182
|
req.transition(:idle)
|
|
@@ -341,7 +365,6 @@ module HTTPX
|
|
|
341
365
|
end
|
|
342
366
|
|
|
343
367
|
transition(:closing)
|
|
344
|
-
|
|
345
368
|
transition(:closed)
|
|
346
369
|
end
|
|
347
370
|
|
|
@@ -775,14 +798,19 @@ module HTTPX
|
|
|
775
798
|
Errno::ECONNRESET,
|
|
776
799
|
Errno::EADDRNOTAVAIL,
|
|
777
800
|
Errno::EHOSTUNREACH,
|
|
778
|
-
Errno::EINVAL,
|
|
779
801
|
Errno::ENETUNREACH,
|
|
802
|
+
Errno::EHOSTDOWN,
|
|
803
|
+
Errno::ENETDOWN,
|
|
804
|
+
Errno::EINVAL,
|
|
780
805
|
Errno::EPIPE,
|
|
781
806
|
Errno::ENOENT,
|
|
782
807
|
SocketError,
|
|
783
808
|
IOError => e
|
|
784
809
|
on_connect_error(e)
|
|
785
|
-
rescue TLSError,
|
|
810
|
+
rescue TLSError,
|
|
811
|
+
Errno::ETIMEDOUT,
|
|
812
|
+
::HTTP2::Error::ProtocolError,
|
|
813
|
+
::HTTP2::Error::HandshakeError => e
|
|
786
814
|
# connect errors, exit gracefully
|
|
787
815
|
handle_error(e)
|
|
788
816
|
handle_connect_error(e) if connecting?
|
|
@@ -979,6 +1007,8 @@ module HTTPX
|
|
|
979
1007
|
|
|
980
1008
|
parser.ping
|
|
981
1009
|
|
|
1010
|
+
return unless parser.waiting_for_ping?
|
|
1011
|
+
|
|
982
1012
|
ping_timeout = @options.timeout[:ping_timeout]
|
|
983
1013
|
|
|
984
1014
|
@ping_timer = @current_selector.after(ping_timeout) do
|
data/lib/httpx/errors.rb
CHANGED
|
@@ -43,6 +43,9 @@ module HTTPX
|
|
|
43
43
|
# The HTTPX::Request request object this exception refers to.
|
|
44
44
|
attr_reader :request
|
|
45
45
|
|
|
46
|
+
# The response object this exception refers to.
|
|
47
|
+
attr_reader :response
|
|
48
|
+
|
|
46
49
|
# initializes the exception with the +request+ and +response+ it refers to, and the
|
|
47
50
|
# +timeout+ causing the error, and the
|
|
48
51
|
def initialize(request, response, timeout)
|
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -6,20 +6,19 @@ 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
|
|
20
|
+
@ssl_session = @session_new_cb = nil
|
|
21
|
+
|
|
23
22
|
ctx_options = TLS_OPTIONS
|
|
24
23
|
ctx_options = ctx_options.merge(options.ssl) if options.ssl && !options.ssl.empty?
|
|
25
24
|
@sni_hostname = (ctx_options.delete(:hostname) if ctx_options.key?(:hostname)) || @hostname
|
|
@@ -35,6 +34,7 @@ module HTTPX
|
|
|
35
34
|
@ctx.session_cache_mode =
|
|
36
35
|
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
|
37
36
|
end
|
|
37
|
+
init_session_new_cb
|
|
38
38
|
|
|
39
39
|
yield(self) if block_given?
|
|
40
40
|
end
|
|
@@ -43,14 +43,24 @@ module HTTPX
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
|
|
46
|
+
# sets the ssl session callback to be picked up by the ssl context.
|
|
46
47
|
def session_new_cb(&pr)
|
|
47
|
-
@
|
|
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) }
|
|
48
54
|
end
|
|
49
55
|
else
|
|
50
56
|
# session_new_cb not implemented under JRuby
|
|
51
57
|
def session_new_cb; end
|
|
58
|
+
|
|
59
|
+
def init_session_new_cb; end
|
|
52
60
|
end
|
|
53
61
|
|
|
62
|
+
private :init_session_new_cb
|
|
63
|
+
|
|
54
64
|
def protocol
|
|
55
65
|
return super unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
56
66
|
|
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
|
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
|
|
@@ -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
|
|
@@ -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)
|
|
@@ -59,6 +65,15 @@ module HTTPX
|
|
|
59
65
|
Retries::RECONNECTABLE_ERRORS.any? { |klass| error.is_a?(klass) }
|
|
60
66
|
end
|
|
61
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
|
+
|
|
62
77
|
def when_to_retry(request, response, *)
|
|
63
78
|
return super unless response.is_a?(ErrorResponse)
|
|
64
79
|
|
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
|
|
@@ -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
|
|
@@ -108,11 +108,13 @@ module HTTPX::Plugins
|
|
|
108
108
|
def initialize(*)
|
|
109
109
|
super
|
|
110
110
|
|
|
111
|
-
@init_time =
|
|
111
|
+
@init_time = nil
|
|
112
112
|
end
|
|
113
113
|
|
|
114
114
|
def send_request_to_parser(request)
|
|
115
115
|
if connecting?
|
|
116
|
+
@init_time ||= ::Time.now.utc
|
|
117
|
+
|
|
116
118
|
# request span timeframe should include the time it took to connect.
|
|
117
119
|
request.init_time ||= @init_time
|
|
118
120
|
end
|
|
@@ -123,13 +125,24 @@ module HTTPX::Plugins
|
|
|
123
125
|
def idling
|
|
124
126
|
super
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
@init_time = nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def terminate
|
|
132
|
+
super
|
|
133
|
+
|
|
134
|
+
# ensure that connections which go back to the pool reset their init time.
|
|
135
|
+
@init_time = nil
|
|
129
136
|
end
|
|
130
137
|
|
|
131
138
|
private
|
|
132
139
|
|
|
140
|
+
def connect
|
|
141
|
+
@init_time ||= ::Time.now.utc
|
|
142
|
+
|
|
143
|
+
super
|
|
144
|
+
end
|
|
145
|
+
|
|
133
146
|
def ping(request)
|
|
134
147
|
# if a connection is probed for liveness, the request timeframe should include
|
|
135
148
|
# it too.
|
data/lib/httpx/request/body.rb
CHANGED
|
@@ -4,14 +4,17 @@ module HTTPX
|
|
|
4
4
|
# Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
|
|
5
5
|
class Request::Body < SimpleDelegator
|
|
6
6
|
class << self
|
|
7
|
-
def new(
|
|
8
|
-
|
|
7
|
+
def new(h, options, body: nil, **params)
|
|
8
|
+
case body
|
|
9
|
+
when self
|
|
9
10
|
# request derives its options from body
|
|
10
11
|
body.options = options.merge(params)
|
|
11
|
-
|
|
12
|
+
body
|
|
13
|
+
when nil
|
|
14
|
+
super(h, options, **params)
|
|
15
|
+
else
|
|
16
|
+
super
|
|
12
17
|
end
|
|
13
|
-
|
|
14
|
-
super
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
|
data/lib/httpx/request.rb
CHANGED
|
@@ -105,8 +105,8 @@ module HTTPX
|
|
|
105
105
|
|
|
106
106
|
@state = :idle
|
|
107
107
|
@connection = @response =
|
|
108
|
-
@drainer = @peer_address =
|
|
109
|
-
|
|
108
|
+
@drainer = @peer_address = @callbacks =
|
|
109
|
+
@informational_status = @on_response_arrived = nil
|
|
110
110
|
@ping = @started = false
|
|
111
111
|
@persistent = @options.persistent
|
|
112
112
|
@active_timeouts = []
|
|
@@ -115,9 +115,9 @@ module HTTPX
|
|
|
115
115
|
# dupped initialization
|
|
116
116
|
def initialize_dup(orig)
|
|
117
117
|
super
|
|
118
|
-
@uri = orig.instance_variable_get(:@uri).dup
|
|
119
118
|
@headers = orig.instance_variable_get(:@headers).dup
|
|
120
119
|
@body = orig.instance_variable_get(:@body).dup
|
|
120
|
+
@active_timeouts = orig.instance_variable_get(:@active_timeouts).dup
|
|
121
121
|
end
|
|
122
122
|
|
|
123
123
|
def complete!(response = @response)
|
|
@@ -539,6 +539,10 @@ module HTTPX
|
|
|
539
539
|
return unless @io.connected?
|
|
540
540
|
|
|
541
541
|
resolve if @queries.empty? && !@connections.empty?
|
|
542
|
+
|
|
543
|
+
# #resolve may have closed the resolver already as part of error handling.
|
|
544
|
+
# @fiber-switch-guard
|
|
545
|
+
return unless @io
|
|
542
546
|
when :closed
|
|
543
547
|
return if @state == :closed
|
|
544
548
|
|
data/lib/httpx/response.rb
CHANGED
|
@@ -36,10 +36,10 @@ module HTTPX
|
|
|
36
36
|
# * HTTPX::Response::Body#read
|
|
37
37
|
# * HTTPX::Response::Body#copy_to
|
|
38
38
|
# * HTTPX::Response::Body#close
|
|
39
|
-
attr_reader
|
|
39
|
+
attr_reader :body
|
|
40
40
|
|
|
41
41
|
# The HTTP protocol version used to fetch the response.
|
|
42
|
-
attr_reader
|
|
42
|
+
attr_reader :version
|
|
43
43
|
|
|
44
44
|
# returns the response body buffered in a string.
|
|
45
45
|
def_delegator :@body, :to_s
|
|
@@ -68,7 +68,7 @@ module HTTPX
|
|
|
68
68
|
@headers = @options.headers_class.new(headers)
|
|
69
69
|
@body = @options.response_body_class.new(self, @options)
|
|
70
70
|
@finished = complete?
|
|
71
|
-
@content_type = @content_length = nil
|
|
71
|
+
@callbacks = @content_type = @content_length = nil
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
# dupped initialization
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -56,7 +56,7 @@ module HTTPX
|
|
|
56
56
|
|
|
57
57
|
begin
|
|
58
58
|
select(timeout) do |c|
|
|
59
|
-
c.log(level: 2) { "[#{c.state}] selected from selector##{object_id}
|
|
59
|
+
c.log(level: 2) { "[#{c.state}] selected from selector##{object_id}#{" after #{timeout} secs" unless timeout.nil?}..." }
|
|
60
60
|
|
|
61
61
|
c.call
|
|
62
62
|
end
|
data/lib/httpx/session.rb
CHANGED
|
@@ -237,7 +237,7 @@ module HTTPX
|
|
|
237
237
|
|
|
238
238
|
return unless response && response.finished?
|
|
239
239
|
|
|
240
|
-
log(level: 2) { "response##{response.object_id} fetched" }
|
|
240
|
+
request.log(level: 2) { "response##{response.object_id} fetched" }
|
|
241
241
|
|
|
242
242
|
response
|
|
243
243
|
end
|
|
@@ -337,6 +337,8 @@ module HTTPX
|
|
|
337
337
|
end
|
|
338
338
|
end
|
|
339
339
|
|
|
340
|
+
log(level: 2) { "waiting to receive #{pending} pending requests..." }
|
|
341
|
+
|
|
340
342
|
until pending.zero? || selector.empty?
|
|
341
343
|
# loop on selector until at least one response has been received.
|
|
342
344
|
waiting = true
|
|
@@ -360,7 +362,8 @@ module HTTPX
|
|
|
360
362
|
end
|
|
361
363
|
end
|
|
362
364
|
|
|
363
|
-
raise Error, "something went wrong, responses not found
|
|
365
|
+
raise Error, "something went wrong, #{pending} responses not found " \
|
|
366
|
+
"and requests not resent" unless pending.zero?
|
|
364
367
|
|
|
365
368
|
responses
|
|
366
369
|
end
|
|
@@ -51,7 +51,7 @@ module HTTPX::Transcoder
|
|
|
51
51
|
method(:json_load)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
# rubocop:disable Style/SingleLineMethods
|
|
54
|
+
# rubocop:disable-next Style/SingleLineMethods
|
|
55
55
|
if defined?(MultiJson)
|
|
56
56
|
def json_load(*args); MultiJson.load(*args); end
|
|
57
57
|
def json_dump(*args); MultiJson.dump(*args); end
|
|
@@ -66,6 +66,5 @@ module HTTPX::Transcoder
|
|
|
66
66
|
def json_load(*args); ::JSON.parse(*args); end
|
|
67
67
|
def json_dump(*args); ::JSON.generate(*args); end
|
|
68
68
|
end
|
|
69
|
-
# rubocop:enable Style/SingleLineMethods
|
|
70
69
|
end
|
|
71
70
|
end
|
|
@@ -55,7 +55,7 @@ module HTTPX
|
|
|
55
55
|
|
|
56
56
|
private
|
|
57
57
|
|
|
58
|
-
# rubocop:disable Naming/MemoizedInstanceVariableName
|
|
58
|
+
# rubocop:disable-next Naming/MemoizedInstanceVariableName
|
|
59
59
|
def buffer_deflate!
|
|
60
60
|
return @buffer if defined?(@buffer)
|
|
61
61
|
|
|
@@ -68,7 +68,6 @@ module HTTPX
|
|
|
68
68
|
|
|
69
69
|
@buffer = buffer
|
|
70
70
|
end
|
|
71
|
-
# rubocop:enable Naming/MemoizedInstanceVariableName
|
|
72
71
|
end
|
|
73
72
|
end
|
|
74
73
|
end
|
data/lib/httpx/version.rb
CHANGED
data/sig/connection.rbs
CHANGED
|
@@ -32,10 +32,11 @@ module HTTPX
|
|
|
32
32
|
attr_accessor current_session: Session?
|
|
33
33
|
attr_accessor family: Integer?
|
|
34
34
|
|
|
35
|
+
attr_reader read_buffer: Buffer
|
|
36
|
+
attr_reader write_buffer: Buffer
|
|
37
|
+
|
|
35
38
|
|
|
36
39
|
@window_size: Integer
|
|
37
|
-
@read_buffer: Buffer
|
|
38
|
-
@write_buffer: Buffer
|
|
39
40
|
@inflight: Integer
|
|
40
41
|
@max_concurrent_requests: Integer?
|
|
41
42
|
@keep_alive_timeout: interval?
|
data/sig/errors.rbs
CHANGED
|
@@ -31,9 +31,9 @@ module HTTPX
|
|
|
31
31
|
|
|
32
32
|
class RequestTimeoutError < TimeoutError
|
|
33
33
|
attr_reader request: Request
|
|
34
|
-
attr_reader response:
|
|
34
|
+
attr_reader response: untyped
|
|
35
35
|
|
|
36
|
-
def initialize: (Request request,
|
|
36
|
+
def initialize: (Request request, untyped response, interval timeout) -> void
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
class ReadTimeoutError < RequestTimeoutError
|
data/sig/io/ssl.rbs
CHANGED
|
@@ -9,6 +9,7 @@ module HTTPX
|
|
|
9
9
|
@ctx: OpenSSL::SSL::SSLContext
|
|
10
10
|
@verify_hostname: bool
|
|
11
11
|
@sni_hostname: String
|
|
12
|
+
@session_new_cb: (^(OpenSSL::SSL::Session sess) -> void)?
|
|
12
13
|
|
|
13
14
|
attr_writer ssl_session: OpenSSL::SSL::Session?
|
|
14
15
|
|
|
@@ -24,5 +25,9 @@ module HTTPX
|
|
|
24
25
|
|
|
25
26
|
# :nocov:
|
|
26
27
|
def try_ssl_connect: () -> void
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def init_session_new_cb: () -> void
|
|
27
32
|
end
|
|
28
33
|
end
|
data/sig/plugins/auth.rbs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module HTTPX
|
|
2
2
|
module Plugins
|
|
3
3
|
module Auth
|
|
4
|
-
type auth_header_value_type = String | ^(Request request) -> string
|
|
4
|
+
type auth_header_value_type = String | ^(?Request request, ?bool should_regenerate) -> string
|
|
5
5
|
|
|
6
6
|
interface _AuthOptions
|
|
7
7
|
def auth_header_value: () -> auth_header_value_type?
|
|
@@ -18,7 +18,7 @@ module HTTPX
|
|
|
18
18
|
@auth_header_value_mtx: Thread::Mutex
|
|
19
19
|
@skip_auth_header_value: bool
|
|
20
20
|
|
|
21
|
-
def authorization: (?string token, ?auth_header_type: string) ?{ (Request) -> string } -> instance
|
|
21
|
+
def authorization: (?string token, ?auth_header_type: string) ?{ (?Request, ?bool) -> string } -> instance
|
|
22
22
|
|
|
23
23
|
def bearer_auth: (?string token) ?{ (Request) -> string } -> instance
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ module HTTPX
|
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
-
def generate_auth_token: () -> String?
|
|
31
|
+
def generate_auth_token: (Request & RequestMethods request, bool should_regenerate) -> String?
|
|
32
32
|
|
|
33
33
|
def dynamic_auth_token?: (auth_header_value_type auth_header_value) -> boolish
|
|
34
34
|
end
|
data/sig/plugins/persistent.rbs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module HTTPX
|
|
2
2
|
module Plugins
|
|
3
3
|
module Persistent
|
|
4
|
+
SAFE_REONNECTABLE_ERRORS: Array[singleton(StandardError)]
|
|
5
|
+
|
|
4
6
|
def self.load_dependencies: (singleton(Session)) -> void
|
|
5
7
|
|
|
6
8
|
def self.extra_options: (Options) -> (Options)
|
|
@@ -9,6 +11,8 @@ module HTTPX
|
|
|
9
11
|
private
|
|
10
12
|
|
|
11
13
|
def reconnectable_error?: (StandardError error) -> bool
|
|
14
|
+
|
|
15
|
+
def safe_reconnectable_error?: (StandardError error) -> bool
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
|
data/sig/plugins/retries.rbs
CHANGED
|
@@ -45,9 +45,11 @@ module HTTPX
|
|
|
45
45
|
|
|
46
46
|
def try_partial_retry: (retriesRequest request, retriesResponse response) -> void
|
|
47
47
|
|
|
48
|
-
def prepare_to_retry: (
|
|
48
|
+
def prepare_to_retry: (retriesRequest request, retriesResponse response) -> void
|
|
49
49
|
|
|
50
|
-
def
|
|
50
|
+
def can_reconnect?: (retriesRequest request, retriesResponse response) -> bool
|
|
51
|
+
|
|
52
|
+
def when_to_retry: (retriesRequest request, retriesResponse response, retriesOptions options) -> Numeric?
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
module RequestMethods
|
data/sig/plugins/tracing.rbs
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: httpx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.8.
|
|
4
|
+
version: 1.8.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tiago Cardoso
|
|
@@ -170,6 +170,7 @@ extra_rdoc_files:
|
|
|
170
170
|
- doc/release_notes/1_7_8.md
|
|
171
171
|
- doc/release_notes/1_8_0.md
|
|
172
172
|
- doc/release_notes/1_8_1.md
|
|
173
|
+
- doc/release_notes/1_8_2.md
|
|
173
174
|
files:
|
|
174
175
|
- LICENSE.txt
|
|
175
176
|
- README.md
|
|
@@ -312,6 +313,7 @@ files:
|
|
|
312
313
|
- doc/release_notes/1_7_8.md
|
|
313
314
|
- doc/release_notes/1_8_0.md
|
|
314
315
|
- doc/release_notes/1_8_1.md
|
|
316
|
+
- doc/release_notes/1_8_2.md
|
|
315
317
|
- lib/httpx.rb
|
|
316
318
|
- lib/httpx/adapters/datadog.rb
|
|
317
319
|
- lib/httpx/adapters/faraday.rb
|
|
@@ -557,7 +559,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
557
559
|
- !ruby/object:Gem::Version
|
|
558
560
|
version: '0'
|
|
559
561
|
requirements: []
|
|
560
|
-
rubygems_version:
|
|
562
|
+
rubygems_version: 4.0.16
|
|
561
563
|
specification_version: 4
|
|
562
564
|
summary: HTTPX, to the future, and beyond
|
|
563
565
|
test_files: []
|