httpx 0.16.0 → 0.18.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/release_notes/0_16_1.md +5 -0
- data/doc/release_notes/0_17_0.md +49 -0
- data/doc/release_notes/0_18_0.md +69 -0
- data/doc/release_notes/0_18_1.md +12 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +5 -3
- data/lib/httpx/adapters/webmock.rb +9 -3
- data/lib/httpx/altsvc.rb +2 -2
- data/lib/httpx/chainable.rb +4 -4
- data/lib/httpx/connection/http1.rb +23 -14
- data/lib/httpx/connection/http2.rb +35 -17
- data/lib/httpx/connection.rb +74 -76
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/extensions.rb +50 -4
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io/ssl.rb +5 -1
- data/lib/httpx/io/tls.rb +7 -7
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +35 -13
- data/lib/httpx/parser/http1.rb +10 -6
- data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
- data/lib/httpx/plugins/aws_sigv4.rb +9 -11
- data/lib/httpx/plugins/compression.rb +5 -3
- data/lib/httpx/plugins/cookies/jar.rb +1 -1
- data/lib/httpx/plugins/digest_authentication.rb +4 -4
- data/lib/httpx/plugins/expect.rb +7 -3
- data/lib/httpx/plugins/grpc/message.rb +2 -2
- data/lib/httpx/plugins/grpc.rb +3 -3
- data/lib/httpx/plugins/h2c.rb +7 -3
- data/lib/httpx/plugins/internal_telemetry.rb +8 -8
- data/lib/httpx/plugins/multipart/decoder.rb +187 -0
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
- data/lib/httpx/plugins/multipart/part.rb +2 -2
- data/lib/httpx/plugins/multipart.rb +16 -2
- data/lib/httpx/plugins/ntlm_authentication.rb +4 -4
- data/lib/httpx/plugins/proxy/ssh.rb +11 -4
- data/lib/httpx/plugins/proxy.rb +6 -4
- data/lib/httpx/plugins/response_cache/store.rb +55 -0
- data/lib/httpx/plugins/response_cache.rb +88 -0
- data/lib/httpx/plugins/retries.rb +36 -14
- data/lib/httpx/plugins/stream.rb +3 -4
- data/lib/httpx/pool.rb +39 -13
- data/lib/httpx/registry.rb +1 -1
- data/lib/httpx/request.rb +12 -13
- data/lib/httpx/resolver/https.rb +5 -7
- data/lib/httpx/resolver/native.rb +19 -5
- data/lib/httpx/resolver/resolver_mixin.rb +2 -1
- data/lib/httpx/resolver/system.rb +2 -0
- data/lib/httpx/resolver.rb +2 -2
- data/lib/httpx/response.rb +60 -44
- data/lib/httpx/selector.rb +9 -19
- data/lib/httpx/session.rb +22 -15
- data/lib/httpx/session2.rb +3 -1
- data/lib/httpx/timers.rb +84 -0
- data/lib/httpx/transcoder/body.rb +2 -1
- data/lib/httpx/transcoder/form.rb +20 -0
- data/lib/httpx/transcoder/json.rb +12 -0
- data/lib/httpx/transcoder.rb +62 -1
- data/lib/httpx/utils.rb +10 -2
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/buffer.rbs +2 -2
- data/sig/chainable.rbs +7 -1
- data/sig/connection/http1.rbs +15 -4
- data/sig/connection/http2.rbs +19 -5
- data/sig/connection.rbs +15 -9
- data/sig/headers.rbs +19 -18
- data/sig/options.rbs +13 -5
- data/sig/parser/http1.rbs +3 -3
- data/sig/plugins/aws_sdk_authentication.rbs +22 -4
- data/sig/plugins/aws_sigv4.rbs +12 -3
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/multipart.rbs +64 -8
- data/sig/plugins/proxy.rbs +6 -6
- data/sig/plugins/response_cache.rbs +35 -0
- data/sig/plugins/retries.rbs +3 -0
- data/sig/pool.rbs +6 -0
- data/sig/request.rbs +11 -8
- data/sig/resolver/native.rbs +2 -1
- data/sig/resolver/resolver_mixin.rbs +1 -1
- data/sig/resolver/system.rbs +3 -1
- data/sig/response.rbs +11 -4
- data/sig/selector.rbs +8 -6
- data/sig/session.rbs +8 -14
- data/sig/timers.rbs +32 -0
- data/sig/transcoder/form.rbs +1 -0
- data/sig/transcoder/json.rbs +1 -0
- data/sig/transcoder.rbs +5 -4
- data/sig/utils.rbs +4 -0
- metadata +62 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc0ed6728252132961087b7747743f0011f2f8100eb9b242080b7ac29ce68752
|
4
|
+
data.tar.gz: 68c3c8f592d2e7599b7a5dcca4e9f73e7d61aba743bacb17d4c276c404203f5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 162b4b96df5b3b76bccdbe5940ed09aac4dea1d4b019321b07f859912d5d9fb770080d69198c70e4fd69a745723e250bcfce34e76baa83fdf3352f63d0b14620
|
7
|
+
data.tar.gz: 63099247e157ddd0b1f01bdbd459a51500b5bb7ac0a624a93c09958efbd10ec2c79027c5bb932ccacfa3335a8f9d0c7691b835ac30eabbe1ed6f7ced3a47421d
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# 0.17.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### Response mime type decoders (#json, #form)
|
6
|
+
|
7
|
+
https://gitlab.com/honeyryderchuck/httpx/-/wikis/Response-Handling#response-decoding
|
8
|
+
|
9
|
+
Two new methods, `#json` and `#form`, were added to `HTTPX::Response`. As the name implies, they'll decode the raw payload into ruby objects you can work with.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# after HTTPX.get("https://api.smth/endpoint-returning-json")
|
13
|
+
response.json # same as JSON.dump(response.to_s)
|
14
|
+
```
|
15
|
+
|
16
|
+
Although not yet documented, integrating custom decoders is also possible (i.e. parsing HTML with `nokogiri` or something similar).
|
17
|
+
|
18
|
+
## Improvements
|
19
|
+
|
20
|
+
### Connection: reduce interest calculations
|
21
|
+
|
22
|
+
Due to it being an intensive task, internal interest calculation in connections was reduce to the bare minimum.
|
23
|
+
|
24
|
+
### Immutable Options, internal recycling of instances, improves memory usage in the happy path
|
25
|
+
|
26
|
+
A lot of effort went into avoiding generating options objects internally whenever necessary. This means, when sending several requests with the same set of options (the most common case in `httpx` usage), internally only one object is passed around. For that, the following improvements were done:
|
27
|
+
|
28
|
+
* `Options#merge` returns the same options the the options being merged are a subset of the current set of options (b126938a6547e09b726dd64298fb488891d938e9).
|
29
|
+
* `Session#build_request` bypasses instantiation of options if it receives an `Options` object (which happens internally in the happy path, if users don't call `#build_request` directly) (3d549817cb41d4b904102fdc61afe3ecd9170893).
|
30
|
+
* Improving internal `Session` APIs to not pass around options, and instead rely on accessing request options.
|
31
|
+
* `Options#to_hash` does not build internal garbage arrays anymore (cc02679b804f63798f5d2136a039be1624e96ab6).
|
32
|
+
|
33
|
+
### Reduce regexp operations in the HTTP/1 parser
|
34
|
+
|
35
|
+
Some code paths in the HTTP/1 parser still using regular expressions were replaced by string operations accomplishing the same.
|
36
|
+
|
37
|
+
### HTTP/1 improvements on the complexity of connection accounting calculations
|
38
|
+
|
39
|
+
Managing open HTTP/1 connections relies on operations calculating whether there are requests waiting for completion. This relied on traversing all requests for that connectionn (O(n)); it now only checks the completion state of the first and last request of that connection, given that all requests in HTTP/1 are sequential (O(1)); this optimization brings a big improvement to persistent and pipelined requests (65261217b1270913e4bb93717e8b8dcfa775565a).
|
40
|
+
|
41
|
+
## Bugfixes
|
42
|
+
|
43
|
+
* fixing HTTP/1 protocol uncompliant exposing multiple values for the "Host" header (e435dd0534314508262184fb03d83124d89d2079).
|
44
|
+
|
45
|
+
* Custom response finalizer introduced in 0.16.0 has been reverted. It was brought to my attention that `Tempfile` implementation already takes care of the file on GC (and `httpx` was duplicating), and the approach taken in `httpx` was buggy in several ways (not tolerant to forks, never recycled finalizers...) (aa3be21c890f92a41afcc7931f01dd24cc801f7c).
|
46
|
+
|
47
|
+
## Chore
|
48
|
+
|
49
|
+
RBS Typing improvements based on latest stdlib signatures additions, such as `openssl`, `digest`, `socket` and others.
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# 0.18.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### Response Cache
|
6
|
+
|
7
|
+
https://gitlab.com/honeyryderchuck/httpx/-/wikis/Response-Cache
|
8
|
+
|
9
|
+
The `:response_cache` plugin handles transparent usage of HTTP caching and conditional requests to improve performance and bandwidth usage.
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
client = HTTPX.plugin(:response_cache)
|
13
|
+
r1 = client.get("https://nghttp2.org/httpbin/cache")
|
14
|
+
r2 = client.get("https://nghttp2.org/httpbin/cache")
|
15
|
+
|
16
|
+
r1.status #=> 200
|
17
|
+
r2.status #=> 304
|
18
|
+
r1.body == r2.body #=> true
|
19
|
+
```
|
20
|
+
|
21
|
+
### jitter on "retry-after"
|
22
|
+
|
23
|
+
On the `:retries` plugin, jitter calculation is now applied to the value in seconds defined by user after which a request should be retried (i.e. if `:retry_after` option is set to `2`, the retry interval may be `1.5422312` seconds, for example). This is important to avoid cases of synchronized "thundering herd", where server rejects requests, but they all get retried at the same time because the retry interval is exactly the same.
|
24
|
+
|
25
|
+
You can override the jitter calculation function by using the [:retry_jitter](https://gitlab.com/honeyryderchuck/httpx/-/wikis/Retries#retry_jitter) option:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
HTTPX.plugin(:retries, retry_after: 2, retry_jitter: ->(interval) { interval + rand }) # interval is 3
|
29
|
+
```
|
30
|
+
|
31
|
+
You can opt out of this by setting `HTTPX_NO_JITTER=1` environment variable.
|
32
|
+
|
33
|
+
### Response#error
|
34
|
+
|
35
|
+
`HTTPX::Response#error` was added, to match `HTTPX::Response#error`. It returns an exception for 4xx/5xx responses (`HTTPX::HTTPError`), `nil` otherwise. It allows for end users to write such code:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
if (response = HTTPX.get(uri)).error
|
39
|
+
# success
|
40
|
+
else
|
41
|
+
#error
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Improvements
|
46
|
+
|
47
|
+
* `webmock` adapter: added support for "stub_http_request#to_timeout" (https://gitlab.com/honeyryderchuck/httpx/-/merge_requests/165).
|
48
|
+
|
49
|
+
## timers not a dependency
|
50
|
+
|
51
|
+
The functionality provided by the `timers` gem was replaced by a simpler custom implementation. Although powerful, its complexity was somewhat unnecessary for `httpx`'s simpler event loop, where user-defined timeouts are usually the same for a given batch of requests. The removal of `timers` reduces the number of dependencies to 1, which is `http-2-next` and is maintained by me.
|
52
|
+
|
53
|
+
## AWS plugins
|
54
|
+
|
55
|
+
* `aws_sdk_authentication` plugin: removed implementation relying on `aws-sdk-s3`, replacing it with an `aws-sdk-core` relying implementation, which only uses credentials strategies and region discovery (the whole point of this SDK is to use a minimal subset of AWS SDK).
|
56
|
+
|
57
|
+
|
58
|
+
## Bugfixes
|
59
|
+
|
60
|
+
* Fixed Error class declaration on response decoders when mime type is invalid (https://gitlab.com/honeyryderchuck/httpx/-/merge_requests/166).
|
61
|
+
* `ErrorResponse#to_s` now removes ANSI escape sequences from error backtraces.
|
62
|
+
* Persistent connections were kept around both in the pool and in the selector; the first is necessary, but the second caused busy loop scenarios all over; they are now removed when no requests are being handled.
|
63
|
+
* Connections which failed connection handshake were removed from the pool, but not from the selector list, causing busy loop scenarios in a few cases; this has been fixed.
|
64
|
+
* Fixed issue where HTTP/2 streams were being closed twice (and signaling it also twice), messing connection accounting in the pool.
|
65
|
+
* DoH resolver was always subscribed to the default thread "connection pool", which broke scenarios were session was patched to use its custom pool; it now ensures that it subscribes to the pool it was created in.
|
66
|
+
* `:aws_sigv4` plugin: removed require of `aws-sdk-s3`, left there by mistake (the whole point of the plugin is to run without the AWS SDK).
|
67
|
+
## Chore
|
68
|
+
|
69
|
+
* `HTTPX::ErrorResponse#status` is now deprecated.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# 0.18.1
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
* HTTP/1.1 pipelining logs were logging the previously-buffered requests all together for each triggered request, which created some confusion for users when reporting errors. This has been fixed.
|
6
|
+
* HTTP/2 coalescing is now skipped when performing TLS connections with VERIFY_NONE.
|
7
|
+
* HTTP/2 peer GOAWAY frames will now result in a (retryable) connection error, instead of being ignored and leaving a "ghost" connection behind.
|
8
|
+
* fixed total timeout call which was not raising the exception.
|
9
|
+
|
10
|
+
## Chore
|
11
|
+
|
12
|
+
This gem now requires MFA-based gem releases.
|
@@ -22,6 +22,8 @@ module Faraday
|
|
22
22
|
# :nocov:
|
23
23
|
|
24
24
|
module RequestMixin
|
25
|
+
using ::HTTPX::HashExtensions
|
26
|
+
|
25
27
|
private
|
26
28
|
|
27
29
|
def build_request(env)
|
@@ -38,7 +40,7 @@ module Faraday
|
|
38
40
|
timeout_options = {
|
39
41
|
connect_timeout: env.request.open_timeout,
|
40
42
|
operation_timeout: env.request.timeout,
|
41
|
-
}.
|
43
|
+
}.compact
|
42
44
|
|
43
45
|
options = {
|
44
46
|
ssl: {},
|
@@ -101,7 +103,7 @@ module Faraday
|
|
101
103
|
end
|
102
104
|
|
103
105
|
def on_response(&blk)
|
104
|
-
if
|
106
|
+
if blk
|
105
107
|
@on_response = lambda do |response|
|
106
108
|
blk.call(response)
|
107
109
|
end
|
@@ -112,7 +114,7 @@ module Faraday
|
|
112
114
|
end
|
113
115
|
|
114
116
|
def on_complete(&blk)
|
115
|
-
if
|
117
|
+
if blk
|
116
118
|
@on_complete = blk
|
117
119
|
self
|
118
120
|
else
|
@@ -19,7 +19,7 @@ module WebMock
|
|
19
19
|
module InstanceMethods
|
20
20
|
private
|
21
21
|
|
22
|
-
def send_requests(*requests
|
22
|
+
def send_requests(*requests)
|
23
23
|
request_signatures = requests.map do |request|
|
24
24
|
request_signature = _build_webmock_request_signature(request)
|
25
25
|
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
@@ -47,7 +47,7 @@ module WebMock
|
|
47
47
|
|
48
48
|
unless real_requests.empty?
|
49
49
|
reqs = real_requests.keys
|
50
|
-
reqs.zip(super(*reqs
|
50
|
+
reqs.zip(super(*reqs)).each do |req, res|
|
51
51
|
idx = real_requests[req]
|
52
52
|
|
53
53
|
if WebMock::CallbackRegistry.any_callbacks?
|
@@ -86,7 +86,9 @@ module WebMock
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def _build_from_webmock_response(request, webmock_response)
|
89
|
-
return
|
89
|
+
return _build_error_response(request, HTTPX::TimeoutError.new(1, "Timed out")) if webmock_response.should_timeout
|
90
|
+
|
91
|
+
return _build_error_response(request, webmock_response.exception) if webmock_response.exception
|
90
92
|
|
91
93
|
response = request.options.response_class.new(request,
|
92
94
|
webmock_response.status[0],
|
@@ -95,6 +97,10 @@ module WebMock
|
|
95
97
|
response << webmock_response.body.dup
|
96
98
|
response
|
97
99
|
end
|
100
|
+
|
101
|
+
def _build_error_response(request, exception)
|
102
|
+
HTTPX::ErrorResponse.new(request, exception, request.options)
|
103
|
+
end
|
98
104
|
end
|
99
105
|
end
|
100
106
|
|
data/lib/httpx/altsvc.rb
CHANGED
@@ -10,14 +10,14 @@ module HTTPX
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
def cached_altsvc(origin)
|
13
|
-
now =
|
13
|
+
now = Utils.now
|
14
14
|
@altsvc_mutex.synchronize do
|
15
15
|
lookup(origin, now)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
def cached_altsvc_set(origin, entry)
|
20
|
-
now =
|
20
|
+
now = Utils.now
|
21
21
|
@altsvc_mutex.synchronize do
|
22
22
|
return if @altsvcs[origin].any? { |altsvc| altsvc["origin"] == entry["origin"] }
|
23
23
|
|
data/lib/httpx/chainable.rb
CHANGED
@@ -4,9 +4,9 @@ module HTTPX
|
|
4
4
|
module Chainable
|
5
5
|
%i[head get post put delete trace options connect patch].each do |meth|
|
6
6
|
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
7
|
-
def #{meth}(*uri, **options)
|
8
|
-
request(:#{meth}, uri, **options)
|
9
|
-
end
|
7
|
+
def #{meth}(*uri, **options) # def get(*uri, **options)
|
8
|
+
request(:#{meth}, uri, **options) # request(:get, uri, **options)
|
9
|
+
end # end
|
10
10
|
MOD
|
11
11
|
end
|
12
12
|
|
@@ -78,7 +78,7 @@ module HTTPX
|
|
78
78
|
with(option.to_sym => (args.first || options))
|
79
79
|
end
|
80
80
|
|
81
|
-
def respond_to_missing?(meth)
|
81
|
+
def respond_to_missing?(meth, *)
|
82
82
|
return super unless meth =~ /\Awith_(.+)/
|
83
83
|
|
84
84
|
option = Regexp.last_match(1)
|
@@ -36,6 +36,8 @@ module HTTPX
|
|
36
36
|
|
37
37
|
request = @requests.first
|
38
38
|
|
39
|
+
return unless request
|
40
|
+
|
39
41
|
return :w if request.interests == :w || !@buffer.empty?
|
40
42
|
|
41
43
|
:r
|
@@ -59,7 +61,12 @@ module HTTPX
|
|
59
61
|
def empty?
|
60
62
|
# this means that for every request there's an available
|
61
63
|
# partial response, so there are no in-flight requests waiting.
|
62
|
-
@requests.empty? ||
|
64
|
+
@requests.empty? || (
|
65
|
+
# checking all responses can be time-consuming. Alas, as in HTTP/1, responses
|
66
|
+
# do not come out of order, we can get away with checking first and last.
|
67
|
+
!@requests.first.response.nil? &&
|
68
|
+
(@requests.size == 1 || !@requests.last.response.nil?)
|
69
|
+
)
|
63
70
|
end
|
64
71
|
|
65
72
|
def <<(data)
|
@@ -260,7 +267,7 @@ module HTTPX
|
|
260
267
|
def set_protocol_headers(request)
|
261
268
|
if !request.headers.key?("content-length") &&
|
262
269
|
request.body.bytesize == Float::INFINITY
|
263
|
-
request.chunk!
|
270
|
+
request.body.chunk!
|
264
271
|
end
|
265
272
|
|
266
273
|
connection = request.headers["connection"]
|
@@ -278,17 +285,16 @@ module HTTPX
|
|
278
285
|
# on the last request of the possible batch (either allowed max requests,
|
279
286
|
# or if smaller, the size of the batch itself)
|
280
287
|
requests_limit = [@max_requests, @requests.size].min
|
281
|
-
if request
|
282
|
-
"keep-alive"
|
283
|
-
else
|
288
|
+
if request == @requests[requests_limit - 1]
|
284
289
|
"close"
|
290
|
+
else
|
291
|
+
"keep-alive"
|
285
292
|
end
|
286
293
|
end
|
287
294
|
|
288
|
-
{
|
289
|
-
|
290
|
-
|
291
|
-
}
|
295
|
+
extra_headers = { "connection" => connection }
|
296
|
+
extra_headers["host"] = request.authority unless request.headers.key?("host")
|
297
|
+
extra_headers
|
292
298
|
end
|
293
299
|
|
294
300
|
def headline_uri(request)
|
@@ -309,8 +315,9 @@ module HTTPX
|
|
309
315
|
end
|
310
316
|
|
311
317
|
def join_headers(request)
|
312
|
-
|
313
|
-
|
318
|
+
headline = "#{request.verb.to_s.upcase} #{headline_uri(request)} HTTP/#{@version.join(".")}"
|
319
|
+
@buffer << headline << CRLF
|
320
|
+
log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
|
314
321
|
extra_headers = set_protocol_headers(request)
|
315
322
|
join_headers2(request.headers.each(extra_headers))
|
316
323
|
log { "<- " }
|
@@ -318,7 +325,7 @@ module HTTPX
|
|
318
325
|
end
|
319
326
|
|
320
327
|
def join_body(request)
|
321
|
-
return if request.empty?
|
328
|
+
return if request.body.empty?
|
322
329
|
|
323
330
|
while (chunk = request.drain_body)
|
324
331
|
log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
|
@@ -327,7 +334,9 @@ module HTTPX
|
|
327
334
|
throw(:buffer_full, request) if @buffer.full?
|
328
335
|
end
|
329
336
|
|
330
|
-
|
337
|
+
return unless (error = request.drain_error)
|
338
|
+
|
339
|
+
raise error
|
331
340
|
end
|
332
341
|
|
333
342
|
def join_trailers(request)
|
@@ -354,7 +363,7 @@ module HTTPX
|
|
354
363
|
}.freeze
|
355
364
|
|
356
365
|
def capitalized(field)
|
357
|
-
UPCASED[field] || field.
|
366
|
+
UPCASED[field] || field.split("-").map(&:capitalize).join("-")
|
358
367
|
end
|
359
368
|
end
|
360
369
|
Connection.register "http/1.1", Connection::HTTP1
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "securerandom"
|
4
|
-
require "io/wait"
|
5
4
|
require "http/2/next"
|
6
5
|
|
7
6
|
module HTTPX
|
@@ -17,6 +16,12 @@ module HTTPX
|
|
17
16
|
end
|
18
17
|
end
|
19
18
|
|
19
|
+
class GoawayError < Error
|
20
|
+
def initialize
|
21
|
+
super(0, :no_error)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
20
25
|
attr_reader :streams, :pending
|
21
26
|
|
22
27
|
def initialize(buffer, options)
|
@@ -56,7 +61,7 @@ module HTTPX
|
|
56
61
|
|
57
62
|
return :w if !@pending.empty? && can_buffer_more_requests?
|
58
63
|
|
59
|
-
return :w
|
64
|
+
return :w unless @drains.empty?
|
60
65
|
|
61
66
|
if @buffer.empty?
|
62
67
|
return if @streams.empty? && @pings.empty?
|
@@ -218,7 +223,7 @@ module HTTPX
|
|
218
223
|
log(level: 1, color: :yellow) do
|
219
224
|
request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
|
220
225
|
end
|
221
|
-
stream.headers(request.headers.each(extra_headers), end_stream: request.empty?)
|
226
|
+
stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
|
222
227
|
end
|
223
228
|
|
224
229
|
def join_trailers(stream, request)
|
@@ -234,7 +239,7 @@ module HTTPX
|
|
234
239
|
end
|
235
240
|
|
236
241
|
def join_body(stream, request)
|
237
|
-
return if request.empty?
|
242
|
+
return if request.body.empty?
|
238
243
|
|
239
244
|
chunk = @drains.delete(request) || request.drain_body
|
240
245
|
while chunk
|
@@ -249,7 +254,9 @@ module HTTPX
|
|
249
254
|
chunk = next_chunk
|
250
255
|
end
|
251
256
|
|
252
|
-
|
257
|
+
return unless (error = request.drain_error)
|
258
|
+
|
259
|
+
on_stream_refuse(stream, request, error)
|
253
260
|
end
|
254
261
|
|
255
262
|
######
|
@@ -257,8 +264,10 @@ module HTTPX
|
|
257
264
|
######
|
258
265
|
|
259
266
|
def on_stream_headers(stream, request, h)
|
260
|
-
|
261
|
-
|
267
|
+
response = request.response
|
268
|
+
|
269
|
+
if response.is_a?(Response) && response.version == "2.0"
|
270
|
+
on_stream_trailers(stream, response, h)
|
262
271
|
return
|
263
272
|
end
|
264
273
|
|
@@ -274,11 +283,11 @@ module HTTPX
|
|
274
283
|
handle(request, stream) if request.expects?
|
275
284
|
end
|
276
285
|
|
277
|
-
def on_stream_trailers(stream,
|
286
|
+
def on_stream_trailers(stream, response, h)
|
278
287
|
log(color: :yellow) do
|
279
288
|
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{v}" }.join("\n")
|
280
289
|
end
|
281
|
-
|
290
|
+
response.merge_headers(h)
|
282
291
|
end
|
283
292
|
|
284
293
|
def on_stream_data(stream, request, data)
|
@@ -288,23 +297,25 @@ module HTTPX
|
|
288
297
|
end
|
289
298
|
|
290
299
|
def on_stream_refuse(stream, request, error)
|
291
|
-
stream.close
|
292
300
|
on_stream_close(stream, request, error)
|
301
|
+
stream.close
|
293
302
|
end
|
294
303
|
|
295
304
|
def on_stream_close(stream, request, error)
|
305
|
+
return if error == :stream_closed && !@streams.key?(request)
|
306
|
+
|
296
307
|
log(level: 2) { "#{stream.id}: closing stream" }
|
297
308
|
@drains.delete(request)
|
298
309
|
@streams.delete(request)
|
299
310
|
|
300
|
-
if error
|
311
|
+
if error
|
301
312
|
ex = Error.new(stream.id, error)
|
302
313
|
ex.set_backtrace(caller)
|
303
314
|
response = ErrorResponse.new(request, ex, request.options)
|
304
315
|
emit(:response, request, response)
|
305
316
|
else
|
306
317
|
response = request.response
|
307
|
-
if response.status == 421
|
318
|
+
if response && response.status == 421
|
308
319
|
ex = MisdirectedRequestError.new(response)
|
309
320
|
ex.set_backtrace(caller)
|
310
321
|
emit(:error, request, ex)
|
@@ -339,9 +350,16 @@ module HTTPX
|
|
339
350
|
|
340
351
|
def on_close(_last_frame, error, _payload)
|
341
352
|
is_connection_closed = @connection.state == :closed
|
342
|
-
if error
|
353
|
+
if error
|
343
354
|
@buffer.clear if is_connection_closed
|
344
|
-
|
355
|
+
if error == :no_error
|
356
|
+
ex = GoawayError.new
|
357
|
+
@pending.unshift(*@streams.keys)
|
358
|
+
@drains.clear
|
359
|
+
@streams.clear
|
360
|
+
else
|
361
|
+
ex = Error.new(0, error)
|
362
|
+
end
|
345
363
|
ex.set_backtrace(caller)
|
346
364
|
handle_error(ex)
|
347
365
|
end
|
@@ -385,10 +403,10 @@ module HTTPX
|
|
385
403
|
end
|
386
404
|
|
387
405
|
def on_pong(ping)
|
388
|
-
if
|
389
|
-
close(:protocol_error, "ping payload did not match")
|
390
|
-
else
|
406
|
+
if @pings.delete(ping.to_s)
|
391
407
|
emit(:pong)
|
408
|
+
else
|
409
|
+
close(:protocol_error, "ping payload did not match")
|
392
410
|
end
|
393
411
|
end
|
394
412
|
end
|