httpx 0.15.4 → 0.18.0
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_0.md +93 -0
- 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/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +8 -14
- data/lib/httpx/adapters/webmock.rb +9 -3
- data/lib/httpx/altsvc.rb +2 -2
- data/lib/httpx/buffer.rb +1 -1
- data/lib/httpx/callbacks.rb +1 -1
- data/lib/httpx/chainable.rb +18 -11
- data/lib/httpx/connection/http1.rb +21 -13
- data/lib/httpx/connection/http2.rb +20 -25
- data/lib/httpx/connection.rb +73 -77
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/errors.rb +11 -11
- data/lib/httpx/extensions.rb +50 -4
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io/ssl.rb +3 -3
- data/lib/httpx/io/tls.rb +8 -8
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +108 -81
- data/lib/httpx/parser/http1.rb +11 -7
- data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
- data/lib/httpx/plugins/aws_sigv4.rb +19 -20
- data/lib/httpx/plugins/compression.rb +17 -14
- data/lib/httpx/plugins/cookies/cookie.rb +4 -2
- data/lib/httpx/plugins/cookies/jar.rb +21 -2
- data/lib/httpx/plugins/cookies.rb +20 -7
- data/lib/httpx/plugins/digest_authentication.rb +19 -15
- data/lib/httpx/plugins/expect.rb +26 -18
- data/lib/httpx/plugins/follow_redirects.rb +9 -9
- data/lib/httpx/plugins/grpc/call.rb +4 -1
- data/lib/httpx/plugins/grpc/message.rb +2 -2
- data/lib/httpx/plugins/grpc.rb +72 -46
- 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 +12 -10
- data/lib/httpx/plugins/proxy/socks4.rb +2 -1
- data/lib/httpx/plugins/proxy/socks5.rb +2 -1
- data/lib/httpx/plugins/proxy/ssh.rb +20 -13
- data/lib/httpx/plugins/proxy.rb +10 -10
- 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 +46 -23
- data/lib/httpx/plugins/stream.rb +3 -4
- data/lib/httpx/plugins/upgrade.rb +7 -6
- data/lib/httpx/pool.rb +39 -13
- data/lib/httpx/registry.rb +2 -2
- data/lib/httpx/request.rb +16 -25
- data/lib/httpx/resolver/https.rb +4 -8
- 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 +91 -48
- data/lib/httpx/selector.rb +11 -24
- data/lib/httpx/session.rb +41 -23
- data/lib/httpx/session2.rb +23 -0
- data/lib/httpx/timers.rb +84 -0
- data/lib/httpx/transcoder/body.rb +3 -2
- data/lib/httpx/transcoder/chunker.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 +7 -3
- data/sig/buffer.rbs +3 -1
- data/sig/chainable.rbs +31 -29
- data/sig/connection/http1.rbs +11 -5
- data/sig/connection/http2.rbs +16 -5
- data/sig/connection.rbs +31 -13
- data/sig/errors.rbs +35 -1
- data/sig/headers.rbs +20 -19
- data/sig/httpx.rbs +4 -1
- data/sig/loggable.rbs +3 -1
- data/sig/options.rbs +45 -34
- data/sig/parser/http1.rbs +3 -3
- data/sig/plugins/authentication.rbs +1 -1
- data/sig/plugins/aws_sdk_authentication.rbs +25 -3
- data/sig/plugins/aws_sigv4.rbs +13 -5
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/compression.rbs +4 -6
- data/sig/plugins/cookies/cookie.rbs +5 -7
- data/sig/plugins/cookies/jar.rbs +9 -10
- data/sig/plugins/cookies.rbs +4 -5
- data/sig/plugins/digest_authentication.rbs +2 -3
- data/sig/plugins/expect.rbs +2 -4
- data/sig/plugins/follow_redirects.rbs +3 -5
- data/sig/plugins/grpc.rbs +4 -7
- data/sig/plugins/h2c.rbs +0 -2
- data/sig/plugins/multipart.rbs +64 -10
- data/sig/plugins/ntlm_authentication.rbs +2 -3
- data/sig/plugins/persistent.rbs +3 -8
- data/sig/plugins/proxy/ssh.rbs +4 -4
- data/sig/plugins/proxy.rbs +13 -13
- data/sig/plugins/push_promise.rbs +0 -2
- data/sig/plugins/response_cache.rbs +35 -0
- data/sig/plugins/retries.rbs +7 -8
- data/sig/plugins/stream.rbs +1 -1
- data/sig/plugins/upgrade.rbs +2 -3
- data/sig/pool.rbs +7 -2
- data/sig/registry.rbs +1 -1
- data/sig/request.rbs +11 -8
- data/sig/resolver/native.rbs +10 -5
- data/sig/resolver/resolver_mixin.rbs +4 -5
- data/sig/resolver/system.rbs +4 -0
- data/sig/resolver.rbs +7 -0
- data/sig/response.rbs +26 -13
- data/sig/selector.rbs +11 -9
- data/sig/session.rbs +22 -23
- data/sig/timers.rbs +32 -0
- data/sig/transcoder/body.rbs +6 -1
- data/sig/transcoder/chunker.rbs +8 -2
- data/sig/transcoder/form.rbs +3 -1
- data/sig/transcoder/json.rbs +2 -0
- data/sig/transcoder.rbs +13 -5
- data/sig/utils.rbs +6 -0
- metadata +18 -18
- data/lib/httpx/request2.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 372f89cdf05727a32d23467503299ae77831cb5d23356467dc0c7f3a22bbfd0c
|
4
|
+
data.tar.gz: bbb3df9079bf4f5a449f37f34dd6b1d3208a978e8a8eaa7fdd69ea9d1829ac6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 38211af1e56fbc823ad0780198579ff11d4fa4b5b64677af95e78361f8b274d0dc43b124697358f1a0b17c2d45e4cdd620f97ca983af7fa60ce59515e9b51785
|
7
|
+
data.tar.gz: 2c9d48f3ad7a499046ab94ac3ee1831f1623ce44990de0f08320ac6e5ce3767ca938381cd7b96c3799477f201a811aa5c446d62f88bd58c6e1e0afd84c53af75
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# 0.16.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### Response::Body#to_s does not clear the internal buffer
|
6
|
+
|
7
|
+
It's well documented that the response body should be treated as a file, and that calling `.to_s` on a response should be done only once, and the user should not expect the same call to return the same response body again, while suggesting that the first call should be cached in a variable in case it's needed:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
response = HTTPX.get("https://google.com")
|
11
|
+
body = response.body.to_s #=> "<html ...."
|
12
|
+
response.body.to_s #=> ""
|
13
|
+
|
14
|
+
# thankfully,it's cached in the body var there.
|
15
|
+
```
|
16
|
+
|
17
|
+
The justification for this behaviour probably had to do with avoiding keeping huge payloads around, but it got a bit lost in git history. It became a feature, not a bug.
|
18
|
+
|
19
|
+
However, I got an [issue report](https://gitlab.com/honeyryderchuck/httpx/-/issues/143) that made me change my mind about this behaviour (tl;dr: it broke pattern matching when matching against response bodies more than once).
|
20
|
+
|
21
|
+
So now, you can call `.to_s` how many times you want!
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
response = HTTPX.get("https://google.com")
|
25
|
+
body = response.body.to_s #=> "<html ...."
|
26
|
+
response.body.to_s #=> "<html ....", still here!
|
27
|
+
```
|
28
|
+
|
29
|
+
Some optimizations were done around how the body is carried forward, and bodies buffered in files will now get properly garbage collected and not leak descriptors behind when users forget to call `.close`.
|
30
|
+
|
31
|
+
### grpc plugin improvements
|
32
|
+
|
33
|
+
##### build fully-enabled stub from grpc service
|
34
|
+
|
35
|
+
The `:grpc` plugin can now build fully-loaded stubs from existing GRPC generic services.
|
36
|
+
|
37
|
+
GRPC stubs could be a bit tedious to write when compared to what the `grpc` gem offers, which is, auto-generation from ruby service stubs from protobuf definitions:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# service generated from the command:
|
41
|
+
#
|
42
|
+
# > grpc_tools_ruby_protoc -I ../../protos --ruby_out=../lib --grpc_out=../lib ../../protos/route_guide.proto
|
43
|
+
#
|
44
|
+
require "route_guide_services_pb.rb"
|
45
|
+
|
46
|
+
# with httpx, before 0.16
|
47
|
+
stub = HTTPX.plugin(:grpc).build_stub("localhost:#{server_port}", service: "RouteGuide")
|
48
|
+
.rpc(:GetFeature, Point, Feature)
|
49
|
+
.rpc(:ListFeatures, # ... and so on, all hand stitched
|
50
|
+
|
51
|
+
stub.get_feature(# ...
|
52
|
+
|
53
|
+
# with httpx 0.16
|
54
|
+
stub = HTTPX.plugin(:grpc).build_stub("localhost:#{server_port}", service: RouteGuide)
|
55
|
+
# that's it!
|
56
|
+
stub.get_feature(# ...
|
57
|
+
```
|
58
|
+
|
59
|
+
#### no google/protobuf direct dependency
|
60
|
+
|
61
|
+
`"google/protobuf"` is no longer assumed when using the plugin, i.e. you can use other protobuf serializers, such as https://github.com/ruby-protobuf/protobuf , which supports `jruby` (unlike the former).
|
62
|
+
|
63
|
+
### OptionsMethods for plugins
|
64
|
+
|
65
|
+
https://gitlab.com/honeyryderchuck/httpx/-/wikis/Custom-Plugins
|
66
|
+
|
67
|
+
You can now define an `OptionsMethods` module under your custom plugin to define your own methods. The tl;dr is, that, given the following module below, a new `:bar` option will be available (and the method will be used to set it):
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
module CustomPlugin
|
71
|
+
module OptionsMethods
|
72
|
+
def option_bar(x) ; x; end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
HTTPX.plugin(CustomPlugin).with(bar: 2)
|
77
|
+
```
|
78
|
+
|
79
|
+
### cookies plugin: improved jar management
|
80
|
+
|
81
|
+
The behaviour of the cookies jar from the `:cookies` plugin was a bit unpredictable in certain conditions, for instance if a "Cookie" header would be passed directly via `.with(headers: {"Cookie" => "a=1"})` and there'd be a value for it already (in same cases, it'd be fully ignored). This would even get worse, if the session had a jar, and a specific set of cookies would be passed to a request(i.e.: `session_with_cookies.get("http://url.get", headers: {"Cookies" => "..."}`).
|
82
|
+
|
83
|
+
The behaviour was fixed, and is now specced under https://gitlab.com/honeyryderchuck/httpx/-/blob/master/test/support/requests/plugins/cookies.rb .
|
84
|
+
|
85
|
+
## Bugfixes
|
86
|
+
|
87
|
+
* Cookies sorting in the `:cookies` plugin jar was fixed for truffleruby;
|
88
|
+
|
89
|
+
## Chore
|
90
|
+
|
91
|
+
* errors when setting options nnow raise `TypeError` instead of `HTTPX::Error`.
|
92
|
+
* options are now internally frozen by default, which should protect the internals against accidentally updating them;
|
93
|
+
* Fixed optimization around options initialization, to prevent needless allocations;
|
@@ -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.
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "delegate"
|
3
4
|
require "httpx"
|
4
5
|
require "faraday"
|
5
6
|
|
@@ -21,6 +22,8 @@ module Faraday
|
|
21
22
|
# :nocov:
|
22
23
|
|
23
24
|
module RequestMixin
|
25
|
+
using ::HTTPX::HashExtensions
|
26
|
+
|
24
27
|
private
|
25
28
|
|
26
29
|
def build_request(env)
|
@@ -37,7 +40,7 @@ module Faraday
|
|
37
40
|
timeout_options = {
|
38
41
|
connect_timeout: env.request.open_timeout,
|
39
42
|
operation_timeout: env.request.timeout,
|
40
|
-
}.
|
43
|
+
}.compact
|
41
44
|
|
42
45
|
options = {
|
43
46
|
ssl: {},
|
@@ -91,15 +94,16 @@ module Faraday
|
|
91
94
|
end
|
92
95
|
|
93
96
|
class ParallelManager
|
94
|
-
class ResponseHandler
|
97
|
+
class ResponseHandler < SimpleDelegator
|
95
98
|
attr_reader :env
|
96
99
|
|
97
100
|
def initialize(env)
|
98
101
|
@env = env
|
102
|
+
super
|
99
103
|
end
|
100
104
|
|
101
105
|
def on_response(&blk)
|
102
|
-
if
|
106
|
+
if blk
|
103
107
|
@on_response = lambda do |response|
|
104
108
|
blk.call(response)
|
105
109
|
end
|
@@ -110,23 +114,13 @@ module Faraday
|
|
110
114
|
end
|
111
115
|
|
112
116
|
def on_complete(&blk)
|
113
|
-
if
|
117
|
+
if blk
|
114
118
|
@on_complete = blk
|
115
119
|
self
|
116
120
|
else
|
117
121
|
@on_complete
|
118
122
|
end
|
119
123
|
end
|
120
|
-
|
121
|
-
def respond_to_missing?(meth)
|
122
|
-
@env.respond_to?(meth) || super
|
123
|
-
end
|
124
|
-
|
125
|
-
def method_missing(meth, *args, &blk)
|
126
|
-
return super unless @env && @env.respond_to?(meth)
|
127
|
-
|
128
|
-
@env.__send__(meth, *args, &blk)
|
129
|
-
end
|
130
124
|
end
|
131
125
|
|
132
126
|
include RequestMixin
|
@@ -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/buffer.rb
CHANGED
data/lib/httpx/callbacks.rb
CHANGED
@@ -19,7 +19,7 @@ module HTTPX
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def emit(type, *args)
|
22
|
-
callbacks(type).delete_if { |pr| :delete == pr
|
22
|
+
callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
|
23
23
|
end
|
24
24
|
|
25
25
|
protected
|
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
|
|
@@ -34,21 +34,21 @@ module HTTPX
|
|
34
34
|
branch(default_options).wrap(&blk)
|
35
35
|
end
|
36
36
|
|
37
|
-
def plugin(
|
37
|
+
def plugin(pl, options = nil, &blk)
|
38
38
|
klass = is_a?(Session) ? self.class : Session
|
39
39
|
klass = Class.new(klass)
|
40
40
|
klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
|
41
|
-
klass.plugin(
|
41
|
+
klass.plugin(pl, options, &blk).new
|
42
42
|
end
|
43
43
|
|
44
44
|
# deprecated
|
45
45
|
# :nocov:
|
46
|
-
def plugins(
|
46
|
+
def plugins(pls)
|
47
47
|
warn ":#{__method__} is deprecated, use :plugin instead"
|
48
48
|
klass = is_a?(Session) ? self.class : Session
|
49
49
|
klass = Class.new(klass)
|
50
50
|
klass.instance_variable_set(:@default_options, klass.default_options.merge(default_options))
|
51
|
-
klass.plugins(
|
51
|
+
klass.plugins(pls).new
|
52
52
|
end
|
53
53
|
# :nocov:
|
54
54
|
|
@@ -71,12 +71,19 @@ module HTTPX
|
|
71
71
|
def method_missing(meth, *args, **options)
|
72
72
|
return super unless meth =~ /\Awith_(.+)/
|
73
73
|
|
74
|
-
option = Regexp.last_match(1)
|
75
|
-
|
74
|
+
option = Regexp.last_match(1)
|
75
|
+
|
76
|
+
return super unless option
|
77
|
+
|
78
|
+
with(option.to_sym => (args.first || options))
|
76
79
|
end
|
77
80
|
|
78
|
-
def respond_to_missing?(meth, *
|
79
|
-
|
81
|
+
def respond_to_missing?(meth, *)
|
82
|
+
return super unless meth =~ /\Awith_(.+)/
|
83
|
+
|
84
|
+
option = Regexp.last_match(1)
|
85
|
+
|
86
|
+
default_options.respond_to?(option) || super
|
80
87
|
end
|
81
88
|
end
|
82
89
|
end
|
@@ -59,7 +59,12 @@ module HTTPX
|
|
59
59
|
def empty?
|
60
60
|
# this means that for every request there's an available
|
61
61
|
# partial response, so there are no in-flight requests waiting.
|
62
|
-
@requests.empty? ||
|
62
|
+
@requests.empty? || (
|
63
|
+
# checking all responses can be time-consuming. Alas, as in HTTP/1, responses
|
64
|
+
# do not come out of order, we can get away with checking first and last.
|
65
|
+
!@requests.first.response.nil? &&
|
66
|
+
(@requests.size == 1 || !@requests.last.response.nil?)
|
67
|
+
)
|
63
68
|
end
|
64
69
|
|
65
70
|
def <<(data)
|
@@ -260,10 +265,12 @@ module HTTPX
|
|
260
265
|
def set_protocol_headers(request)
|
261
266
|
if !request.headers.key?("content-length") &&
|
262
267
|
request.body.bytesize == Float::INFINITY
|
263
|
-
request.chunk!
|
268
|
+
request.body.chunk!
|
264
269
|
end
|
265
270
|
|
266
|
-
connection =
|
271
|
+
connection = request.headers["connection"]
|
272
|
+
|
273
|
+
connection ||= if request.options.persistent
|
267
274
|
# when in a persistent connection, the request can't be at
|
268
275
|
# the edge of a renegotiation
|
269
276
|
if @requests.index(request) + 1 < @max_requests
|
@@ -276,17 +283,16 @@ module HTTPX
|
|
276
283
|
# on the last request of the possible batch (either allowed max requests,
|
277
284
|
# or if smaller, the size of the batch itself)
|
278
285
|
requests_limit = [@max_requests, @requests.size].min
|
279
|
-
if request
|
280
|
-
"keep-alive"
|
281
|
-
else
|
286
|
+
if request == @requests[requests_limit - 1]
|
282
287
|
"close"
|
288
|
+
else
|
289
|
+
"keep-alive"
|
283
290
|
end
|
284
291
|
end
|
285
292
|
|
286
|
-
{
|
287
|
-
|
288
|
-
|
289
|
-
}
|
293
|
+
extra_headers = { "connection" => connection }
|
294
|
+
extra_headers["host"] = request.authority unless request.headers.key?("host")
|
295
|
+
extra_headers
|
290
296
|
end
|
291
297
|
|
292
298
|
def headline_uri(request)
|
@@ -316,7 +322,7 @@ module HTTPX
|
|
316
322
|
end
|
317
323
|
|
318
324
|
def join_body(request)
|
319
|
-
return if request.empty?
|
325
|
+
return if request.body.empty?
|
320
326
|
|
321
327
|
while (chunk = request.drain_body)
|
322
328
|
log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
|
@@ -325,7 +331,9 @@ module HTTPX
|
|
325
331
|
throw(:buffer_full, request) if @buffer.full?
|
326
332
|
end
|
327
333
|
|
328
|
-
|
334
|
+
return unless (error = request.drain_error)
|
335
|
+
|
336
|
+
raise error
|
329
337
|
end
|
330
338
|
|
331
339
|
def join_trailers(request)
|
@@ -352,7 +360,7 @@ module HTTPX
|
|
352
360
|
}.freeze
|
353
361
|
|
354
362
|
def capitalized(field)
|
355
|
-
UPCASED[field] || field.
|
363
|
+
UPCASED[field] || field.split("-").map(&:capitalize).join("-")
|
356
364
|
end
|
357
365
|
end
|
358
366
|
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
|
@@ -11,7 +10,7 @@ module HTTPX
|
|
11
10
|
|
12
11
|
MAX_CONCURRENT_REQUESTS = HTTP2Next::DEFAULT_MAX_CONCURRENT_STREAMS
|
13
12
|
|
14
|
-
Error
|
13
|
+
class Error < Error
|
15
14
|
def initialize(id, code)
|
16
15
|
super("stream #{id} closed with error: #{code}")
|
17
16
|
end
|
@@ -56,7 +55,7 @@ module HTTPX
|
|
56
55
|
|
57
56
|
return :w if !@pending.empty? && can_buffer_more_requests?
|
58
57
|
|
59
|
-
return :w
|
58
|
+
return :w unless @drains.empty?
|
60
59
|
|
61
60
|
if @buffer.empty?
|
62
61
|
return if @streams.empty? && @pings.empty?
|
@@ -218,7 +217,7 @@ module HTTPX
|
|
218
217
|
log(level: 1, color: :yellow) do
|
219
218
|
request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
|
220
219
|
end
|
221
|
-
stream.headers(request.headers.each(extra_headers), end_stream: request.empty?)
|
220
|
+
stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
|
222
221
|
end
|
223
222
|
|
224
223
|
def join_trailers(stream, request)
|
@@ -234,7 +233,7 @@ module HTTPX
|
|
234
233
|
end
|
235
234
|
|
236
235
|
def join_body(stream, request)
|
237
|
-
return if request.empty?
|
236
|
+
return if request.body.empty?
|
238
237
|
|
239
238
|
chunk = @drains.delete(request) || request.drain_body
|
240
239
|
while chunk
|
@@ -249,7 +248,9 @@ module HTTPX
|
|
249
248
|
chunk = next_chunk
|
250
249
|
end
|
251
250
|
|
252
|
-
|
251
|
+
return unless (error = request.drain_error)
|
252
|
+
|
253
|
+
on_stream_refuse(stream, request, error)
|
253
254
|
end
|
254
255
|
|
255
256
|
######
|
@@ -257,8 +258,10 @@ module HTTPX
|
|
257
258
|
######
|
258
259
|
|
259
260
|
def on_stream_headers(stream, request, h)
|
260
|
-
|
261
|
-
|
261
|
+
response = request.response
|
262
|
+
|
263
|
+
if response.is_a?(Response) && response.version == "2.0"
|
264
|
+
on_stream_trailers(stream, response, h)
|
262
265
|
return
|
263
266
|
end
|
264
267
|
|
@@ -274,11 +277,11 @@ module HTTPX
|
|
274
277
|
handle(request, stream) if request.expects?
|
275
278
|
end
|
276
279
|
|
277
|
-
def on_stream_trailers(stream,
|
280
|
+
def on_stream_trailers(stream, response, h)
|
278
281
|
log(color: :yellow) do
|
279
282
|
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{v}" }.join("\n")
|
280
283
|
end
|
281
|
-
|
284
|
+
response.merge_headers(h)
|
282
285
|
end
|
283
286
|
|
284
287
|
def on_stream_data(stream, request, data)
|
@@ -288,11 +291,13 @@ module HTTPX
|
|
288
291
|
end
|
289
292
|
|
290
293
|
def on_stream_refuse(stream, request, error)
|
291
|
-
stream.close
|
292
294
|
on_stream_close(stream, request, error)
|
295
|
+
stream.close
|
293
296
|
end
|
294
297
|
|
295
298
|
def on_stream_close(stream, request, error)
|
299
|
+
return if error == :stream_closed && !@streams.key?(request)
|
300
|
+
|
296
301
|
log(level: 2) { "#{stream.id}: closing stream" }
|
297
302
|
@drains.delete(request)
|
298
303
|
@streams.delete(request)
|
@@ -304,7 +309,7 @@ module HTTPX
|
|
304
309
|
emit(:response, request, response)
|
305
310
|
else
|
306
311
|
response = request.response
|
307
|
-
if response.status == 421
|
312
|
+
if response && response.status == 421
|
308
313
|
ex = MisdirectedRequestError.new(response)
|
309
314
|
ex.set_backtrace(caller)
|
310
315
|
emit(:error, request, ex)
|
@@ -385,22 +390,12 @@ module HTTPX
|
|
385
390
|
end
|
386
391
|
|
387
392
|
def on_pong(ping)
|
388
|
-
if
|
389
|
-
close(:protocol_error, "ping payload did not match")
|
390
|
-
else
|
393
|
+
if @pings.delete(ping.to_s)
|
391
394
|
emit(:pong)
|
395
|
+
else
|
396
|
+
close(:protocol_error, "ping payload did not match")
|
392
397
|
end
|
393
398
|
end
|
394
|
-
|
395
|
-
def respond_to_missing?(meth, *args)
|
396
|
-
@connection.respond_to?(meth, *args) || super
|
397
|
-
end
|
398
|
-
|
399
|
-
def method_missing(meth, *args, &blk)
|
400
|
-
return super unless @connection.respond_to?(meth)
|
401
|
-
|
402
|
-
@connection.__send__(meth, *args, &blk)
|
403
|
-
end
|
404
399
|
end
|
405
400
|
Connection.register "h2", Connection::HTTP2
|
406
401
|
end
|