httpx 1.9.0 → 1.9.1
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_1.md +1 -1
- data/doc/release_notes/1_8_2.md +4 -4
- data/doc/release_notes/1_8_3.md +3 -5
- data/doc/release_notes/1_8_4.md +1 -1
- data/doc/release_notes/1_9_0.md +7 -7
- data/doc/release_notes/1_9_1.md +32 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +14 -1
- data/lib/httpx/connection/http1.rb +24 -17
- data/lib/httpx/connection/http2.rb +108 -29
- data/lib/httpx/connection.rb +158 -162
- data/lib/httpx/io/tcp.rb +24 -7
- data/lib/httpx/plugins/aws_sigv4.rb +0 -1
- data/lib/httpx/plugins/cache.rb +1 -1
- data/lib/httpx/plugins/callbacks.rb +1 -0
- data/lib/httpx/plugins/content_digest.rb +3 -5
- data/lib/httpx/plugins/expect.rb +8 -1
- data/lib/httpx/plugins/fiber_concurrency.rb +56 -19
- data/lib/httpx/plugins/h2c.rb +12 -11
- data/lib/httpx/plugins/hedging.rb +2 -2
- data/lib/httpx/plugins/ntlm_auth.rb +4 -3
- data/lib/httpx/plugins/ntlm_v2_auth.rb +4 -3
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -16
- data/lib/httpx/plugins/proxy/socks5.rb +4 -4
- data/lib/httpx/plugins/proxy.rb +8 -15
- data/lib/httpx/plugins/query.rb +1 -0
- data/lib/httpx/plugins/stream.rb +12 -10
- data/lib/httpx/plugins/stream_bidi.rb +4 -0
- data/lib/httpx/plugins/tracing.rb +62 -21
- data/lib/httpx/plugins/upgrade/h2.rb +2 -2
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/https.rb +3 -2
- data/lib/httpx/resolver/native.rb +19 -9
- data/lib/httpx/resolver/resolver.rb +2 -2
- data/lib/httpx/resolver/system.rb +4 -0
- data/lib/httpx/resolver.rb +5 -5
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +108 -7
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +4 -2
- data/sig/connection/http2.rbs +8 -2
- data/sig/connection.rbs +11 -6
- data/sig/io/tcp.rbs +5 -0
- data/sig/plugins/expect.rbs +6 -0
- data/sig/plugins/fiber_concurrency.rbs +10 -1
- data/sig/plugins/proxy/http.rbs +3 -1
- data/sig/plugins/stream_bidi.rbs +0 -2
- data/sig/plugins/tracing.rbs +10 -2
- data/sig/resolver/https.rbs +2 -0
- data/sig/resolver/native.rbs +3 -0
- data/sig/resolver/resolver.rbs +1 -1
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +1 -1
- data/sig/selector.rbs +15 -1
- data/sig/session.rbs +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8eaea3d273dcba77ecae2abaf1979fa7ebbfddadf5c8f67b71636aac594b12e6
|
|
4
|
+
data.tar.gz: d4e50760994c7dff6d7ae963427f721ce349add9455055e01fc7074b2a9350ed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ae04971cb261d34dd72b1cd724e66e57321909e087b122357ab4506dfb4955603fc3c2ea54c9e34e9e9dcf5484aa347c89ad8c72a9fe94db26706c6850fc5442
|
|
7
|
+
data.tar.gz: a1e5ed666d7419b80da97449150ee67c95d2b1b9855e77060fff123aeaeaf268fb4caafde3242c9421d9cd042e5df14a088ecad30c837449a9fdee2b3376f6cb
|
data/doc/release_notes/1_8_1.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
## Bugfixes
|
|
15
15
|
|
|
16
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
|
|
17
|
+
* `:digest_auth` plugin: do not retry if there was an issue building the authorization header after the probe.
|
|
18
18
|
* `webmock` adapter: prevent mocked requests from opening a connection to the peer server that will not be used.
|
|
19
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
20
|
* connection: cancel and discard ping timer before closing (removes timer reference)
|
data/doc/release_notes/1_8_2.md
CHANGED
|
@@ -11,7 +11,7 @@ session = HTTPX.with(unknown_option: 1) # will raise a HTTPX::Error: unknown opt
|
|
|
11
11
|
|
|
12
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
13
|
* Add missing `response` attribute reader to `HTTPX::RequestTimeoutError`.
|
|
14
|
-
* Improve correctness of calling `#dup` on requests and connections.
|
|
14
|
+
* Improve the correctness of calling `#dup` on requests and connections.
|
|
15
15
|
|
|
16
16
|
## Bugfixes
|
|
17
17
|
|
|
@@ -21,10 +21,10 @@ session = HTTPX.with(unknown_option: 1) # will raise a HTTPX::Error: unknown opt
|
|
|
21
21
|
* `:retries` plugin: do not retry on any HTTP/2 frame error (only on GOAWAY or PING frame errors).
|
|
22
22
|
* `:persistent` plugin: do not decrement request remaining retries on ping or GOAWAY frame errors.
|
|
23
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
|
|
25
|
-
* datadog adapter: the connection initialization time is now set only on connect or first request received, the reason, making it more accurate.
|
|
24
|
+
* `:follow_redirects` plugin: fixed issue when a timeout error would be raised but only bubbles 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 being, making it more accurate.
|
|
26
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
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
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
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
|
|
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.
|
data/doc/release_notes/1_8_3.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
## Bugfixes
|
|
4
4
|
|
|
5
|
-
options print a warning instead of raising an error on unknown option
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
the format of options messages (exceptions and warnings) also slightly changes to wrap the option in quotes, for proper highlighting.
|
|
5
|
+
* options print a warning instead of raising an error on unknown option
|
|
6
|
+
* 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.
|
|
7
|
+
* The format of option messages (exceptions and warnings) also slightly changes to wrap the option in quotes, for proper highlighting.
|
data/doc/release_notes/1_8_4.md
CHANGED
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
* `:retries` plugin: resend requests which were piped to a closed connection internally.
|
|
11
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
12
|
* fixed compatibility issue starting with the `json` gem 3.0.0, when calling `HTTPX::Response#json`.
|
|
13
|
-
* avoid
|
|
13
|
+
* avoid bookkeeping 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`).
|
data/doc/release_notes/1_9_0.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
### `:hedging` plugin
|
|
6
6
|
|
|
7
|
-
The `:hedging` implements a strategy called "request hedging", whereby a request is sent
|
|
7
|
+
The `:hedging` plugin implements a strategy called "request hedging", whereby a request is sent two times, over different connections, and picking up whichever arrives first.
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
10
|
http = HTTP.plugin(:hedging)
|
|
@@ -12,7 +12,7 @@ http.get("http://www.example.com")
|
|
|
12
12
|
# sent two times, only the fastest one comes back...
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
You can read more about it in https://gitlab.com/os85/httpx/wikis/Hedging .
|
|
16
16
|
|
|
17
17
|
### `:proxy/http` plugin: new options `:proxy_headers` and `:enable_proxy_tunnel`
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ The `:proxy/http` plugin (which gets automatically loaded by the `:proxy` plugin
|
|
|
20
20
|
|
|
21
21
|
#### `:proxy_headers`
|
|
22
22
|
|
|
23
|
-
Can be passed a separate set of HTTP headers, which are going to be sent to the proxy when establishing the
|
|
23
|
+
Can be passed a separate set of HTTP headers, which are going to be sent to the proxy when establishing the CONNECT proxy tunnel.
|
|
24
24
|
|
|
25
25
|
```ruby
|
|
26
26
|
http = HTTPX.plugin(:proxy).with(proxy_headers: { "x-proxy-1" => "foo" }, headers: { "x-header-1" => "bar" }).get("https://example.com")
|
|
@@ -48,10 +48,10 @@ When set to `true`, it forces the establishment of a CONNECT proxy tunnel before
|
|
|
48
48
|
|
|
49
49
|
## Bugfixes
|
|
50
50
|
|
|
51
|
-
* `:auth` plugin: fixed clause inverted check, which caused
|
|
52
|
-
* `:auth` plugin: take auth_header_expires_at into account when deciding to regenerate on retry (token may have
|
|
51
|
+
* `:auth` plugin: fixed clause inverted check, which caused a new token to always be generated on retry.
|
|
52
|
+
* `:auth` plugin: take auth_header_expires_at into account when deciding to regenerate on retry (token may have expired by the time the request will be retried).
|
|
53
53
|
* `:tracing` plugin: fixed request initial time calculation in order to take into account the time to wait for a PING correctly.
|
|
54
|
-
* `:tracing` plugin: fixed request initial time calculation for pending requests which were waiting for the connect phase and errored
|
|
54
|
+
* `:tracing` plugin: fixed request initial time calculation for pending requests which were waiting for the connect phase and errored meanwhile.
|
|
55
55
|
* `:proxy/http` plugin: `:headers` will not be sent anymore to the proxy when establishing the CONNECT proxy tunnel.
|
|
56
56
|
* socket connect timeout bookkeeping is fixed, where in a multi request scenario, connect timeout errors shall not be raised too early if an error happens before the remainder of connections elapses.
|
|
57
57
|
* `:proxy` plugin: allow proxy params to be frozen, which also fixes an issue where proxies identified as unhealthy by a previous request, would still be retried on for different hostnames.
|
|
@@ -72,4 +72,4 @@ When set to `true`, it forces the establishment of a CONNECT proxy tunnel before
|
|
|
72
72
|
|
|
73
73
|
## Chore
|
|
74
74
|
|
|
75
|
-
* Emit warning message whenever connection is deactivated with pending requests; the message contains internal state which should help debug it.
|
|
75
|
+
* Emit a warning message whenever the connection is deactivated with pending requests; the message contains an internal state which should help debug it.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 1.9.1
|
|
2
|
+
|
|
3
|
+
# Improvements
|
|
4
|
+
|
|
5
|
+
* `:connect_timeout` will be split across the IP addresses available for the hostname. The connection phase will then loop on the list, and on each connect timeout, the next IP will tried; the connect timeout error will then only be raised if no healthy connections could be established to any of the IPs. This is the same heuristic used by curl v7.
|
|
6
|
+
* Connections and parsers share the same pending requests queue: before, each had their own, and requests could fly across them depending on the several edge cases, thereby making it needlessly complex and wasteful. Also, when moving requests is necessary, they can short-circuit more efficiently.
|
|
7
|
+
* Handled quoted charset property when parsing the "content-type" header in the response.
|
|
8
|
+
* http1: Support peer server keep alive timeout (exposed via a response header like "Keep-Alive: timeout=10"), which overrides the client (or the default) `:keep_alive_timeout` setting.
|
|
9
|
+
* http2: during the consumption loop, handle more pending requests when the window allows it.
|
|
10
|
+
* several improvements on connection read/write consumption handling which save a few socket read/write syscalls.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Bugfixes
|
|
14
|
+
|
|
15
|
+
* fix the json decoding integration with `oj`, which was using the `Oj.load` function in an unsafe manner, which `oj` documents. It now uses the `:strict` mode by default.
|
|
16
|
+
* when doing Happy Eyeballs v2, do not accidentally coalesce sibling connections.
|
|
17
|
+
* on session termination, ensure that idle connections are closed, something which may happen when an `alt-svc` response header creates a new connection, but no subsequent requests are sent its way.
|
|
18
|
+
* fix race condition when terminating a selector, where its connections were checked on its state after termination; because terminated connections get checked back into the pool, they may be checked out before the state check happens. This fixes it by checking its current selector against itself.
|
|
19
|
+
* ensure that the selector only calls the error handling once for the same connection.
|
|
20
|
+
* request timeouts are now only set when the parser accepts them, and guaranteed only once.
|
|
21
|
+
* when an operation timeout occurs, do not raise it on connections for which one wasn't set.
|
|
22
|
+
* native resolver: fix DNS query retry handling due to a confusion about using `Enumerable.first` and `Enumerable.find`, and the former silently ignoring passed blocks.
|
|
23
|
+
* native resolver: discard received answers for DNS queries which have since been dropped due to a timeout.
|
|
24
|
+
* native resolver: try out the next nameserver in case the prior was unreachable.
|
|
25
|
+
* http-2: keep processing unfinished streams when receiving a GOAWAY with a last stream id pointing to streams that haven't been finished.
|
|
26
|
+
* `faraday` adapter: always load the `OnData` plugin, as a streaming request (which uses the `.on_data` Faraday API) may be preceded by a non-streaming request to fetch metadata (a pattern observed when using the google cloud storage ruby SDK).
|
|
27
|
+
* `ntml` and `ntlmv2` plugins: when used to send multiple requests, should return an array of responses.
|
|
28
|
+
* `oauth` plugin: fix wrong method call.
|
|
29
|
+
* `cache` plugin: skip cache flow for error responses.
|
|
30
|
+
* `proxy` plugin: make the class used for the CONNECT proxy request inherit all functionality from other loaded plugins.
|
|
31
|
+
* `fiber_concurrency`: fixed some corner cases where the connection could loop indefinitely or become corrupt.
|
|
32
|
+
* `tracing`: fixing request start time bookkeeping.
|
|
@@ -33,7 +33,7 @@ module Faraday
|
|
|
33
33
|
|
|
34
34
|
@connection = @connection.plugin(:proxy).with(proxy: proxy_options)
|
|
35
35
|
end
|
|
36
|
-
@connection = @connection.plugin(OnDataPlugin)
|
|
36
|
+
@connection = @connection.plugin(OnDataPlugin)
|
|
37
37
|
|
|
38
38
|
@connection = @config_block.call(@connection) || @connection if @config_block
|
|
39
39
|
@connection
|
|
@@ -20,11 +20,21 @@ module WebMock
|
|
|
20
20
|
WebMock::RequestSignature.new(
|
|
21
21
|
request.verb.downcase.to_sym,
|
|
22
22
|
uri.to_s,
|
|
23
|
-
body: request
|
|
23
|
+
body: build_webmock_request_body(request),
|
|
24
24
|
headers: request.headers.to_h
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def build_webmock_request_body(request)
|
|
29
|
+
body = request.body
|
|
30
|
+
|
|
31
|
+
return body.to_s unless body.respond_to?(:read)
|
|
32
|
+
|
|
33
|
+
body.read.to_s.tap do
|
|
34
|
+
body.rewind
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
28
38
|
def build_webmock_response(_request, response)
|
|
29
39
|
webmock_response = WebMock::Response.new
|
|
30
40
|
webmock_response.status = [response.status, HTTP_REASONS[response.status]]
|
|
@@ -61,6 +71,9 @@ module WebMock
|
|
|
61
71
|
def do_init_connection(connection, selector)
|
|
62
72
|
super
|
|
63
73
|
|
|
74
|
+
# skip for connections generated by HEv2 logic.
|
|
75
|
+
return if connection.family
|
|
76
|
+
|
|
64
77
|
connection.once(:unmock_connection) do
|
|
65
78
|
next unless connection.current_session == self
|
|
66
79
|
|
|
@@ -20,14 +20,14 @@ module HTTPX
|
|
|
20
20
|
|
|
21
21
|
attr_accessor :max_concurrent_requests
|
|
22
22
|
|
|
23
|
-
def initialize(buffer, options)
|
|
23
|
+
def initialize(buffer, pending, options)
|
|
24
24
|
@options = options
|
|
25
25
|
@max_concurrent_requests = @options.max_concurrent_requests || MAX_REQUESTS
|
|
26
26
|
@max_requests = @options.max_requests
|
|
27
27
|
@parser = Parser::HTTP1.new(self, options.max_response_headers, options.max_response_header_value_size)
|
|
28
28
|
@buffer = buffer
|
|
29
29
|
@version = [1, 1]
|
|
30
|
-
@pending =
|
|
30
|
+
@pending = pending
|
|
31
31
|
@requests = []
|
|
32
32
|
@callbacks = @request = nil
|
|
33
33
|
@handshake_completed = @pipelining = false
|
|
@@ -51,21 +51,28 @@ module HTTPX
|
|
|
51
51
|
@max_requests = @options.max_requests || MAX_REQUESTS
|
|
52
52
|
@parser.reset!
|
|
53
53
|
@handshake_completed = false
|
|
54
|
-
reset_requests
|
|
55
54
|
end
|
|
56
55
|
|
|
56
|
+
# move incompleted requests back to pending queue.
|
|
57
57
|
def reset_requests
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
completed_idx = 0
|
|
59
|
+
@requests.each_with_index do |request, idx|
|
|
60
|
+
if request.response
|
|
61
|
+
completed_idx = idx + 1
|
|
62
|
+
next
|
|
63
|
+
end
|
|
60
64
|
|
|
61
65
|
request.transition(:idle)
|
|
62
|
-
@pending.unshift(request)
|
|
63
66
|
end
|
|
67
|
+
# drop completed requests
|
|
68
|
+
@requests.shift(completed_idx)
|
|
69
|
+
@pending.unshift(*@requests)
|
|
64
70
|
@requests.clear
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
def close
|
|
68
74
|
reset
|
|
75
|
+
reset_requests
|
|
69
76
|
emit(:close)
|
|
70
77
|
end
|
|
71
78
|
|
|
@@ -88,14 +95,16 @@ module HTTPX
|
|
|
88
95
|
@parser << data
|
|
89
96
|
end
|
|
90
97
|
|
|
91
|
-
def send(request, head = false)
|
|
98
|
+
def send(request, head = false) # rubocop:disable Naming/PredicateMethod
|
|
92
99
|
unless @max_requests.positive? && @max_requests > @requests.size
|
|
93
100
|
head ? @pending.unshift(request) : @pending << request
|
|
94
|
-
return
|
|
101
|
+
return false
|
|
95
102
|
end
|
|
96
103
|
|
|
97
104
|
@requests << request
|
|
98
105
|
@pipelining = @max_concurrent_requests > 1 && @requests.size > 1
|
|
106
|
+
emit(:request_accepted, request)
|
|
107
|
+
true
|
|
99
108
|
end
|
|
100
109
|
|
|
101
110
|
def consume
|
|
@@ -195,8 +204,7 @@ module HTTPX
|
|
|
195
204
|
manage_connection(request, response)
|
|
196
205
|
|
|
197
206
|
if exhausted?
|
|
198
|
-
|
|
199
|
-
@requests.clear
|
|
207
|
+
reset_requests
|
|
200
208
|
|
|
201
209
|
emit(:exhausted)
|
|
202
210
|
elsif @requests.any?
|
|
@@ -237,13 +245,16 @@ module HTTPX
|
|
|
237
245
|
def ping
|
|
238
246
|
reset
|
|
239
247
|
emit(:reset)
|
|
240
|
-
emit(:exhausted)
|
|
241
248
|
end
|
|
242
249
|
|
|
243
250
|
def waiting_for_ping?
|
|
244
251
|
false
|
|
245
252
|
end
|
|
246
253
|
|
|
254
|
+
def no_more_requests?
|
|
255
|
+
@requests.empty?
|
|
256
|
+
end
|
|
257
|
+
|
|
247
258
|
private
|
|
248
259
|
|
|
249
260
|
def manage_connection(request, response)
|
|
@@ -252,8 +263,7 @@ module HTTPX
|
|
|
252
263
|
when /keep-alive/i
|
|
253
264
|
if @handshake_completed
|
|
254
265
|
if @max_requests.zero?
|
|
255
|
-
|
|
256
|
-
@requests.clear
|
|
266
|
+
reset_requests
|
|
257
267
|
emit(:exhausted)
|
|
258
268
|
end
|
|
259
269
|
return
|
|
@@ -267,10 +277,7 @@ module HTTPX
|
|
|
267
277
|
end]
|
|
268
278
|
@max_requests = parameters["max"].to_i - 1 if parameters.key?("max")
|
|
269
279
|
|
|
270
|
-
if parameters.key?("timeout")
|
|
271
|
-
keep_alive_timeout = parameters["timeout"].to_i
|
|
272
|
-
emit(:timeout, keep_alive_timeout)
|
|
273
|
-
end
|
|
280
|
+
emit(:keep_alive_timeout, parameters["timeout"].to_i) if parameters.key?("timeout")
|
|
274
281
|
@handshake_completed = true
|
|
275
282
|
when /close/i
|
|
276
283
|
disable
|
|
@@ -50,11 +50,11 @@ module HTTPX
|
|
|
50
50
|
|
|
51
51
|
attr_reader :streams, :pending
|
|
52
52
|
|
|
53
|
-
def initialize(buffer, options)
|
|
54
|
-
@callbacks = nil
|
|
53
|
+
def initialize(buffer, pending, options)
|
|
54
|
+
@callbacks = @last_error = nil
|
|
55
55
|
@options = options
|
|
56
56
|
@settings = @options.http2_settings
|
|
57
|
-
@pending =
|
|
57
|
+
@pending = pending
|
|
58
58
|
@streams = {}
|
|
59
59
|
@drains = {}
|
|
60
60
|
@pings = []
|
|
@@ -74,10 +74,15 @@ module HTTPX
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def interests
|
|
77
|
-
if @connection.closed
|
|
77
|
+
if @connection.state == :closed
|
|
78
78
|
return unless @handshake_completed
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
if @buffer.empty?
|
|
81
|
+
# a closed HTTP/2 connection can still receive DATA frames from inflight streams.
|
|
82
|
+
return :r unless @streams.empty?
|
|
83
|
+
|
|
84
|
+
return
|
|
85
|
+
end
|
|
81
86
|
|
|
82
87
|
# HTTP/2 GOAWAY frame buffered.
|
|
83
88
|
return :w
|
|
@@ -85,7 +90,7 @@ module HTTPX
|
|
|
85
90
|
|
|
86
91
|
unless @connection.state == :connected && @handshake_completed
|
|
87
92
|
# HTTP/2 in intermediate state or still completing initialization-
|
|
88
|
-
return @buffer.empty? ? :r : :
|
|
93
|
+
return @buffer.empty? ? :r : :w
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
unless @connection.send_buffer.empty?
|
|
@@ -97,15 +102,10 @@ module HTTPX
|
|
|
97
102
|
return :r
|
|
98
103
|
end
|
|
99
104
|
|
|
100
|
-
# only wait for writable if we're not waiting on more streams,
|
|
101
|
-
# or waiting on ping ACKs.
|
|
102
|
-
nothing_to_wait_for = @streams.empty? && @pings.empty?
|
|
103
|
-
|
|
104
105
|
# there are pending bufferable requests
|
|
105
|
-
if !@pending.empty? &&
|
|
106
|
-
# only wait for writable if
|
|
107
|
-
|
|
108
|
-
return nothing_to_wait_for ? :w : :rw
|
|
106
|
+
if !@pending.empty? && can_buffer_pending_requests?
|
|
107
|
+
# only wait for writable if not waiting on the peer.
|
|
108
|
+
return waiting_for_peer? ? :rw : :w
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
unless @drains.empty?
|
|
@@ -123,17 +123,47 @@ module HTTPX
|
|
|
123
123
|
|
|
124
124
|
if @buffer.empty?
|
|
125
125
|
# skip if no more requests or pings to process
|
|
126
|
-
return
|
|
126
|
+
return unless waiting_for_peer?
|
|
127
127
|
|
|
128
128
|
:r
|
|
129
129
|
else
|
|
130
130
|
# buffered frames
|
|
131
|
-
|
|
131
|
+
waiting_for_peer? ? :rw : :w
|
|
132
132
|
end
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
def close
|
|
136
|
-
|
|
136
|
+
if @connection.closed?
|
|
137
|
+
# if there are inflight streams waiting to be completed AFTER the peer alread shut down the
|
|
138
|
+
# connection from their end, raise-or-retry depending on whether the error is recoverable.
|
|
139
|
+
if (ex = @last_error)
|
|
140
|
+
if @streams.any? || @pending.any?
|
|
141
|
+
if ex.is_a?(GoawayError) && !ex.unrecoverable?
|
|
142
|
+
inflight_unprocessed_requests = @streams.map do |req, _|
|
|
143
|
+
req.transition(:idle)
|
|
144
|
+
req
|
|
145
|
+
end
|
|
146
|
+
@streams.clear
|
|
147
|
+
|
|
148
|
+
# only signal exhaustion when inflight requests had to be moved back.
|
|
149
|
+
unless inflight_unprocessed_requests.empty?
|
|
150
|
+
@pending.unshift(*inflight_unprocessed_requests)
|
|
151
|
+
emit(:exhausted, ex)
|
|
152
|
+
end
|
|
153
|
+
else
|
|
154
|
+
while (req, _ = @streams.shift)
|
|
155
|
+
emit(:error, req, ex)
|
|
156
|
+
end
|
|
157
|
+
while (req = @pending.shift)
|
|
158
|
+
emit(:error, req, ex)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# clean pending payload now that everything's gone.
|
|
164
|
+
@buffer.clear
|
|
165
|
+
end
|
|
166
|
+
else
|
|
137
167
|
@connection.goaway
|
|
138
168
|
emit(:timeout, @options.timeout.close_handshake_timeout)
|
|
139
169
|
end
|
|
@@ -156,10 +186,16 @@ module HTTPX
|
|
|
156
186
|
# data received by the peer.
|
|
157
187
|
emit_stream_error(stream, request, error)
|
|
158
188
|
end
|
|
189
|
+
|
|
190
|
+
# if the connection transitioned to :closed, start tearing it down. Explicitly bypass the
|
|
191
|
+
# :closing transition to give it time to complete the inflight streams.
|
|
192
|
+
return unless @connection.state == :closed
|
|
193
|
+
|
|
194
|
+
close
|
|
159
195
|
end
|
|
160
196
|
|
|
161
197
|
def send(request, head = false)
|
|
162
|
-
unless
|
|
198
|
+
unless can_buffer_pending_requests?
|
|
163
199
|
head ? @pending.unshift(request) : @pending << request
|
|
164
200
|
return false
|
|
165
201
|
end
|
|
@@ -168,6 +204,7 @@ module HTTPX
|
|
|
168
204
|
handle_stream(stream, request)
|
|
169
205
|
@streams[request] = stream
|
|
170
206
|
@max_requests -= 1
|
|
207
|
+
emit(:request_accepted, request)
|
|
171
208
|
end
|
|
172
209
|
handle(request, stream)
|
|
173
210
|
true
|
|
@@ -176,6 +213,7 @@ module HTTPX
|
|
|
176
213
|
false
|
|
177
214
|
rescue ::HTTP2::Error::Error, ArgumentError => e
|
|
178
215
|
emit(:error, request, e)
|
|
216
|
+
false
|
|
179
217
|
end
|
|
180
218
|
|
|
181
219
|
def consume
|
|
@@ -184,6 +222,8 @@ module HTTPX
|
|
|
184
222
|
|
|
185
223
|
handle(request, stream)
|
|
186
224
|
end
|
|
225
|
+
|
|
226
|
+
send_pending
|
|
187
227
|
end
|
|
188
228
|
|
|
189
229
|
def handle_error(ex, request = nil)
|
|
@@ -191,37 +231,60 @@ module HTTPX
|
|
|
191
231
|
case ex
|
|
192
232
|
when OperationTimeoutError
|
|
193
233
|
if !@handshake_completed && @connection.state != :closed
|
|
234
|
+
# timed out waiting for HTTP/2 connection handshake to complete.
|
|
194
235
|
@connection.goaway(:settings_timeout, "closing due to settings timeout")
|
|
195
236
|
emit(:close_handshake)
|
|
196
237
|
settings_ex = SettingsTimeoutError.new(ex.timeout, ex.message)
|
|
197
238
|
settings_ex.set_backtrace(ex.backtrace)
|
|
198
239
|
ex = settings_ex
|
|
240
|
+
elsif @connection.state == :closing
|
|
241
|
+
# failed to receive data for inflight streams in time.
|
|
242
|
+
ex = @last_error || ex
|
|
243
|
+
end
|
|
244
|
+
when EOFError
|
|
245
|
+
if @connection.state == :closing
|
|
246
|
+
# socket closed while waiting for inflight streams to finish
|
|
247
|
+
ex = @last_error || ex
|
|
199
248
|
end
|
|
200
249
|
when GoawayError
|
|
201
250
|
last_stream_id = ex.last_stream_id
|
|
202
251
|
end
|
|
203
252
|
|
|
204
253
|
inflight_unprocessed_requests = [] #: Array[Request]
|
|
254
|
+
unprocessed_streams = [] #: Array[::HTTP2::Stream]
|
|
255
|
+
|
|
256
|
+
@streams.delete_if do |req, stream|
|
|
257
|
+
# allow inflight streams to finish
|
|
258
|
+
next(false) if !last_stream_id.infinite? && stream.id <= last_stream_id
|
|
205
259
|
|
|
206
|
-
while (req, stream = @streams.shift)
|
|
207
260
|
@drains.delete(stream)
|
|
208
261
|
|
|
209
|
-
next if request && request == req
|
|
262
|
+
next(true) if request && request == req
|
|
210
263
|
|
|
211
264
|
if stream.id > last_stream_id
|
|
212
265
|
req.transition(:idle)
|
|
213
266
|
# unprocessed request
|
|
214
267
|
inflight_unprocessed_requests << req
|
|
268
|
+
unprocessed_streams << stream
|
|
215
269
|
|
|
216
|
-
next
|
|
270
|
+
next(true)
|
|
217
271
|
end
|
|
218
272
|
|
|
219
273
|
emit(:error, req, ex)
|
|
274
|
+
true
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
@pending.unshift(*inflight_unprocessed_requests) if inflight_unprocessed_requests.any?
|
|
278
|
+
|
|
279
|
+
unless @streams.empty?
|
|
280
|
+
unprocessed_streams.each(&:close)
|
|
281
|
+
|
|
282
|
+
# streams covered by the GOAWAY are still being processed.
|
|
283
|
+
return
|
|
220
284
|
end
|
|
221
285
|
|
|
222
286
|
if ex.is_a?(GoawayError) && !ex.unrecoverable?
|
|
223
287
|
# resend unprocessed requests on a different connection
|
|
224
|
-
@pending.unshift(*inflight_unprocessed_requests) if inflight_unprocessed_requests.any?
|
|
225
288
|
emit(:exhausted, ex) if @pending.any?
|
|
226
289
|
return
|
|
227
290
|
end
|
|
@@ -244,18 +307,29 @@ module HTTPX
|
|
|
244
307
|
@pings.any?
|
|
245
308
|
end
|
|
246
309
|
|
|
310
|
+
def no_more_requests?
|
|
311
|
+
@streams.empty?
|
|
312
|
+
end
|
|
313
|
+
|
|
247
314
|
def reset_requests; end
|
|
248
315
|
|
|
249
316
|
private
|
|
250
317
|
|
|
251
|
-
|
|
252
|
-
|
|
318
|
+
# whether there are PING acks, or streams waiting for more data
|
|
319
|
+
# (that can be a response or WINDOW_UPDATE frames).
|
|
320
|
+
def waiting_for_peer?
|
|
321
|
+
waiting_for_ping? || @streams.any? { |r, s| !r.can_buffer? || !s.send_buffer.empty? }
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def can_buffer_pending_requests?
|
|
325
|
+
!@connection.closed? &&
|
|
326
|
+
(@handshake_completed || !@wait_for_handshake) &&
|
|
253
327
|
@streams.size < @max_concurrent_requests &&
|
|
254
328
|
@streams.size < @max_requests
|
|
255
329
|
end
|
|
256
330
|
|
|
257
331
|
def send_pending
|
|
258
|
-
while (request = @pending.shift)
|
|
332
|
+
while !@buffer.full? && can_buffer_pending_requests? && (request = @pending.shift)
|
|
259
333
|
break unless send(request, true)
|
|
260
334
|
end
|
|
261
335
|
end
|
|
@@ -527,16 +601,21 @@ module HTTPX
|
|
|
527
601
|
emit(:error, request, error)
|
|
528
602
|
end
|
|
529
603
|
else
|
|
530
|
-
|
|
604
|
+
# keep goaway error around, in case inflight streams never complete.
|
|
605
|
+
@last_error = ex = GoawayError.new(error, last_stream_id)
|
|
531
606
|
ex.set_backtrace(caller)
|
|
532
607
|
|
|
533
608
|
handle_error(ex)
|
|
534
|
-
teardown
|
|
535
609
|
end
|
|
536
610
|
end
|
|
537
|
-
return unless is_connection_closed
|
|
611
|
+
return unless is_connection_closed
|
|
538
612
|
|
|
539
|
-
|
|
613
|
+
if @streams.empty?
|
|
614
|
+
emit(:close)
|
|
615
|
+
else
|
|
616
|
+
# this connection should not start new streams.
|
|
617
|
+
emit(:closing)
|
|
618
|
+
end
|
|
540
619
|
end
|
|
541
620
|
|
|
542
621
|
def on_frame_sent(frame)
|