httpx 0.18.0 → 0.19.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -4
- data/doc/release_notes/0_18_1.md +12 -0
- data/doc/release_notes/0_18_2.md +10 -0
- data/doc/release_notes/0_18_3.md +7 -0
- data/doc/release_notes/0_18_4.md +14 -0
- data/doc/release_notes/0_18_5.md +10 -0
- data/doc/release_notes/0_18_6.md +5 -0
- data/doc/release_notes/0_18_7.md +5 -0
- data/doc/release_notes/0_19_0.md +39 -0
- data/doc/release_notes/0_19_1.md +5 -0
- data/doc/release_notes/0_19_2.md +7 -0
- data/doc/release_notes/0_19_3.md +6 -0
- data/lib/httpx/adapters/faraday.rb +58 -12
- data/lib/httpx/adapters/webmock.rb +71 -59
- data/lib/httpx/altsvc.rb +25 -9
- data/lib/httpx/connection/http1.rb +10 -7
- data/lib/httpx/connection/http2.rb +23 -8
- data/lib/httpx/connection.rb +26 -12
- data/lib/httpx/extensions.rb +16 -0
- data/lib/httpx/headers.rb +0 -2
- data/lib/httpx/io/ssl.rb +4 -0
- data/lib/httpx/io/tcp.rb +27 -6
- data/lib/httpx/io/udp.rb +0 -1
- data/lib/httpx/options.rb +44 -11
- data/lib/httpx/plugins/cookies.rb +5 -7
- data/lib/httpx/plugins/internal_telemetry.rb +1 -1
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +18 -4
- data/lib/httpx/plugins/proxy/http.rb +10 -23
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +35 -15
- data/lib/httpx/plugins/retries.rb +15 -12
- data/lib/httpx/pool.rb +40 -20
- data/lib/httpx/request.rb +1 -1
- data/lib/httpx/resolver/https.rb +32 -42
- data/lib/httpx/resolver/multi.rb +79 -0
- data/lib/httpx/resolver/native.rb +28 -36
- data/lib/httpx/resolver/resolver.rb +95 -0
- data/lib/httpx/resolver/system.rb +175 -19
- data/lib/httpx/resolver.rb +37 -11
- data/lib/httpx/response.rb +4 -2
- data/lib/httpx/selector.rb +7 -0
- data/lib/httpx/session.rb +2 -16
- data/lib/httpx/session_extensions.rb +26 -0
- data/lib/httpx/timers.rb +1 -1
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +3 -0
- data/sig/connection/http1.rbs +5 -2
- data/sig/connection/http2.rbs +5 -2
- data/sig/connection.rbs +1 -0
- data/sig/errors.rbs +8 -0
- data/sig/headers.rbs +0 -2
- data/sig/httpx.rbs +4 -0
- data/sig/options.rbs +10 -7
- data/sig/parser/http1.rbs +14 -5
- data/sig/pool.rbs +17 -9
- data/sig/registry.rbs +3 -0
- data/sig/request.rbs +11 -0
- data/sig/resolver/https.rbs +15 -27
- data/sig/resolver/multi.rbs +7 -0
- data/sig/resolver/native.rbs +3 -12
- data/sig/resolver/resolver.rbs +36 -0
- data/sig/resolver/system.rbs +3 -9
- data/sig/resolver.rbs +12 -10
- data/sig/response.rbs +15 -5
- data/sig/selector.rbs +3 -3
- data/sig/timers.rbs +5 -2
- data/sig/transcoder/chunker.rbs +16 -5
- data/sig/transcoder/json.rbs +5 -0
- data/sig/transcoder.rbs +3 -1
- metadata +31 -5
- data/lib/httpx/resolver/resolver_mixin.rb +0 -75
- data/sig/resolver/resolver_mixin.rbs +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 431fcabfda42f5d6010d903a15c6a83357123c9eef3528a769755c8639f1ec61
|
4
|
+
data.tar.gz: 52512337b7b2081a4ab123f7261dee14adb97da019fc9244b9ddf8e7de53c904
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7777af904a2e5a8f6f34984a69cb022c853483b6634dd631a3fbe92c9005fadb3637a21a214ff1e14ef4729ffa70d68362b703b9ccd25e2904b820fc674dbe6b
|
7
|
+
data.tar.gz: 94d92ad004319ecb3098b5e901921bfaf5d818cbb0e2e87eed8987248d1cc52d0fc70a5dc1dfe1a92faa8eae9e7074c926c0c01563002cb8067af449ae79fd51
|
data/README.md
CHANGED
@@ -45,7 +45,7 @@ response = HTTPX.get("https://nghttp2.org")
|
|
45
45
|
puts response.status #=> 200
|
46
46
|
body = response.body
|
47
47
|
puts body #=> #<HTTPX::Response ...
|
48
|
-
```
|
48
|
+
```
|
49
49
|
|
50
50
|
You can also send as many requests as you want simultaneously:
|
51
51
|
|
@@ -79,7 +79,7 @@ In Ruby, HTTP client implementations are a known cheap commodity. Why this one?
|
|
79
79
|
|
80
80
|
### Concurrency
|
81
81
|
|
82
|
-
This library supports HTTP/2 seamlessly (which means, if the request is secure, and the server support ALPN negotiation AND HTTP/2, the request will be made through HTTP/2). If you pass multiple URIs, and they can utilize the same connection, they will run concurrently in it.
|
82
|
+
This library supports HTTP/2 seamlessly (which means, if the request is secure, and the server support ALPN negotiation AND HTTP/2, the request will be made through HTTP/2). If you pass multiple URIs, and they can utilize the same connection, they will run concurrently in it.
|
83
83
|
|
84
84
|
However if the server supports HTTP/1.1, it will try to use HTTP pipelining, falling back to 1 request at a time if the server doesn't support it (if the server support Keep-Alive connections, it will reuse the same connection).
|
85
85
|
|
@@ -137,13 +137,18 @@ In order to use HTTP/2 under JRuby, [check this link](https://gitlab.com/honeyry
|
|
137
137
|
|
138
138
|
### Known bugs
|
139
139
|
|
140
|
-
Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryderchuck/httpx/issues/36)).
|
140
|
+
* Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryderchuck/httpx/issues/36)).
|
141
|
+
* Using `total_timeout` along with the `:persistent` plugin [does not work as you might expect](https://gitlab.com/honeyryderchuck/httpx/-/wikis/Timeouts#total_timeout).
|
142
|
+
|
143
|
+
## Versioning Policy
|
144
|
+
|
145
|
+
Although 0.x software, `httpx` is considered API-stable and production-ready, i.e. current API or options may be subject to deprecation and emit log warnings, but can only effectively be removed in a major version change.
|
141
146
|
|
142
147
|
## Contributing
|
143
148
|
|
144
149
|
* Discuss your contribution in an issue
|
145
150
|
* Fork it
|
146
151
|
* Make your changes, add some tests
|
147
|
-
* Ensure all tests pass (`bundle exec rake test`)
|
152
|
+
* Ensure all tests pass (`docker-compose -f docker-compose.yml -f docker-compose-ruby-{RUBY_VERSION}.yml run httpx bundle exec rake test`)
|
148
153
|
* Open a Merge Request (that's Pull Request in Github-ish)
|
149
154
|
* Wait for feedback
|
@@ -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.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# 0.18.2
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
* A bug was reported and fixed, whereby a persistent connection with a `:total_timeout` set was triggering the timeout and leaving the process looping indefinitely.
|
6
|
+
|
7
|
+
|
8
|
+
## Chore
|
9
|
+
|
10
|
+
The quirk of using the `:persistent` plugin with `:total_timeout` has been documented: https://gitlab.com/honeyryderchuck/httpx/-/wikis/Timeouts#total_timeout.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# 0.18.3
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
* request bodies eager-loaded from enumerables yield duped partial chunks.
|
6
|
+
|
7
|
+
An error was observed while looking at webmock integration, where requests formed via the multipart plugin were returning an empty string as body. The issue was caused by an optimization on multipart encoder, which reuses the same buffer when reading chunks. Unfortunately, these cannot be yielded the same way via IO.copy_stream, as the same (cleared) buffer will be used to generate the eager-loaded body chunks.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# 0.18.4
|
2
|
+
|
3
|
+
## Improvements
|
4
|
+
|
5
|
+
* faraday adapter: added support for `#on_data` callback in order to support [faraday streaming](https://lostisland.github.io/faraday/usage/streaming).
|
6
|
+
|
7
|
+
* multipart plugin: removed support for file mime type detection using `mime-types`. The reasoning behind it was that `mime-types` uses the filename, which is a very inaccurate detection strategy (ex: an mp4 video will be identified as `application/mp4`, instead of the correct `video/mp4`).
|
8
|
+
* multipart plugin: supported for file mime type detection using `marcel` and `filemagic` was added. Both use the magic header bytes, which is a more accurate strategy for file type detection.
|
9
|
+
|
10
|
+
## Bugfixes
|
11
|
+
|
12
|
+
* webmock adapter has been reimplemented to work with `httpx` plugins (such as the `:retries` plugin). Some other fixes were applied to make it work better under `vcr` (a common `webmock` extension).
|
13
|
+
|
14
|
+
* fixed the URI-related bug which was making requests stall under ruby 3.1 (still not officially testing against it).
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# 0.18.5
|
2
|
+
|
3
|
+
## Improvements
|
4
|
+
|
5
|
+
* ruby 3.1 is now officially supported.
|
6
|
+
* when a user sets a `Host` header for an HTTP/2 request, this will be used in the `:authority` HTTP/2 pseudo-header, instead of silently ignored (mimicking what "curl" does).
|
7
|
+
|
8
|
+
## Bugfixes
|
9
|
+
|
10
|
+
* fixed "throw outside of catch block" error happening when pipelining requests on an HTTP/1 connection and resulting in a timeout.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# 0.19.0
|
2
|
+
|
3
|
+
## Features
|
4
|
+
|
5
|
+
### Happy Eyeballs v2
|
6
|
+
|
7
|
+
When the system supports dual-stack networking, `httpx` implements the Happy Eyeballs v2 algorithm (RFC 8305) to resolve hostnames to both IPv6 and IPv4 addresses while privileging IPv6 connectivity. This is implemented by `httpx` both for the `:native` as well as the `:https` (DoH) resolver (which do not perform address sorting, thereby being "DNS-based load-balancing" friendly), and "outsourced" to `getaddrinfo` when using the `:system` resolver.
|
8
|
+
|
9
|
+
IPv6 connectivity will also be privileged for `/etc/hosts` local DNS (i.e. `localhost` connections will connec to `::1`).
|
10
|
+
|
11
|
+
A new option, `:ip_families`, will also be available (`[Socket::AF_INET6, Socket::AF_INET]` in dual-stack systems). If you'd like to i.e. force IPv4 connectivity, you can do use it (`client = HTTPX.with(ip_families: [Socket::AF_INET])`).
|
12
|
+
|
13
|
+
## Improvements
|
14
|
+
|
15
|
+
### DNS: :system resolver uses getaddrinfo (instead of the resolver lib)
|
16
|
+
|
17
|
+
The `:system` resolver switched to using the `getaddinfo` system function to perform DNS requests. Not only is this call **not** blocking the session event loop anymore (unlike pre-0.19.0 `:system` resolver), it adds a lot of functionality that the stdlib `resolv` library just doesn't support at the moment (such as SRV records).
|
18
|
+
|
19
|
+
### HTTP/2 proxy support
|
20
|
+
|
21
|
+
The `:proxy` plugin handles "prior-knowledge" HTTP/2 proxies.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
HTTPX.plugin(:proxy, fallback_protocol: "h2").with_proxy(uri: "http://http2-proxy:3128").get(...
|
25
|
+
```
|
26
|
+
|
27
|
+
Connection coalescing has also been enabled for proxied connections (also `CONNECT`-tunneled connections).
|
28
|
+
|
29
|
+
### curl-to-httpx
|
30
|
+
|
31
|
+
widget in [project website](https://honeyryderchuck.gitlab.io/httpx/) to turn curl commands into the equivalent `httpx` code.
|
32
|
+
|
33
|
+
## Bugfixes
|
34
|
+
|
35
|
+
* faraday adapter now supports passing session options.
|
36
|
+
* proxy: several fixes which enabled env-var (`HTTP(S)_PROXY`) defined proxy support.
|
37
|
+
* proxy: fixed graceful recovery from proxy tcp connect errors.
|
38
|
+
* several fixes around CNAMEs timeouts with the native resolver.
|
39
|
+
* https resolver is now closed when wrapping session closes (it was left open).
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# 0.19.2
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
* skip resolution delay path for early resolve cases
|
6
|
+
|
7
|
+
when the early resolve path (using IP, /etc/hosts IP, IP from cache) is followed, emit_addresses is called, and in a particular case (dual-stack network but using an IPv4 address), the happy eyeballs resolution delay path was activated when it shouldn't (it's meant to be used only for DNS network requests), and resulted in @pool being called before it was ever set. This simple check ensures that it doesn't happen before it must.
|
@@ -21,6 +21,17 @@ module Faraday
|
|
21
21
|
end
|
22
22
|
# :nocov:
|
23
23
|
|
24
|
+
unless Faraday::RequestOptions.method_defined?(:stream_response?)
|
25
|
+
module RequestOptionsExtensions
|
26
|
+
refine Faraday::RequestOptions do
|
27
|
+
def stream_response?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
using RequestOptionsExtensions
|
33
|
+
end
|
34
|
+
|
24
35
|
module RequestMixin
|
25
36
|
using ::HTTPX::HashExtensions
|
26
37
|
|
@@ -64,6 +75,30 @@ module Faraday
|
|
64
75
|
|
65
76
|
include RequestMixin
|
66
77
|
|
78
|
+
module OnDataPlugin
|
79
|
+
module RequestMethods
|
80
|
+
attr_writer :response_on_data
|
81
|
+
|
82
|
+
def response=(response)
|
83
|
+
super
|
84
|
+
|
85
|
+
return if response.is_a?(::HTTPX::ErrorResponse)
|
86
|
+
|
87
|
+
response.body.on_data = @response_on_data
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
module ResponseBodyMethods
|
92
|
+
attr_writer :on_data
|
93
|
+
|
94
|
+
def write(chunk)
|
95
|
+
return super unless @on_data
|
96
|
+
|
97
|
+
@on_data.call(chunk, chunk.bytesize)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
67
102
|
class Session < ::HTTPX::Session
|
68
103
|
plugin(:compression)
|
69
104
|
plugin(:persistent)
|
@@ -104,7 +139,7 @@ module Faraday
|
|
104
139
|
|
105
140
|
def on_response(&blk)
|
106
141
|
if blk
|
107
|
-
@on_response =
|
142
|
+
@on_response = ->(response) do
|
108
143
|
blk.call(response)
|
109
144
|
end
|
110
145
|
self
|
@@ -137,15 +172,21 @@ module Faraday
|
|
137
172
|
end
|
138
173
|
|
139
174
|
def run
|
140
|
-
requests = @handlers.map { |handler| build_request(handler.env) }
|
141
175
|
env = @handlers.last.env
|
142
176
|
|
143
|
-
proxy_options = { uri: env.request.proxy }
|
144
|
-
|
145
177
|
session = @session.with(options_from_env(env))
|
146
|
-
session = session.plugin(:proxy).with(proxy:
|
178
|
+
session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
|
179
|
+
session = session.plugin(OnDataPlugin) if env.request.stream_response?
|
180
|
+
|
181
|
+
requests = @handlers.map { |handler| session.build_request(*build_request(handler.env)) }
|
147
182
|
|
148
|
-
|
183
|
+
if env.request.stream_response?
|
184
|
+
requests.each do |request|
|
185
|
+
request.response_on_data = env.request.on_data
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
responses = session.request(*requests)
|
149
190
|
Array(responses).each_with_index do |response, index|
|
150
191
|
handler = @handlers[index]
|
151
192
|
handler.on_response.call(response)
|
@@ -162,9 +203,9 @@ module Faraday
|
|
162
203
|
end
|
163
204
|
end
|
164
205
|
|
165
|
-
def initialize(app)
|
206
|
+
def initialize(app, options = {})
|
166
207
|
super(app)
|
167
|
-
@session = Session.new
|
208
|
+
@session = Session.new(options)
|
168
209
|
end
|
169
210
|
|
170
211
|
def call(env)
|
@@ -172,6 +213,7 @@ module Faraday
|
|
172
213
|
if parallel?(env)
|
173
214
|
handler = env[:parallel_manager].enqueue(env)
|
174
215
|
handler.on_response do |response|
|
216
|
+
response.raise_for_status
|
175
217
|
save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
|
176
218
|
response_headers.merge!(response.headers)
|
177
219
|
end
|
@@ -179,11 +221,15 @@ module Faraday
|
|
179
221
|
return handler
|
180
222
|
end
|
181
223
|
|
182
|
-
meth, uri, request_options = build_request(env)
|
183
|
-
|
184
224
|
session = @session.with(options_from_env(env))
|
185
|
-
session = session.plugin(:proxy).with(proxy:
|
186
|
-
|
225
|
+
session = session.plugin(:proxy).with(proxy: { uri: env.request.proxy }) if env.request.proxy
|
226
|
+
session = session.plugin(OnDataPlugin) if env.request.stream_response?
|
227
|
+
|
228
|
+
request = session.build_request(*build_request(env))
|
229
|
+
|
230
|
+
request.response_on_data = env.request.on_data if env.request.stream_response?
|
231
|
+
|
232
|
+
response = session.request(request)
|
187
233
|
response.raise_for_status unless response.is_a?(::HTTPX::Response)
|
188
234
|
save_response(env, response.status, response.body.to_s, response.headers, response.reason) do |response_headers|
|
189
235
|
response_headers.merge!(response.headers)
|
@@ -16,56 +16,8 @@ module WebMock
|
|
16
16
|
# Requests are "hijacked" at the session, before they're distributed to a connection.
|
17
17
|
#
|
18
18
|
module Plugin
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def send_requests(*requests)
|
23
|
-
request_signatures = requests.map do |request|
|
24
|
-
request_signature = _build_webmock_request_signature(request)
|
25
|
-
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
26
|
-
request_signature
|
27
|
-
end
|
28
|
-
|
29
|
-
responses = request_signatures.map do |request_signature|
|
30
|
-
WebMock::StubRegistry.instance.response_for_request(request_signature)
|
31
|
-
end
|
32
|
-
|
33
|
-
real_requests = {}
|
34
|
-
|
35
|
-
requests.each_with_index.each_with_object([request_signatures, responses]) do |(request, idx), (sig_reqs, mock_responses)|
|
36
|
-
if (webmock_response = mock_responses[idx])
|
37
|
-
mock_responses[idx] = _build_from_webmock_response(request, webmock_response)
|
38
|
-
WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, sig_reqs[idx], webmock_response)
|
39
|
-
log { "mocking #{request.uri} with #{mock_responses[idx].inspect}" }
|
40
|
-
elsif WebMock.net_connect_allowed?(sig_reqs[idx].uri)
|
41
|
-
log { "performing #{request.uri}" }
|
42
|
-
real_requests[request] = idx
|
43
|
-
else
|
44
|
-
raise WebMock::NetConnectNotAllowedError, sig_reqs[idx]
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
unless real_requests.empty?
|
49
|
-
reqs = real_requests.keys
|
50
|
-
reqs.zip(super(*reqs)).each do |req, res|
|
51
|
-
idx = real_requests[req]
|
52
|
-
|
53
|
-
if WebMock::CallbackRegistry.any_callbacks?
|
54
|
-
webmock_response = _build_webmock_response(req, res)
|
55
|
-
WebMock::CallbackRegistry.invoke_callbacks(
|
56
|
-
{ lib: :httpx, real_request: true }, request_signatures[idx],
|
57
|
-
webmock_response
|
58
|
-
)
|
59
|
-
end
|
60
|
-
|
61
|
-
responses[idx] = res
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
responses
|
66
|
-
end
|
67
|
-
|
68
|
-
def _build_webmock_request_signature(request)
|
19
|
+
class << self
|
20
|
+
def build_webmock_request_signature(request)
|
69
21
|
uri = WebMock::Util::URI.heuristic_parse(request.uri)
|
70
22
|
uri.path = uri.normalized_path.gsub("[^:]//", "/")
|
71
23
|
|
@@ -77,7 +29,7 @@ module WebMock
|
|
77
29
|
)
|
78
30
|
end
|
79
31
|
|
80
|
-
def
|
32
|
+
def build_webmock_response(_request, response)
|
81
33
|
webmock_response = WebMock::Response.new
|
82
34
|
webmock_response.status = [response.status, HTTP_REASONS[response.status]]
|
83
35
|
webmock_response.body = response.body.to_s
|
@@ -85,10 +37,10 @@ module WebMock
|
|
85
37
|
webmock_response
|
86
38
|
end
|
87
39
|
|
88
|
-
def
|
89
|
-
return
|
40
|
+
def build_from_webmock_response(request, webmock_response)
|
41
|
+
return build_error_response(request, HTTPX::TimeoutError.new(1, "Timed out")) if webmock_response.should_timeout
|
90
42
|
|
91
|
-
return
|
43
|
+
return build_error_response(request, webmock_response.exception) if webmock_response.exception
|
92
44
|
|
93
45
|
response = request.options.response_class.new(request,
|
94
46
|
webmock_response.status[0],
|
@@ -98,10 +50,70 @@ module WebMock
|
|
98
50
|
response
|
99
51
|
end
|
100
52
|
|
101
|
-
def
|
53
|
+
def build_error_response(request, exception)
|
102
54
|
HTTPX::ErrorResponse.new(request, exception, request.options)
|
103
55
|
end
|
104
56
|
end
|
57
|
+
|
58
|
+
module InstanceMethods
|
59
|
+
def build_connection(*)
|
60
|
+
connection = super
|
61
|
+
connection.once(:unmock_connection) do
|
62
|
+
pool.__send__(:resolve_connection, connection)
|
63
|
+
pool.__send__(:unregister_connection, connection) unless connection.addresses
|
64
|
+
end
|
65
|
+
connection
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module ConnectionMethods
|
70
|
+
def initialize(*)
|
71
|
+
super
|
72
|
+
@mocked = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def open?
|
76
|
+
return true if @mocked
|
77
|
+
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
81
|
+
def interests
|
82
|
+
return if @mocked
|
83
|
+
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
def send(request)
|
88
|
+
request_signature = Plugin.build_webmock_request_signature(request)
|
89
|
+
WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
|
90
|
+
|
91
|
+
if (mock_response = WebMock::StubRegistry.instance.response_for_request(request_signature))
|
92
|
+
response = Plugin.build_from_webmock_response(request, mock_response)
|
93
|
+
WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, request_signature, mock_response)
|
94
|
+
log { "mocking #{request.uri} with #{mock_response.inspect}" }
|
95
|
+
request.response = response
|
96
|
+
request.emit(:response, response)
|
97
|
+
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
98
|
+
if WebMock::CallbackRegistry.any_callbacks?
|
99
|
+
request.on(:response) do |resp|
|
100
|
+
unless resp.is_a?(HTTPX::ErrorResponse)
|
101
|
+
webmock_response = Plugin.build_webmock_response(request, resp)
|
102
|
+
WebMock::CallbackRegistry.invoke_callbacks(
|
103
|
+
{ lib: :httpx, real_request: true }, request_signature,
|
104
|
+
webmock_response
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@mocked = false
|
110
|
+
emit(:unmock_connection, self)
|
111
|
+
super
|
112
|
+
else
|
113
|
+
raise WebMock::NetConnectNotAllowedError, request_signature
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
105
117
|
end
|
106
118
|
|
107
119
|
class HttpxAdapter < HttpLibAdapter
|
@@ -109,12 +121,12 @@ module WebMock
|
|
109
121
|
|
110
122
|
class << self
|
111
123
|
def enable!
|
112
|
-
@original_session =
|
124
|
+
@original_session = HTTPX::Session
|
113
125
|
|
114
|
-
webmock_session =
|
126
|
+
webmock_session = HTTPX.plugin(Plugin)
|
115
127
|
|
116
|
-
|
117
|
-
|
128
|
+
HTTPX.send(:remove_const, :Session)
|
129
|
+
HTTPX.send(:const_set, :Session, webmock_session.class)
|
118
130
|
end
|
119
131
|
|
120
132
|
def disable!
|
data/lib/httpx/altsvc.rb
CHANGED
@@ -70,7 +70,7 @@ module HTTPX
|
|
70
70
|
|
71
71
|
scanner = StringScanner.new(altsvc)
|
72
72
|
until scanner.eos?
|
73
|
-
|
73
|
+
alt_service = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
|
74
74
|
|
75
75
|
alt_params = []
|
76
76
|
loop do
|
@@ -80,29 +80,45 @@ module HTTPX
|
|
80
80
|
break if scanner.eos? || scanner.scan(/ *, */)
|
81
81
|
end
|
82
82
|
alt_params = Hash[alt_params.map { |field| field.split("=") }]
|
83
|
-
|
83
|
+
|
84
|
+
alt_proto, alt_authority = alt_service.split("=")
|
85
|
+
alt_origin = parse_altsvc_origin(alt_proto, alt_authority)
|
86
|
+
return unless alt_origin
|
87
|
+
|
88
|
+
yield(alt_origin, alt_params.merge("proto" => alt_proto))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_altsvc_scheme(alt_proto)
|
93
|
+
case alt_proto
|
94
|
+
when "h2c"
|
95
|
+
"http"
|
96
|
+
when "h2"
|
97
|
+
"https"
|
84
98
|
end
|
85
99
|
end
|
86
100
|
|
87
101
|
# :nocov:
|
88
102
|
if RUBY_VERSION < "2.2"
|
89
|
-
def parse_altsvc_origin(alt_origin)
|
90
|
-
|
103
|
+
def parse_altsvc_origin(alt_proto, alt_origin)
|
104
|
+
alt_scheme = parse_altsvc_scheme(alt_proto) or return
|
105
|
+
|
91
106
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
92
107
|
if alt_origin.start_with?(":")
|
93
|
-
alt_origin = "#{
|
108
|
+
alt_origin = "#{alt_scheme}://dummy#{alt_origin}"
|
94
109
|
uri = URI.parse(alt_origin)
|
95
110
|
uri.host = nil
|
96
111
|
uri
|
97
112
|
else
|
98
|
-
URI.parse("#{
|
113
|
+
URI.parse("#{alt_scheme}://#{alt_origin}")
|
99
114
|
end
|
100
115
|
end
|
101
116
|
else
|
102
|
-
def parse_altsvc_origin(alt_origin)
|
103
|
-
|
117
|
+
def parse_altsvc_origin(alt_proto, alt_origin)
|
118
|
+
alt_scheme = parse_altsvc_scheme(alt_proto) or return
|
104
119
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
105
|
-
|
120
|
+
|
121
|
+
URI.parse("#{alt_scheme}://#{alt_origin}")
|
106
122
|
end
|
107
123
|
end
|
108
124
|
# :nocov:
|
@@ -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
|
@@ -182,7 +184,7 @@ module HTTPX
|
|
182
184
|
end
|
183
185
|
|
184
186
|
if @pipelining
|
185
|
-
disable
|
187
|
+
catch(:called) { disable }
|
186
188
|
else
|
187
189
|
@requests.each do |request|
|
188
190
|
emit(:error, request, ex)
|
@@ -295,10 +297,6 @@ module HTTPX
|
|
295
297
|
extra_headers
|
296
298
|
end
|
297
299
|
|
298
|
-
def headline_uri(request)
|
299
|
-
request.path
|
300
|
-
end
|
301
|
-
|
302
300
|
def handle(request)
|
303
301
|
catch(:buffer_full) do
|
304
302
|
request.transition(:headers)
|
@@ -312,9 +310,14 @@ module HTTPX
|
|
312
310
|
end
|
313
311
|
end
|
314
312
|
|
313
|
+
def join_headline(request)
|
314
|
+
"#{request.verb.to_s.upcase} #{request.path} HTTP/#{@version.join(".")}"
|
315
|
+
end
|
316
|
+
|
315
317
|
def join_headers(request)
|
316
|
-
|
317
|
-
|
318
|
+
headline = join_headline(request)
|
319
|
+
@buffer << headline << CRLF
|
320
|
+
log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
|
318
321
|
extra_headers = set_protocol_headers(request)
|
319
322
|
join_headers2(request.headers.each(extra_headers))
|
320
323
|
log { "<- " }
|
@@ -16,6 +16,12 @@ module HTTPX
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
class GoawayError < Error
|
20
|
+
def initialize
|
21
|
+
super(0, :no_error)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
19
25
|
attr_reader :streams, :pending
|
20
26
|
|
21
27
|
def initialize(buffer, options)
|
@@ -152,10 +158,6 @@ module HTTPX
|
|
152
158
|
end
|
153
159
|
end
|
154
160
|
|
155
|
-
def headline_uri(request)
|
156
|
-
request.path
|
157
|
-
end
|
158
|
-
|
159
161
|
def handle(request, stream)
|
160
162
|
catch(:buffer_full) do
|
161
163
|
request.transition(:headers)
|
@@ -207,13 +209,19 @@ module HTTPX
|
|
207
209
|
{
|
208
210
|
":scheme" => request.scheme,
|
209
211
|
":method" => request.verb.to_s.upcase,
|
210
|
-
":path" =>
|
212
|
+
":path" => request.path,
|
211
213
|
":authority" => request.authority,
|
212
214
|
}
|
213
215
|
end
|
214
216
|
|
215
217
|
def join_headers(stream, request)
|
216
218
|
extra_headers = set_protocol_headers(request)
|
219
|
+
|
220
|
+
if request.headers.key?("host")
|
221
|
+
log { "forbidden \"host\" header found (#{request.headers["host"]}), will use it as authority..." }
|
222
|
+
extra_headers[":authority"] = request.headers["host"]
|
223
|
+
end
|
224
|
+
|
217
225
|
log(level: 1, color: :yellow) do
|
218
226
|
request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{v}" }.join("\n")
|
219
227
|
end
|
@@ -302,7 +310,7 @@ module HTTPX
|
|
302
310
|
@drains.delete(request)
|
303
311
|
@streams.delete(request)
|
304
312
|
|
305
|
-
if error
|
313
|
+
if error
|
306
314
|
ex = Error.new(stream.id, error)
|
307
315
|
ex.set_backtrace(caller)
|
308
316
|
response = ErrorResponse.new(request, ex, request.options)
|
@@ -344,9 +352,16 @@ module HTTPX
|
|
344
352
|
|
345
353
|
def on_close(_last_frame, error, _payload)
|
346
354
|
is_connection_closed = @connection.state == :closed
|
347
|
-
if error
|
355
|
+
if error
|
348
356
|
@buffer.clear if is_connection_closed
|
349
|
-
|
357
|
+
if error == :no_error
|
358
|
+
ex = GoawayError.new
|
359
|
+
@pending.unshift(*@streams.keys)
|
360
|
+
@drains.clear
|
361
|
+
@streams.clear
|
362
|
+
else
|
363
|
+
ex = Error.new(0, error)
|
364
|
+
end
|
350
365
|
ex.set_backtrace(caller)
|
351
366
|
handle_error(ex)
|
352
367
|
end
|