httpx 1.8.1 → 1.8.4
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/doc/release_notes/1_8_3.md +9 -0
- data/doc/release_notes/1_8_4.md +13 -0
- data/lib/httpx/adapters/webmock.rb +2 -1
- data/lib/httpx/buffer.rb +5 -0
- data/lib/httpx/callbacks.rb +2 -2
- data/lib/httpx/chainable.rb +2 -2
- data/lib/httpx/connection/http1.rb +1 -1
- data/lib/httpx/connection/http2.rb +69 -17
- data/lib/httpx/connection.rb +58 -14
- data/lib/httpx/errors.rb +3 -0
- data/lib/httpx/io/ssl.rb +18 -8
- data/lib/httpx/options.rb +21 -12
- data/lib/httpx/plugins/auth.rb +6 -5
- data/lib/httpx/plugins/cookies/cookie.rb +1 -1
- data/lib/httpx/plugins/cookies.rb +2 -2
- data/lib/httpx/plugins/follow_redirects.rb +28 -14
- data/lib/httpx/plugins/grpc/call.rb +4 -4
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -2
- data/lib/httpx/plugins/oauth.rb +4 -2
- data/lib/httpx/plugins/persistent.rb +15 -0
- data/lib/httpx/plugins/proxy/http.rb +27 -6
- data/lib/httpx/plugins/proxy.rb +13 -7
- data/lib/httpx/plugins/retries.rb +26 -9
- data/lib/httpx/plugins/server_sent_events.rb +2 -2
- data/lib/httpx/plugins/stream.rb +2 -2
- data/lib/httpx/plugins/tracing.rb +20 -7
- data/lib/httpx/request/body.rb +8 -5
- data/lib/httpx/request.rb +8 -5
- data/lib/httpx/resolver/multi.rb +2 -2
- data/lib/httpx/resolver/native.rb +4 -0
- data/lib/httpx/resolver/resolver.rb +2 -2
- data/lib/httpx/response/body.rb +2 -2
- data/lib/httpx/response.rb +7 -7
- data/lib/httpx/selector.rb +1 -1
- data/lib/httpx/session.rb +7 -3
- data/lib/httpx/transcoder/chunker.rb +2 -2
- data/lib/httpx/transcoder/json.rb +8 -9
- data/lib/httpx/transcoder/utils/deflater.rb +1 -2
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http2.rbs +15 -1
- data/sig/connection.rbs +4 -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/proxy/http.rbs +2 -0
- data/sig/plugins/retries.rbs +4 -2
- data/sig/plugins/tracing.rbs +1 -1
- data/sig/request.rbs +2 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: daca70f040a0f0afcc413a6eb3b2e586ae56a5405477c0045cf226aaddf5b49c
|
|
4
|
+
data.tar.gz: 53910e19b4bf264a2d3cf4aed4298de57c4c6373f868e3d9e187140c15f2ed63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b19e3219fb672bfbb45c4e5427c68b9f7ba715003f5b39796a2314bd8b991b58c5bc185767e303557159f04d61d84c19523f25b953b604a74753e55859defb8
|
|
7
|
+
data.tar.gz: 844bf656335bdbc5599003d6ee9724ff54b05478bfa9de37d5e251acff788154d4d7cea4b866b56c7d4c42d07b1afa8ece6982b02c5236d981843f092ee25ceb
|
|
@@ -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.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# 1.8.3
|
|
2
|
+
|
|
3
|
+
## Bugfixes
|
|
4
|
+
|
|
5
|
+
options print a warning instead of raising an error on unknown option
|
|
6
|
+
|
|
7
|
+
the approach introduced in the previous version, of erroring on something people have been accidentally relying on, is too strict in hindsight for a patch version. This was reverted, and instead, a deprecation warning will be printed, to give time for folks to migrate.
|
|
8
|
+
|
|
9
|
+
the format of options messages (exceptions and warnings) also slightly changes to wrap the option in quotes, for proper highlighting.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 1.8.4
|
|
2
|
+
|
|
3
|
+
## Improvements
|
|
4
|
+
|
|
5
|
+
* send requests which were not sent, or were not processed by the server, when the server sends back a GOAWAY frame (which is deemed recoverable, like when there's no error), after forcing a reconnection (instead of bubbling up the GOAWAY error for all of them).
|
|
6
|
+
|
|
7
|
+
## Bugfixes
|
|
8
|
+
|
|
9
|
+
* `:retries` plugin: also retry requests which failed due to a RST_STREAM frame with REFUSED_STREAM error code.
|
|
10
|
+
* `:retries` plugin: resend requests which were piped to a closed connection internally.
|
|
11
|
+
* `:proxy` plugin: fix issue related with reconnecting inactive connections shut down by the peer, when used with the `:persistent` plugin, where the CONNECT request wasn't being sent to establish the tunnel.
|
|
12
|
+
* fixed compatibility issue starting with the `json` gem 3.0.0, when calling `HTTPX::Response#json`.
|
|
13
|
+
* avoid bookkepping issue in session, where the `on_response_arrived` on a request may be called more than once (which it can happen in the `:follow_redirects` plugin, where a request may have completed due to an error, while the request is waiting to be resent due to `retry-after`).
|
|
@@ -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
data/lib/httpx/callbacks.rb
CHANGED
|
@@ -14,9 +14,9 @@ module HTTPX
|
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def emit(type,
|
|
17
|
+
def emit(type, ...)
|
|
18
18
|
log { "emit #{type.inspect} callbacks" } if respond_to?(:log)
|
|
19
|
-
callbacks(type).delete_if { |pr| :delete == pr.call(
|
|
19
|
+
callbacks(type).delete_if { |pr| :delete == pr.call(...) } # rubocop:disable Style/YodaCondition
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def callbacks_for?(type)
|
data/lib/httpx/chainable.rb
CHANGED
|
@@ -13,8 +13,8 @@ module HTTPX
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# delegates to the default session (see HTTPX::Session#request).
|
|
16
|
-
def request(
|
|
17
|
-
branch(default_options).request(
|
|
16
|
+
def request(...)
|
|
17
|
+
branch(default_options).request(...)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def accept(type)
|
|
@@ -23,14 +23,35 @@ module HTTPX
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
class GoawayError < Error
|
|
26
|
-
|
|
26
|
+
UNRECOVERABLE_ERRORS = %i[settings_timeout inadequate_security].freeze
|
|
27
|
+
|
|
28
|
+
def initialize(code, last_stream_id)
|
|
29
|
+
@code = code
|
|
30
|
+
@last_stream_id = last_stream_id
|
|
27
31
|
super(0, code)
|
|
28
32
|
end
|
|
33
|
+
|
|
34
|
+
def last_stream_id
|
|
35
|
+
return Float::INFINITY if unrecoverable?
|
|
36
|
+
|
|
37
|
+
@last_stream_id
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def unrecoverable?
|
|
41
|
+
UNRECOVERABLE_ERRORS.include?(@code)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class RstStreamError < Error
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class RefusedStreamError < RstStreamError
|
|
29
49
|
end
|
|
30
50
|
|
|
31
51
|
attr_reader :streams, :pending
|
|
32
52
|
|
|
33
53
|
def initialize(buffer, options)
|
|
54
|
+
@callbacks = nil
|
|
34
55
|
@options = options
|
|
35
56
|
@settings = @options.http2_settings
|
|
36
57
|
@pending = []
|
|
@@ -53,7 +74,7 @@ module HTTPX
|
|
|
53
74
|
end
|
|
54
75
|
|
|
55
76
|
def interests
|
|
56
|
-
if @connection.
|
|
77
|
+
if @connection.closed?
|
|
57
78
|
return unless @handshake_completed
|
|
58
79
|
|
|
59
80
|
return if @buffer.empty?
|
|
@@ -94,7 +115,7 @@ module HTTPX
|
|
|
94
115
|
end
|
|
95
116
|
|
|
96
117
|
def close
|
|
97
|
-
unless @connection.
|
|
118
|
+
unless @connection.closed?
|
|
98
119
|
@connection.goaway
|
|
99
120
|
emit(:timeout, @options.timeout[:close_handshake_timeout])
|
|
100
121
|
end
|
|
@@ -102,7 +123,7 @@ module HTTPX
|
|
|
102
123
|
end
|
|
103
124
|
|
|
104
125
|
def empty?
|
|
105
|
-
@connection.
|
|
126
|
+
@connection.closed? || @streams.empty?
|
|
106
127
|
end
|
|
107
128
|
|
|
108
129
|
def exhausted?
|
|
@@ -148,18 +169,43 @@ module HTTPX
|
|
|
148
169
|
end
|
|
149
170
|
|
|
150
171
|
def handle_error(ex, request = nil)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
172
|
+
last_stream_id = Float::INFINITY
|
|
173
|
+
case ex
|
|
174
|
+
when OperationTimeoutError
|
|
175
|
+
if !@handshake_completed && @connection.state != :closed
|
|
176
|
+
@connection.goaway(:settings_timeout, "closing due to settings timeout")
|
|
177
|
+
emit(:close_handshake)
|
|
178
|
+
settings_ex = SettingsTimeoutError.new(ex.timeout, ex.message)
|
|
179
|
+
settings_ex.set_backtrace(ex.backtrace)
|
|
180
|
+
ex = settings_ex
|
|
181
|
+
end
|
|
182
|
+
when GoawayError
|
|
183
|
+
last_stream_id = ex.last_stream_id
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
inflight_unprocessed_requests = [] #: Array[Request]
|
|
187
|
+
|
|
188
|
+
while (req, stream = @streams.shift)
|
|
159
189
|
next if request && request == req
|
|
160
190
|
|
|
191
|
+
if stream.id > last_stream_id
|
|
192
|
+
req.transition(:idle)
|
|
193
|
+
# unprocessed request
|
|
194
|
+
inflight_unprocessed_requests << req
|
|
195
|
+
|
|
196
|
+
next
|
|
197
|
+
end
|
|
198
|
+
|
|
161
199
|
emit(:error, req, ex)
|
|
162
200
|
end
|
|
201
|
+
|
|
202
|
+
if ex.is_a?(GoawayError) && !ex.unrecoverable?
|
|
203
|
+
# resend unprocessed requests on a different connection
|
|
204
|
+
@pending.unshift(*inflight_unprocessed_requests) if inflight_unprocessed_requests.any?
|
|
205
|
+
emit(:exhausted, ex) if @pending.any?
|
|
206
|
+
return
|
|
207
|
+
end
|
|
208
|
+
|
|
163
209
|
while (req = @pending.shift)
|
|
164
210
|
next if request && request == req
|
|
165
211
|
|
|
@@ -394,7 +440,14 @@ module HTTPX
|
|
|
394
440
|
when :http_1_1_required
|
|
395
441
|
emit(:error, request, error)
|
|
396
442
|
else
|
|
397
|
-
ex =
|
|
443
|
+
ex =
|
|
444
|
+
case error
|
|
445
|
+
when :refused_stream
|
|
446
|
+
RefusedStreamError.new(stream.id, error)
|
|
447
|
+
else
|
|
448
|
+
RstStreamError.new(stream.id, error)
|
|
449
|
+
end
|
|
450
|
+
|
|
398
451
|
ex.set_backtrace(caller)
|
|
399
452
|
response = ErrorResponse.new(request, ex)
|
|
400
453
|
request.response = response
|
|
@@ -431,8 +484,8 @@ module HTTPX
|
|
|
431
484
|
send_pending
|
|
432
485
|
end
|
|
433
486
|
|
|
434
|
-
def on_close(
|
|
435
|
-
is_connection_closed = @connection.
|
|
487
|
+
def on_close(last_stream_id, error, _payload)
|
|
488
|
+
is_connection_closed = @connection.closed?
|
|
436
489
|
if error
|
|
437
490
|
@buffer.clear if is_connection_closed
|
|
438
491
|
case error
|
|
@@ -441,12 +494,11 @@ module HTTPX
|
|
|
441
494
|
emit(:error, request, error)
|
|
442
495
|
end
|
|
443
496
|
else
|
|
444
|
-
ex = GoawayError.new(error)
|
|
497
|
+
ex = GoawayError.new(error, last_stream_id)
|
|
445
498
|
ex.set_backtrace(caller)
|
|
446
499
|
|
|
447
500
|
handle_error(ex)
|
|
448
501
|
teardown
|
|
449
|
-
|
|
450
502
|
end
|
|
451
503
|
end
|
|
452
504
|
return unless is_connection_closed && @streams.empty?
|
data/lib/httpx/connection.rb
CHANGED
|
@@ -36,21 +36,25 @@ 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
|
-
@exhausted = @cloned = @main_sibling =
|
|
54
|
+
@exhausted = @cloned = @main_sibling =
|
|
55
|
+
# variable used to gate against a potential endless loop where the peer continuously closes the connection with
|
|
56
|
+
# GOAWAY frames without ever processing a request.
|
|
57
|
+
@previously_exhausted_with_error = false
|
|
54
58
|
|
|
55
59
|
@options = Options.new(options)
|
|
56
60
|
@type = initialize_type(uri, @options)
|
|
@@ -76,6 +80,29 @@ module HTTPX
|
|
|
76
80
|
self.addresses = @options.addresses if @options.addresses
|
|
77
81
|
end
|
|
78
82
|
|
|
83
|
+
# dupped initialization
|
|
84
|
+
def initialize_dup(orig)
|
|
85
|
+
super
|
|
86
|
+
@callbacks = @parser = @sibling = @coalesced_connection = @altsvc_connection = nil
|
|
87
|
+
@origins = orig.origins.dup
|
|
88
|
+
@read_buffer = orig.read_buffer.dup
|
|
89
|
+
@write_buffer = orig.write_buffer.dup
|
|
90
|
+
@inflight = 0
|
|
91
|
+
@pending = []
|
|
92
|
+
transition(:idle)
|
|
93
|
+
|
|
94
|
+
if @io
|
|
95
|
+
# initialize new IO object with the same set of addresses
|
|
96
|
+
addresses = @io.addresses
|
|
97
|
+
@io = nil
|
|
98
|
+
self.addresses = addresses
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return unless @current_session && @current_selector
|
|
102
|
+
|
|
103
|
+
@current_session.pin(self, @current_selector)
|
|
104
|
+
end
|
|
105
|
+
|
|
79
106
|
def peer
|
|
80
107
|
@origin
|
|
81
108
|
end
|
|
@@ -148,11 +175,11 @@ module HTTPX
|
|
|
148
175
|
|
|
149
176
|
def merge(connection)
|
|
150
177
|
@origins |= connection.instance_variable_get(:@origins)
|
|
151
|
-
if @ssl_session.nil? && connection.ssl_session
|
|
152
|
-
@ssl_session =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
178
|
+
if @ssl_session.nil? && (ssl_session = connection.ssl_session)
|
|
179
|
+
@ssl_session = ssl_session
|
|
180
|
+
# the socket only needs the merged session if it can still resume it,
|
|
181
|
+
# i.e. if TLS hasn't been negotiated yet.
|
|
182
|
+
@io.ssl_session = ssl_session if @io.is_a?(SSL) && !@io.connected?
|
|
156
183
|
end
|
|
157
184
|
connection.purge_pending do |req|
|
|
158
185
|
req.transition(:idle)
|
|
@@ -341,7 +368,6 @@ module HTTPX
|
|
|
341
368
|
end
|
|
342
369
|
|
|
343
370
|
transition(:closing)
|
|
344
|
-
|
|
345
371
|
transition(:closed)
|
|
346
372
|
end
|
|
347
373
|
|
|
@@ -688,6 +714,7 @@ module HTTPX
|
|
|
688
714
|
AltSvc.emit(request, response) do |alt_origin, origin, alt_params|
|
|
689
715
|
build_altsvc_connection(alt_origin, origin, alt_params)
|
|
690
716
|
end
|
|
717
|
+
@previously_exhausted_with_error = false
|
|
691
718
|
@response_received_at = Utils.now
|
|
692
719
|
@no_more_requests_counter = 0
|
|
693
720
|
@inflight -= 1
|
|
@@ -703,9 +730,19 @@ module HTTPX
|
|
|
703
730
|
parser.on(:promise) do |request, stream|
|
|
704
731
|
request.emit(:promise, parser, stream)
|
|
705
732
|
end
|
|
706
|
-
parser.on(:exhausted) do
|
|
733
|
+
parser.on(:exhausted) do |error|
|
|
707
734
|
enqueue_pending_requests_from_parser(parser)
|
|
708
735
|
|
|
736
|
+
if error
|
|
737
|
+
if @previously_exhausted_with_error
|
|
738
|
+
@previously_exhausted_with_error = false
|
|
739
|
+
on_error(error)
|
|
740
|
+
next
|
|
741
|
+
else
|
|
742
|
+
@previously_exhausted_with_error = true
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
709
746
|
@exhausted = true
|
|
710
747
|
parser.close
|
|
711
748
|
|
|
@@ -775,14 +812,19 @@ module HTTPX
|
|
|
775
812
|
Errno::ECONNRESET,
|
|
776
813
|
Errno::EADDRNOTAVAIL,
|
|
777
814
|
Errno::EHOSTUNREACH,
|
|
778
|
-
Errno::EINVAL,
|
|
779
815
|
Errno::ENETUNREACH,
|
|
816
|
+
Errno::EHOSTDOWN,
|
|
817
|
+
Errno::ENETDOWN,
|
|
818
|
+
Errno::EINVAL,
|
|
780
819
|
Errno::EPIPE,
|
|
781
820
|
Errno::ENOENT,
|
|
782
821
|
SocketError,
|
|
783
822
|
IOError => e
|
|
784
823
|
on_connect_error(e)
|
|
785
|
-
rescue TLSError,
|
|
824
|
+
rescue TLSError,
|
|
825
|
+
Errno::ETIMEDOUT,
|
|
826
|
+
::HTTP2::Error::ProtocolError,
|
|
827
|
+
::HTTP2::Error::HandshakeError => e
|
|
786
828
|
# connect errors, exit gracefully
|
|
787
829
|
handle_error(e)
|
|
788
830
|
handle_connect_error(e) if connecting?
|
|
@@ -979,6 +1021,8 @@ module HTTPX
|
|
|
979
1021
|
|
|
980
1022
|
parser.ping
|
|
981
1023
|
|
|
1024
|
+
return unless parser.waiting_for_ping?
|
|
1025
|
+
|
|
982
1026
|
ping_timeout = @options.timeout[:ping_timeout]
|
|
983
1027
|
|
|
984
1028
|
@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
|
@@ -133,12 +133,12 @@ module HTTPX
|
|
|
133
133
|
when Options
|
|
134
134
|
unknown_options = options.class.options_names - options_names
|
|
135
135
|
|
|
136
|
-
raise Error, "unknown option:
|
|
136
|
+
raise Error, "unknown option: `:#{unknown_options.first}`" unless unknown_options.empty?
|
|
137
137
|
|
|
138
138
|
DEFAULT_OPTIONS.merge(options)
|
|
139
139
|
else
|
|
140
140
|
options.each_key do |k|
|
|
141
|
-
raise Error, "unknown option:
|
|
141
|
+
raise Error, "unknown option: `:#{k}`" unless options_names.include?(k)
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
options.empty? ? DEFAULT_OPTIONS : DEFAULT_OPTIONS.merge(options)
|
|
@@ -184,7 +184,7 @@ module HTTPX
|
|
|
184
184
|
cache_type.respond_to?(:get) &&
|
|
185
185
|
cache_type.respond_to?(:set) &&
|
|
186
186
|
cache_type.respond_to?(:evict)
|
|
187
|
-
raise TypeError, "
|
|
187
|
+
raise TypeError, "`:resolver_cache` must be a compatible resolver cache and implement `#get`, `#set` and `#evict`"
|
|
188
188
|
end
|
|
189
189
|
|
|
190
190
|
cache_type #: Object & Resolver::_Cache
|
|
@@ -257,12 +257,21 @@ 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
|
+
unless respond_to?(opt)
|
|
267
|
+
# TODO: do this instead in a major or minor version bump
|
|
268
|
+
# raise Error, "unknown option: #{opt}" unless respond_to?(opt)
|
|
269
|
+
warn "DEPRECATION WARNING: unknown option: `:#{opt}`. an exception will be raised in a future version."
|
|
270
|
+
next(true)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
!respond_to?(opt) || public_send(opt) == v
|
|
274
|
+
end
|
|
266
275
|
end
|
|
267
276
|
|
|
268
277
|
opts = dup
|
|
@@ -400,7 +409,7 @@ module HTTPX
|
|
|
400
409
|
# converts +v+ into an Integer before setting the +#{option}+ option.
|
|
401
410
|
private def option_#{option}(value) # private def option_max_requests(v)
|
|
402
411
|
value = Integer(value) unless value.respond_to?(:infinite?) && value.infinite?
|
|
403
|
-
raise TypeError, "
|
|
412
|
+
raise TypeError, "`:#{option}` must be positive" unless value.positive? # raise TypeError, ":max_requests must be positive" unless value.positive?
|
|
404
413
|
|
|
405
414
|
value
|
|
406
415
|
end
|
|
@@ -452,11 +461,11 @@ module HTTPX
|
|
|
452
461
|
|
|
453
462
|
# Validate keys and values
|
|
454
463
|
timeout_hash.each do |key, val|
|
|
455
|
-
raise TypeError, "invalid timeout:
|
|
464
|
+
raise TypeError, "invalid timeout: `:#{key}`" unless default_timeouts.key?(key)
|
|
456
465
|
|
|
457
466
|
next if val.nil?
|
|
458
467
|
|
|
459
|
-
raise TypeError, "
|
|
468
|
+
raise TypeError, "`:#{key}` must be numeric" unless val.is_a?(Numeric)
|
|
460
469
|
end
|
|
461
470
|
|
|
462
471
|
timeout_hash
|
|
@@ -486,15 +495,15 @@ module HTTPX
|
|
|
486
495
|
when Symbol
|
|
487
496
|
meth = :"resolver_#{resolver_type}_class"
|
|
488
497
|
|
|
489
|
-
raise TypeError, "
|
|
498
|
+
raise TypeError, "`:resolver_class` must be a supported type" unless respond_to?(meth)
|
|
490
499
|
|
|
491
500
|
resolver_type
|
|
492
501
|
when Class
|
|
493
|
-
raise TypeError, "
|
|
502
|
+
raise TypeError, "`:resolver_class` must be a subclass of `#{Resolver::Resolver}`" unless resolver_type < Resolver::Resolver
|
|
494
503
|
|
|
495
504
|
resolver_type
|
|
496
505
|
else
|
|
497
|
-
raise TypeError, "
|
|
506
|
+
raise TypeError, "`:resolver_class` must be a supported type"
|
|
498
507
|
end
|
|
499
508
|
end
|
|
500
509
|
|
|
@@ -509,7 +518,7 @@ module HTTPX
|
|
|
509
518
|
cache_type.respond_to?(:get) &&
|
|
510
519
|
cache_type.respond_to?(:set) &&
|
|
511
520
|
cache_type.respond_to?(:evict)
|
|
512
|
-
raise TypeError, "
|
|
521
|
+
raise TypeError, "`:resolver_cache` must be a compatible resolver cache and implement `#resolve`, `#get`, `#set` and `#evict`"
|
|
513
522
|
end
|
|
514
523
|
end
|
|
515
524
|
|
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
|