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
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
|
data/doc/release_notes/1_7_1.md
CHANGED
|
@@ -17,5 +17,4 @@
|
|
|
17
17
|
* `:stream_bidi` plugin: fix internal state preventing bidi requests from being retried.
|
|
18
18
|
* `:stream_bidi` plugin: will only allow initial request body being passed using `:body` param (others, like `:json`, will raise an exception)
|
|
19
19
|
* https resolver: return `:idle` on `#state` calls when no connection is available (sometimes called in internal log messages).
|
|
20
|
-
* selector loop fix: when there are no selectables and an interval is passed, sleep instead of returning (thereby avoiding potential busy loop).
|
|
21
|
-
*
|
|
20
|
+
* selector loop fix: when there are no selectables and an interval is passed, sleep instead of returning (thereby avoiding potential busy loop).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 1.8.1
|
|
2
|
+
|
|
3
|
+
## Improvements
|
|
4
|
+
|
|
5
|
+
* the session will now eagerly write initial data to connections instead of waiting for I/O the first time (saves a syscall).
|
|
6
|
+
* removed all rescue blocks rescuing `StandardError` internally; this could interfere with usages of `httpx` inside `Timeout.timeout` blocks, which may emit an exception at any point, including in blocks where that exception could be rescued, leaving the session in a potentially irrecoverable state and looping.
|
|
7
|
+
* debug logs change the display of ids from 'tid:123, fid:123' to 'tid=123 fid:123'.
|
|
8
|
+
* and when using the `datadog` adapter, all related correlation ids (trace id, span id, etc) will also be added to the identifiers in the same format
|
|
9
|
+
* plugins built on top of the `:retries` plugin will skip the `retry_after` interval when certain conditions are met.
|
|
10
|
+
* `:persistent` plugin: request will be retried immediately, when the error happens after the keep alive interval expired.
|
|
11
|
+
* `:auth` plugin: request will be retried immediately if it failed due to a "401 Unauthorized Error".
|
|
12
|
+
* `:ssrf_filter` plugin: removed public/private references of exception message.
|
|
13
|
+
|
|
14
|
+
## Bugfixes
|
|
15
|
+
|
|
16
|
+
* `:auth` plugin: handle edge case where `:auth_header_expires_at` returns no value.
|
|
17
|
+
* `:digest_auth` plugin: do not retry if there was an issue building building the authorization header after the probe.
|
|
18
|
+
* `webmock` adapter: prevent mocked requests from opening a connection to the peer server that will not be used.
|
|
19
|
+
* clear and cancel request timers on completion or reset, ensuring that they won't be retained way past the point where the request is gone.
|
|
20
|
+
* connection: cancel and discard ping timer before closing (removes timer reference)
|
|
21
|
+
* connection: fix logic where purging-while-resending requests to a coalesced connection was leaving references in the old connection.
|
|
22
|
+
* moved an `on_body_chunk` callback to the `:callbacks` plugin, where it's used and expected to be defined.
|
|
23
|
+
* `http2`: avoid closing the connection on internal errors which are supposed to be specific to a request, such as the max headers or max response body size checks.
|
|
24
|
+
* system resolver will use an internal exception when using `Timeout.timeout` on resolutions to avoid conflicts with the default timeout exception class.
|
|
25
|
+
* prevent edge case where `#alpn_protocol` was being called on a `TCPSocket`.
|
|
26
|
+
* fixed v1.8.0 regression causing connection to loop when an error happens while connecting.
|
|
27
|
+
* resolver cache: fixed cache eviction logic, where alias answers weren't being evicted, causing resolutions to stall after a certain number of resolutions.
|
|
@@ -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.
|
|
@@ -185,6 +185,13 @@ module Datadog::Tracing
|
|
|
185
185
|
end
|
|
186
186
|
end
|
|
187
187
|
|
|
188
|
+
# patches httpx debug logs to include datadog correlation ids
|
|
189
|
+
module LoggablePatch
|
|
190
|
+
def log_identifiers
|
|
191
|
+
"#{super} #{Datadog::Tracing.log_correlation}"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
188
195
|
module Configuration
|
|
189
196
|
# Default settings for httpx
|
|
190
197
|
#
|
|
@@ -297,6 +304,8 @@ module Datadog::Tracing
|
|
|
297
304
|
|
|
298
305
|
::HTTPX.send(:remove_const, :Session)
|
|
299
306
|
::HTTPX.send(:const_set, :Session, datadog_session.class)
|
|
307
|
+
|
|
308
|
+
::HTTPX::Loggable.singleton_class.prepend(LoggablePatch)
|
|
300
309
|
end
|
|
301
310
|
end
|
|
302
311
|
|
|
@@ -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
|
|
|
@@ -155,6 +156,12 @@ module WebMock
|
|
|
155
156
|
raise WebMock::NetConnectNotAllowedError, request_signature
|
|
156
157
|
end
|
|
157
158
|
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def connect
|
|
163
|
+
super unless @mocked
|
|
164
|
+
end
|
|
158
165
|
end
|
|
159
166
|
end
|
|
160
167
|
|
data/lib/httpx/buffer.rb
CHANGED
|
@@ -29,7 +29,7 @@ module HTTPX
|
|
|
29
29
|
@version = [1, 1]
|
|
30
30
|
@pending = []
|
|
31
31
|
@requests = []
|
|
32
|
-
@request = nil
|
|
32
|
+
@callbacks = @request = nil
|
|
33
33
|
@handshake_completed = @pipelining = false
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -162,18 +162,12 @@ module HTTPX
|
|
|
162
162
|
|
|
163
163
|
return unless request
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
request.log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
|
|
165
|
+
request.log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
|
|
166
|
+
request.log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
|
|
168
167
|
|
|
169
|
-
|
|
168
|
+
response = request.response
|
|
170
169
|
|
|
171
|
-
|
|
172
|
-
rescue StandardError => e
|
|
173
|
-
error_response = ErrorResponse.new(request, e)
|
|
174
|
-
request.response = error_response
|
|
175
|
-
dispatch(request)
|
|
176
|
-
end
|
|
170
|
+
response << chunk
|
|
177
171
|
end
|
|
178
172
|
|
|
179
173
|
def on_complete
|
|
@@ -416,7 +410,7 @@ module HTTPX
|
|
|
416
410
|
end
|
|
417
411
|
|
|
418
412
|
def capitalized(field)
|
|
419
|
-
UPCASED
|
|
413
|
+
UPCASED.fetch(field) { field.split("-").map(&:capitalize).join("-") }
|
|
420
414
|
end
|
|
421
415
|
end
|
|
422
416
|
end
|
|
@@ -31,12 +31,14 @@ 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 = []
|
|
37
38
|
@streams = {}
|
|
38
39
|
@drains = {}
|
|
39
40
|
@pings = []
|
|
41
|
+
@streams_to_close_after_receive = []
|
|
40
42
|
@buffer = buffer
|
|
41
43
|
@handshake_completed = false
|
|
42
44
|
@wait_for_handshake = @settings.key?(:wait_for_handshake) ? @settings.delete(:wait_for_handshake) : true
|
|
@@ -52,7 +54,7 @@ module HTTPX
|
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def interests
|
|
55
|
-
if @connection.
|
|
57
|
+
if @connection.closed?
|
|
56
58
|
return unless @handshake_completed
|
|
57
59
|
|
|
58
60
|
return if @buffer.empty?
|
|
@@ -93,7 +95,7 @@ module HTTPX
|
|
|
93
95
|
end
|
|
94
96
|
|
|
95
97
|
def close
|
|
96
|
-
unless @connection.
|
|
98
|
+
unless @connection.closed?
|
|
97
99
|
@connection.goaway
|
|
98
100
|
emit(:timeout, @options.timeout[:close_handshake_timeout])
|
|
99
101
|
end
|
|
@@ -101,7 +103,7 @@ module HTTPX
|
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
def empty?
|
|
104
|
-
@connection.
|
|
106
|
+
@connection.closed? || @streams.empty?
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
def exhausted?
|
|
@@ -110,6 +112,12 @@ module HTTPX
|
|
|
110
112
|
|
|
111
113
|
def <<(data)
|
|
112
114
|
@connection << data
|
|
115
|
+
|
|
116
|
+
while (stream, request, error = @streams_to_close_after_receive.shift)
|
|
117
|
+
# these streams were marked for cancellation due to errors found while processing the
|
|
118
|
+
# data received by the peer.
|
|
119
|
+
emit_stream_error(stream, request, error)
|
|
120
|
+
end
|
|
113
121
|
end
|
|
114
122
|
|
|
115
123
|
def send(request, head = false)
|
|
@@ -128,7 +136,7 @@ module HTTPX
|
|
|
128
136
|
rescue ::HTTP2::Error::StreamLimitExceeded
|
|
129
137
|
@pending.unshift(request)
|
|
130
138
|
false
|
|
131
|
-
rescue
|
|
139
|
+
rescue ::HTTP2::Error::Error, ArgumentError => e
|
|
132
140
|
emit(:error, request, e)
|
|
133
141
|
end
|
|
134
142
|
|
|
@@ -333,6 +341,8 @@ module HTTPX
|
|
|
333
341
|
@streams[request] = stream
|
|
334
342
|
|
|
335
343
|
handle(request, stream) if request.expects?
|
|
344
|
+
rescue HTTPX::Error => e
|
|
345
|
+
@streams_to_close_after_receive << [stream, request, e]
|
|
336
346
|
end
|
|
337
347
|
|
|
338
348
|
def on_stream_trailers(stream, request, response, h)
|
|
@@ -345,7 +355,18 @@ module HTTPX
|
|
|
345
355
|
def on_stream_data(stream, request, data)
|
|
346
356
|
request.log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
|
|
347
357
|
request.log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact_body(data.inspect)}" }
|
|
358
|
+
|
|
359
|
+
return unless request.response
|
|
360
|
+
|
|
348
361
|
request.response << data
|
|
362
|
+
rescue HTTPX::Error => e
|
|
363
|
+
if stream.state == :closing
|
|
364
|
+
# there won't be any more chunks to process after this one.
|
|
365
|
+
emit_stream_error(stream, request, e)
|
|
366
|
+
else
|
|
367
|
+
# defer until the last chunk from the payload is processed.
|
|
368
|
+
@streams_to_close_after_receive << [stream, request, e]
|
|
369
|
+
end
|
|
349
370
|
end
|
|
350
371
|
|
|
351
372
|
def on_stream_refuse(stream, request, error)
|
|
@@ -381,11 +402,12 @@ module HTTPX
|
|
|
381
402
|
emit(:response, request, response)
|
|
382
403
|
end
|
|
383
404
|
else
|
|
384
|
-
response = request.response
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
405
|
+
if (response = request.response)
|
|
406
|
+
if response.is_a?(Response) && response.status == 421
|
|
407
|
+
emit(:error, request, :http_1_1_required)
|
|
408
|
+
else
|
|
409
|
+
emit(:response, request, response)
|
|
410
|
+
end
|
|
389
411
|
end
|
|
390
412
|
end
|
|
391
413
|
send(@pending.shift) unless @pending.empty?
|
|
@@ -411,7 +433,7 @@ module HTTPX
|
|
|
411
433
|
end
|
|
412
434
|
|
|
413
435
|
def on_close(_last_frame, error, _payload)
|
|
414
|
-
is_connection_closed = @connection.
|
|
436
|
+
is_connection_closed = @connection.closed?
|
|
415
437
|
if error
|
|
416
438
|
@buffer.clear if is_connection_closed
|
|
417
439
|
case error
|
|
@@ -509,6 +531,12 @@ module HTTPX
|
|
|
509
531
|
emit(:pong)
|
|
510
532
|
end
|
|
511
533
|
|
|
534
|
+
def emit_stream_error(stream, request, error)
|
|
535
|
+
teardown(request)
|
|
536
|
+
stream.close
|
|
537
|
+
emit(:error, request, error)
|
|
538
|
+
end
|
|
539
|
+
|
|
512
540
|
def teardown(request = nil)
|
|
513
541
|
if request
|
|
514
542
|
@drains.delete(request)
|
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)
|
|
@@ -161,15 +185,17 @@ module HTTPX
|
|
|
161
185
|
end
|
|
162
186
|
|
|
163
187
|
def purge_pending(&block)
|
|
164
|
-
pendings = []
|
|
165
188
|
if @parser
|
|
166
189
|
pending = @parser.pending
|
|
167
190
|
@inflight -= pending.size
|
|
168
|
-
|
|
191
|
+
pending.reject! do |req|
|
|
192
|
+
block.call(req)
|
|
193
|
+
true
|
|
194
|
+
end
|
|
169
195
|
end
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
196
|
+
@pending.reject! do |req|
|
|
197
|
+
block.call(req)
|
|
198
|
+
true
|
|
173
199
|
end
|
|
174
200
|
end
|
|
175
201
|
|
|
@@ -203,7 +229,7 @@ module HTTPX
|
|
|
203
229
|
return @parser.interests if @parser
|
|
204
230
|
|
|
205
231
|
nil
|
|
206
|
-
rescue
|
|
232
|
+
rescue Error => e
|
|
207
233
|
on_error(e)
|
|
208
234
|
nil
|
|
209
235
|
end
|
|
@@ -215,6 +241,8 @@ module HTTPX
|
|
|
215
241
|
def call
|
|
216
242
|
case @state
|
|
217
243
|
when :idle
|
|
244
|
+
return if no_more_requests?
|
|
245
|
+
|
|
218
246
|
connect
|
|
219
247
|
|
|
220
248
|
# when opening the tcp or ssl socket fails
|
|
@@ -222,7 +250,7 @@ module HTTPX
|
|
|
222
250
|
|
|
223
251
|
consume
|
|
224
252
|
when :closed
|
|
225
|
-
return if
|
|
253
|
+
return if no_more_requests?
|
|
226
254
|
|
|
227
255
|
# there are pending requests to send, restart the state machine.
|
|
228
256
|
idling
|
|
@@ -247,10 +275,14 @@ module HTTPX
|
|
|
247
275
|
consume
|
|
248
276
|
end
|
|
249
277
|
nil
|
|
250
|
-
rescue
|
|
278
|
+
rescue Errno::ECONNRESET,
|
|
279
|
+
Errno::EINVAL,
|
|
280
|
+
SocketError,
|
|
281
|
+
IOError,
|
|
282
|
+
TLSError => e
|
|
251
283
|
@write_buffer.clear
|
|
252
284
|
on_io_error(e)
|
|
253
|
-
rescue
|
|
285
|
+
rescue Error => e
|
|
254
286
|
@write_buffer.clear
|
|
255
287
|
on_error(e)
|
|
256
288
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
@@ -258,6 +290,10 @@ module HTTPX
|
|
|
258
290
|
raise e
|
|
259
291
|
end
|
|
260
292
|
|
|
293
|
+
def initial_call
|
|
294
|
+
call
|
|
295
|
+
end
|
|
296
|
+
|
|
261
297
|
def close
|
|
262
298
|
transition(:active) if @state == :inactive
|
|
263
299
|
|
|
@@ -314,6 +350,11 @@ module HTTPX
|
|
|
314
350
|
# (example: HTTP/1 parser disabling pipelining)
|
|
315
351
|
return if @state == :idle && @pending.any?
|
|
316
352
|
|
|
353
|
+
if @ping_timer
|
|
354
|
+
@ping_timer.cancel
|
|
355
|
+
@ping_timer = nil
|
|
356
|
+
end
|
|
357
|
+
|
|
317
358
|
parser = @parser
|
|
318
359
|
|
|
319
360
|
if parser && parser.respond_to?(:max_concurrent_requests)
|
|
@@ -324,7 +365,6 @@ module HTTPX
|
|
|
324
365
|
end
|
|
325
366
|
|
|
326
367
|
transition(:closing)
|
|
327
|
-
|
|
328
368
|
transition(:closed)
|
|
329
369
|
end
|
|
330
370
|
|
|
@@ -433,6 +473,11 @@ module HTTPX
|
|
|
433
473
|
|
|
434
474
|
def on_io_error(e)
|
|
435
475
|
on_error(e)
|
|
476
|
+
|
|
477
|
+
# do not force close if parser resets the connection.
|
|
478
|
+
# can happen i.e. when HTTP/1.1 pipelining is disabled.
|
|
479
|
+
return if @state == :idle && @pending.any?
|
|
480
|
+
|
|
436
481
|
force_close(true)
|
|
437
482
|
end
|
|
438
483
|
|
|
@@ -456,7 +501,7 @@ module HTTPX
|
|
|
456
501
|
reset
|
|
457
502
|
end
|
|
458
503
|
|
|
459
|
-
# :
|
|
504
|
+
# simplecov:disable
|
|
460
505
|
def inspect
|
|
461
506
|
"#<#{self.class}:#{object_id} " \
|
|
462
507
|
"@origin=#{@origin} " \
|
|
@@ -464,7 +509,7 @@ module HTTPX
|
|
|
464
509
|
"@pending=#{@pending.size} " \
|
|
465
510
|
"@io=#{@io}>"
|
|
466
511
|
end
|
|
467
|
-
# :
|
|
512
|
+
# simplecov:enable
|
|
468
513
|
|
|
469
514
|
private
|
|
470
515
|
|
|
@@ -490,7 +535,7 @@ module HTTPX
|
|
|
490
535
|
# * the number of pending requests
|
|
491
536
|
# * the number of inflight requests
|
|
492
537
|
# * whether the write buffer has bytes (i.e. for close handshake)
|
|
493
|
-
if
|
|
538
|
+
if no_more_requests? && @write_buffer.empty?
|
|
494
539
|
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
495
540
|
|
|
496
541
|
# terminate if an altsvc connection has been established
|
|
@@ -540,7 +585,7 @@ module HTTPX
|
|
|
540
585
|
break if @state == :closing || @state == :closed
|
|
541
586
|
|
|
542
587
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
543
|
-
if
|
|
588
|
+
if no_more_requests? && @write_buffer.empty? # rubocop:disable Style/Next
|
|
544
589
|
no_more_requests_loop_check if @parser && @parser.pending.any?
|
|
545
590
|
|
|
546
591
|
# terminate if an altsvc connection has been established
|
|
@@ -753,14 +798,19 @@ module HTTPX
|
|
|
753
798
|
Errno::ECONNRESET,
|
|
754
799
|
Errno::EADDRNOTAVAIL,
|
|
755
800
|
Errno::EHOSTUNREACH,
|
|
756
|
-
Errno::EINVAL,
|
|
757
801
|
Errno::ENETUNREACH,
|
|
802
|
+
Errno::EHOSTDOWN,
|
|
803
|
+
Errno::ENETDOWN,
|
|
804
|
+
Errno::EINVAL,
|
|
758
805
|
Errno::EPIPE,
|
|
759
806
|
Errno::ENOENT,
|
|
760
807
|
SocketError,
|
|
761
808
|
IOError => e
|
|
762
809
|
on_connect_error(e)
|
|
763
|
-
rescue TLSError,
|
|
810
|
+
rescue TLSError,
|
|
811
|
+
Errno::ETIMEDOUT,
|
|
812
|
+
::HTTP2::Error::ProtocolError,
|
|
813
|
+
::HTTP2::Error::HandshakeError => e
|
|
764
814
|
# connect errors, exit gracefully
|
|
765
815
|
handle_error(e)
|
|
766
816
|
handle_connect_error(e) if connecting?
|
|
@@ -957,9 +1007,12 @@ module HTTPX
|
|
|
957
1007
|
|
|
958
1008
|
parser.ping
|
|
959
1009
|
|
|
1010
|
+
return unless parser.waiting_for_ping?
|
|
1011
|
+
|
|
960
1012
|
ping_timeout = @options.timeout[:ping_timeout]
|
|
961
1013
|
|
|
962
1014
|
@ping_timer = @current_selector.after(ping_timeout) do
|
|
1015
|
+
log(level: 3) { "ping timeout expired..." }
|
|
963
1016
|
error = PingTimeoutError.new(ping_timeout, "Timed out after #{ping_timeout} seconds")
|
|
964
1017
|
on_error(error)
|
|
965
1018
|
end
|
|
@@ -986,6 +1039,11 @@ module HTTPX
|
|
|
986
1039
|
"along with debug logs"
|
|
987
1040
|
end
|
|
988
1041
|
|
|
1042
|
+
# true when there are no more pending nor inflight (in parser) requests
|
|
1043
|
+
def no_more_requests?
|
|
1044
|
+
@pending.empty? && @inflight.zero?
|
|
1045
|
+
end
|
|
1046
|
+
|
|
989
1047
|
# recover internal state and emit all relevant error responses when +error+ was raised.
|
|
990
1048
|
# this takes an optiona +request+ which may have already been handled and can be opted out
|
|
991
1049
|
# in the state recovery process.
|
|
@@ -1099,13 +1157,14 @@ module HTTPX
|
|
|
1099
1157
|
end
|
|
1100
1158
|
|
|
1101
1159
|
timer = selector.after(timeout, callback)
|
|
1102
|
-
|
|
1160
|
+
timer.label = label
|
|
1161
|
+
request.active_timeouts << timer
|
|
1103
1162
|
|
|
1104
1163
|
Array(finish_events).each do |event|
|
|
1105
1164
|
# clean up request timeouts if the connection errors out
|
|
1106
1165
|
request.set_timeout_callback(event) do
|
|
1107
1166
|
timer.cancel
|
|
1108
|
-
request.active_timeouts.delete(
|
|
1167
|
+
request.active_timeouts.delete(timer)
|
|
1109
1168
|
end
|
|
1110
1169
|
end
|
|
1111
1170
|
end
|
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/headers.rb
CHANGED
|
@@ -140,12 +140,12 @@ module HTTPX
|
|
|
140
140
|
@headers.to_s
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
-
# :
|
|
143
|
+
# simplecov:disable
|
|
144
144
|
def inspect
|
|
145
145
|
"#<#{self.class}:#{object_id} " \
|
|
146
146
|
"#{to_hash.inspect}>"
|
|
147
147
|
end
|
|
148
|
-
# :
|
|
148
|
+
# simplecov:enable
|
|
149
149
|
|
|
150
150
|
# this is internal API and doesn't abide to other public API
|
|
151
151
|
# guarantees, like downcasing strings.
|