httpx 1.8.4 → 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 +75 -0
- data/doc/release_notes/1_9_1.md +32 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +15 -2
- data/lib/httpx/altsvc.rb +29 -15
- data/lib/httpx/chainable.rb +4 -1
- data/lib/httpx/connection/http1.rb +34 -35
- data/lib/httpx/connection/http2.rb +146 -33
- data/lib/httpx/connection.rb +282 -162
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/tcp.rb +42 -21
- data/lib/httpx/options.rb +86 -35
- data/lib/httpx/parser/http1.rb +3 -6
- data/lib/httpx/plugins/auth/digest.rb +1 -2
- data/lib/httpx/plugins/auth.rb +11 -2
- data/lib/httpx/plugins/aws_sdk_authentication.rb +7 -3
- data/lib/httpx/plugins/aws_sigv4.rb +1 -1
- data/lib/httpx/plugins/basic_auth.rb +2 -0
- data/lib/httpx/plugins/cache.rb +1 -1
- data/lib/httpx/plugins/callbacks.rb +8 -6
- data/lib/httpx/plugins/circuit_breaker.rb +1 -0
- data/lib/httpx/plugins/content_digest.rb +3 -5
- data/lib/httpx/plugins/digest_auth.rb +2 -0
- data/lib/httpx/plugins/expect.rb +10 -1
- data/lib/httpx/plugins/fiber_concurrency.rb +59 -16
- data/lib/httpx/plugins/follow_redirects.rb +1 -1
- data/lib/httpx/plugins/grpc.rb +21 -0
- data/lib/httpx/plugins/h2c.rb +12 -11
- data/lib/httpx/plugins/hedging.rb +284 -0
- data/lib/httpx/plugins/ntlm_auth.rb +5 -3
- data/lib/httpx/plugins/ntlm_v2_auth.rb +5 -3
- data/lib/httpx/plugins/oauth.rb +2 -2
- data/lib/httpx/plugins/proxy/http.rb +67 -26
- data/lib/httpx/plugins/proxy/socks5.rb +4 -4
- data/lib/httpx/plugins/proxy/ssh.rb +30 -11
- data/lib/httpx/plugins/proxy.rb +68 -54
- data/lib/httpx/plugins/push_promise.rb +4 -2
- data/lib/httpx/plugins/query.rb +1 -0
- data/lib/httpx/plugins/retries.rb +2 -2
- data/lib/httpx/plugins/server_sent_events.rb +1 -0
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +12 -10
- data/lib/httpx/plugins/stream_bidi.rb +4 -0
- data/lib/httpx/plugins/tracing.rb +99 -11
- data/lib/httpx/plugins/upgrade/h2.rb +3 -3
- data/lib/httpx/plugins/upgrade.rb +2 -0
- data/lib/httpx/plugins/webdav.rb +8 -0
- data/lib/httpx/request/body.rb +2 -1
- data/lib/httpx/request.rb +6 -6
- 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 +10 -6
- data/lib/httpx/response.rb +19 -7
- data/lib/httpx/selector.rb +111 -10
- data/lib/httpx/session.rb +36 -10
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +1 -0
- data/sig/altsvc.rbs +3 -3
- data/sig/callbacks.rbs +1 -0
- data/sig/chainable.rbs +3 -1
- data/sig/connection/http1.rbs +4 -2
- data/sig/connection/http2.rbs +23 -17
- data/sig/connection.rbs +29 -7
- data/sig/headers.rbs +1 -1
- data/sig/httpx.rbs +2 -0
- data/sig/io/tcp.rbs +5 -0
- data/sig/options.rbs +43 -12
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +0 -1
- data/sig/plugins/auth.rbs +12 -5
- data/sig/plugins/expect.rbs +6 -0
- data/sig/plugins/fiber_concurrency.rbs +10 -1
- data/sig/plugins/hedging.rbs +44 -0
- data/sig/plugins/proxy/http.rbs +9 -2
- data/sig/plugins/proxy.rbs +3 -2
- data/sig/plugins/retries.rbs +2 -2
- data/sig/plugins/stream_bidi.rbs +0 -2
- data/sig/plugins/tracing.rbs +22 -5
- data/sig/request/body.rbs +1 -1
- data/sig/resolver/entry.rbs +2 -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/response.rbs +1 -2
- data/sig/selector.rbs +17 -3
- data/sig/session.rbs +8 -6
- metadata +7 -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`).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# 1.9.0
|
|
2
|
+
|
|
3
|
+
## Features
|
|
4
|
+
|
|
5
|
+
### `:hedging` plugin
|
|
6
|
+
|
|
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
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
http = HTTP.plugin(:hedging)
|
|
11
|
+
http.get("http://www.example.com")
|
|
12
|
+
# sent two times, only the fastest one comes back...
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You can read more about it in https://gitlab.com/os85/httpx/wikis/Hedging .
|
|
16
|
+
|
|
17
|
+
### `:proxy/http` plugin: new options `:proxy_headers` and `:enable_proxy_tunnel`
|
|
18
|
+
|
|
19
|
+
The `:proxy/http` plugin (which gets automatically loaded by the `:proxy` plugin) gains two new options.
|
|
20
|
+
|
|
21
|
+
#### `:proxy_headers`
|
|
22
|
+
|
|
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
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
http = HTTPX.plugin(:proxy).with(proxy_headers: { "x-proxy-1" => "foo" }, headers: { "x-header-1" => "bar" }).get("https://example.com")
|
|
27
|
+
# -> CONNECT example.com HTTP/1.1
|
|
28
|
+
# -> Host: example.com
|
|
29
|
+
# -> X-Proxy-1: foo
|
|
30
|
+
# ...
|
|
31
|
+
# <- 200 OK
|
|
32
|
+
# ...
|
|
33
|
+
# -> GET / HTTP/1.1
|
|
34
|
+
# -> X-Headers-1: bar
|
|
35
|
+
# ...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
#### `:enable_proxy_tunnel`
|
|
39
|
+
|
|
40
|
+
When set to `true`, it forces the establishment of a CONNECT proxy tunnel before sending the request. Bear in mind that this will be the norm for `https://` requests, so only use this in cases where the requests are to be sent to an `http://` URL and a CONNECT proxy tunnel is required.
|
|
41
|
+
|
|
42
|
+
## Improvements
|
|
43
|
+
|
|
44
|
+
* `:request_timeout` will also observe the connect phase, in case a request is sent to an idle connection. This means that, if you're using `:request_timeout`, you no longer need to specify `:connect_timeout`.
|
|
45
|
+
* The same will happen if you're using `:total_request_timeout`.
|
|
46
|
+
* do not require "io/wait" anymore for ruby 3.2 or higher.
|
|
47
|
+
* `:proxy/http` plugin: raise a `HTTPX::ConnectTimeoutError` if there was a timeout error while waiting for the CONNECT request to complete.
|
|
48
|
+
|
|
49
|
+
## Bugfixes
|
|
50
|
+
|
|
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
|
+
* `: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 meanwhile.
|
|
55
|
+
* `:proxy/http` plugin: `:headers` will not be sent anymore to the proxy when establishing the CONNECT proxy tunnel.
|
|
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
|
+
* `: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.
|
|
58
|
+
* `:callbacks` plugin: fix broken visibility of session `#close` method.
|
|
59
|
+
* native resolver: retry dns queries on not-imp and refused errors (treats them as transient resolver errors).
|
|
60
|
+
* `webmock` adapter: fix emission of `:response` callback so that this only happens after the body is fully buffered (which makes the `:callbacks` plugin `.on_response_completed` callback works the same way when and when not stubbed).
|
|
61
|
+
* `:addresses`: fix support for the documented variant where a hostname/ips hash is passed.
|
|
62
|
+
* `http2`: fix interests calculation, so that:
|
|
63
|
+
* a connection expects to read from connections which still have in-transit streams.
|
|
64
|
+
* an HTTP/2 with no available flow control window in its streams will wait for incoming WINDOW_UPDATE frames.
|
|
65
|
+
* `http2`: pause buffering the request body if there's no available control window in the corresponding stream or connection.
|
|
66
|
+
* `http2`: reset the ping timer too if a connection receives a GOAWAY frame while waiting for a PING to be ack'ed.
|
|
67
|
+
* wait for read or write for any operation, considering that `OpenSSL::SSL::SSLSocket` objects may return both `.wait_readable` and `:wait_writable` for reads and writes.
|
|
68
|
+
* when using `:max_requests`, also account for inflight requests when deciding to send it to the parser or the pending queue.
|
|
69
|
+
* fixed `HTTPX::Options#merge` which was not freezing the resulting options object, something which happens when enabling plugins, which made resulting sessions unusable across Ractors.
|
|
70
|
+
* fixed the `Alt-Svc` header parser to be able to parse values with multiple authorities.
|
|
71
|
+
* fixed request params handling so that they are passed to all requests invoked (instead of only the first of them).
|
|
72
|
+
|
|
73
|
+
## Chore
|
|
74
|
+
|
|
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
|
|
|
@@ -133,10 +146,10 @@ module WebMock
|
|
|
133
146
|
request.transition(:done)
|
|
134
147
|
response.finish!
|
|
135
148
|
request.response = response
|
|
149
|
+
response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
|
|
136
150
|
request.emit_response(response)
|
|
137
151
|
request_signature.headers = request.headers.to_h
|
|
138
152
|
|
|
139
|
-
response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
|
|
140
153
|
elsif WebMock.net_connect_allowed?(request_signature.uri)
|
|
141
154
|
if WebMock::CallbackRegistry.any_callbacks?
|
|
142
155
|
request.on(:response) do |resp|
|
data/lib/httpx/altsvc.rb
CHANGED
|
@@ -10,7 +10,7 @@ module HTTPX
|
|
|
10
10
|
|
|
11
11
|
H2_ALTSVC_SCHEMES = %w[https h2].freeze
|
|
12
12
|
|
|
13
|
-
ALTSVC_IGNORE_IVARS = %i[
|
|
13
|
+
ALTSVC_IGNORE_IVARS = %i[ssl].freeze
|
|
14
14
|
|
|
15
15
|
def send(request)
|
|
16
16
|
request.headers["alt-used"] = @origin.authority if @parser && !@write_buffer.full? && match_altsvcs?(request.uri)
|
|
@@ -30,7 +30,7 @@ module HTTPX
|
|
|
30
30
|
# +uri+
|
|
31
31
|
def match_altsvcs?(uri)
|
|
32
32
|
@origins.any? { |origin| altsvc_match?(uri, origin) } ||
|
|
33
|
-
AltSvc.cached_altsvc(@origin).any? do |altsvc|
|
|
33
|
+
AltSvc.cached_altsvc(@origin.to_s).any? do |altsvc|
|
|
34
34
|
origin = altsvc["origin"]
|
|
35
35
|
altsvc_match?(origin, uri.origin)
|
|
36
36
|
end
|
|
@@ -115,33 +115,45 @@ module HTTPX
|
|
|
115
115
|
return
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
parse(altsvc) do |alt_origin, alt_params|
|
|
118
|
+
parse(altsvc, host: host) do |alt_origin, alt_params|
|
|
119
119
|
alt_origin.host ||= host
|
|
120
120
|
yield(alt_origin, origin, alt_params)
|
|
121
121
|
end
|
|
122
122
|
end
|
|
123
123
|
|
|
124
|
-
def parse(altsvc)
|
|
125
|
-
return enum_for(__method__, altsvc) unless block_given?
|
|
124
|
+
def parse(altsvc, host: nil)
|
|
125
|
+
return enum_for(__method__, altsvc, host: host) unless block_given?
|
|
126
126
|
|
|
127
127
|
scanner = StringScanner.new(altsvc)
|
|
128
128
|
until scanner.eos?
|
|
129
|
-
|
|
129
|
+
alt_services = [] #: Array[String]
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
while (alt_service = scanner.scan(/[^=]+=("[^"]+")/))
|
|
132
|
+
alt_services << alt_service
|
|
133
|
+
scanner.skip(/[, ]+/)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
scanner.skip(/;/)
|
|
137
|
+
|
|
138
|
+
alt_params = [] #: Array[String]
|
|
132
139
|
loop do
|
|
133
140
|
alt_param = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
|
|
134
141
|
alt_params << alt_param.strip if alt_param
|
|
135
142
|
scanner.skip(/;/)
|
|
136
143
|
break if scanner.eos? || scanner.scan(/ *, */)
|
|
137
144
|
end
|
|
138
|
-
alt_params = Hash[alt_params.map
|
|
145
|
+
alt_params = Hash[alt_params.map do |field|
|
|
146
|
+
field.split("=", 2)
|
|
147
|
+
end]
|
|
139
148
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
alt_services.each do |alt_service|
|
|
150
|
+
alt_proto, alt_authority = alt_service.split("=", 2)
|
|
151
|
+
alt_origin = parse_altsvc_origin(alt_proto, alt_authority, host: host)
|
|
143
152
|
|
|
144
|
-
|
|
153
|
+
next unless alt_origin
|
|
154
|
+
|
|
155
|
+
yield(alt_origin, alt_params.merge("proto" => alt_proto))
|
|
156
|
+
end
|
|
145
157
|
end
|
|
146
158
|
end
|
|
147
159
|
|
|
@@ -149,19 +161,21 @@ module HTTPX
|
|
|
149
161
|
case alt_proto
|
|
150
162
|
when "h2c"
|
|
151
163
|
"http"
|
|
152
|
-
when "h2"
|
|
164
|
+
when "h2", "quic", /^h3/
|
|
153
165
|
"https"
|
|
154
166
|
end
|
|
155
167
|
end
|
|
156
168
|
|
|
157
|
-
def parse_altsvc_origin(alt_proto, alt_origin)
|
|
169
|
+
def parse_altsvc_origin(alt_proto, alt_origin, host: nil)
|
|
158
170
|
alt_scheme = parse_altsvc_scheme(alt_proto)
|
|
159
171
|
|
|
160
172
|
return unless alt_scheme
|
|
161
173
|
|
|
162
174
|
alt_origin = alt_origin[1..-2] if alt_origin.start_with?("\"") && alt_origin.end_with?("\"")
|
|
163
175
|
|
|
164
|
-
URI.parse("#{alt_scheme}://#{alt_origin}")
|
|
176
|
+
uri = URI.parse("#{alt_scheme}://#{alt_origin}")
|
|
177
|
+
uri.host = host if uri.host.nil? || uri.host.empty?
|
|
178
|
+
uri
|
|
165
179
|
end
|
|
166
180
|
end
|
|
167
181
|
end
|
data/lib/httpx/chainable.rb
CHANGED
|
@@ -5,9 +5,11 @@ module HTTPX
|
|
|
5
5
|
# delegates to a default session when extended.
|
|
6
6
|
module Chainable
|
|
7
7
|
%w[head get post put delete trace options connect patch].each do |meth|
|
|
8
|
+
http_verb = meth.upcase
|
|
8
9
|
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
|
10
|
+
# sends a #{http_verb} request for the given +uri+, using +options+ for each.
|
|
9
11
|
def #{meth}(*uri, **options) # def get(*uri, **options)
|
|
10
|
-
request("#{
|
|
12
|
+
request("#{http_verb}", uri, **options) # request("GET", uri, **options)
|
|
11
13
|
end # end
|
|
12
14
|
MOD
|
|
13
15
|
end
|
|
@@ -17,6 +19,7 @@ module HTTPX
|
|
|
17
19
|
branch(default_options).request(...)
|
|
18
20
|
end
|
|
19
21
|
|
|
22
|
+
# returns a new session with the "Accept" header set to +type+.
|
|
20
23
|
def accept(type)
|
|
21
24
|
with(headers: { "accept" => String(type) })
|
|
22
25
|
end
|
|
@@ -20,21 +20,21 @@ 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
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def timeout
|
|
37
|
-
@options.timeout
|
|
37
|
+
@options.timeout.operation_timeout
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def interests
|
|
@@ -48,28 +48,31 @@ module HTTPX
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def reset
|
|
51
|
-
if @ping_timer
|
|
52
|
-
@ping_timer.cancel
|
|
53
|
-
@ping_timer = nil
|
|
54
|
-
end
|
|
55
51
|
@max_requests = @options.max_requests || MAX_REQUESTS
|
|
56
52
|
@parser.reset!
|
|
57
53
|
@handshake_completed = false
|
|
58
|
-
reset_requests
|
|
59
54
|
end
|
|
60
55
|
|
|
56
|
+
# move incompleted requests back to pending queue.
|
|
61
57
|
def reset_requests
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
64
64
|
|
|
65
65
|
request.transition(:idle)
|
|
66
|
-
@pending.unshift(request)
|
|
67
66
|
end
|
|
67
|
+
# drop completed requests
|
|
68
|
+
@requests.shift(completed_idx)
|
|
69
|
+
@pending.unshift(*@requests)
|
|
68
70
|
@requests.clear
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
def close
|
|
72
74
|
reset
|
|
75
|
+
reset_requests
|
|
73
76
|
emit(:close)
|
|
74
77
|
end
|
|
75
78
|
|
|
@@ -92,16 +95,16 @@ module HTTPX
|
|
|
92
95
|
@parser << data
|
|
93
96
|
end
|
|
94
97
|
|
|
95
|
-
def send(request)
|
|
96
|
-
unless @max_requests.positive?
|
|
97
|
-
@pending << request
|
|
98
|
-
return
|
|
98
|
+
def send(request, head = false) # rubocop:disable Naming/PredicateMethod
|
|
99
|
+
unless @max_requests.positive? && @max_requests > @requests.size
|
|
100
|
+
head ? @pending.unshift(request) : @pending << request
|
|
101
|
+
return false
|
|
99
102
|
end
|
|
100
103
|
|
|
101
|
-
return if @requests.include?(request)
|
|
102
|
-
|
|
103
104
|
@requests << request
|
|
104
105
|
@pipelining = @max_concurrent_requests > 1 && @requests.size > 1
|
|
106
|
+
emit(:request_accepted, request)
|
|
107
|
+
true
|
|
105
108
|
end
|
|
106
109
|
|
|
107
110
|
def consume
|
|
@@ -132,9 +135,9 @@ module HTTPX
|
|
|
132
135
|
headers = request.options.headers_class.new(h)
|
|
133
136
|
response = request.options.response_class.new(request,
|
|
134
137
|
@parser.status_code,
|
|
135
|
-
@parser.http_version
|
|
138
|
+
@parser.http_version,
|
|
136
139
|
headers)
|
|
137
|
-
request.log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version
|
|
140
|
+
request.log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version}" }
|
|
138
141
|
request.log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
|
|
139
142
|
|
|
140
143
|
if response.content_length && response.content_length > request.options.max_response_body_size
|
|
@@ -187,7 +190,7 @@ module HTTPX
|
|
|
187
190
|
|
|
188
191
|
@request = nil
|
|
189
192
|
@requests.shift
|
|
190
|
-
response = request.response
|
|
193
|
+
response = request.response #: Response
|
|
191
194
|
emit(:response, request, response)
|
|
192
195
|
|
|
193
196
|
if @parser.upgrade?
|
|
@@ -198,19 +201,16 @@ module HTTPX
|
|
|
198
201
|
|
|
199
202
|
@parser.reset!
|
|
200
203
|
@max_requests -= 1
|
|
201
|
-
|
|
202
|
-
disable
|
|
203
|
-
else
|
|
204
|
-
manage_connection(request, response)
|
|
205
|
-
end
|
|
204
|
+
manage_connection(request, response)
|
|
206
205
|
|
|
207
206
|
if exhausted?
|
|
208
|
-
|
|
209
|
-
@requests.clear
|
|
207
|
+
reset_requests
|
|
210
208
|
|
|
211
209
|
emit(:exhausted)
|
|
210
|
+
elsif @requests.any?
|
|
211
|
+
consume
|
|
212
212
|
else
|
|
213
|
-
send(@pending.shift) unless @pending.empty?
|
|
213
|
+
send(@pending.shift, true) unless @pending.empty?
|
|
214
214
|
end
|
|
215
215
|
end
|
|
216
216
|
|
|
@@ -245,13 +245,16 @@ module HTTPX
|
|
|
245
245
|
def ping
|
|
246
246
|
reset
|
|
247
247
|
emit(:reset)
|
|
248
|
-
emit(:exhausted)
|
|
249
248
|
end
|
|
250
249
|
|
|
251
250
|
def waiting_for_ping?
|
|
252
251
|
false
|
|
253
252
|
end
|
|
254
253
|
|
|
254
|
+
def no_more_requests?
|
|
255
|
+
@requests.empty?
|
|
256
|
+
end
|
|
257
|
+
|
|
255
258
|
private
|
|
256
259
|
|
|
257
260
|
def manage_connection(request, response)
|
|
@@ -260,8 +263,7 @@ module HTTPX
|
|
|
260
263
|
when /keep-alive/i
|
|
261
264
|
if @handshake_completed
|
|
262
265
|
if @max_requests.zero?
|
|
263
|
-
|
|
264
|
-
@requests.clear
|
|
266
|
+
reset_requests
|
|
265
267
|
emit(:exhausted)
|
|
266
268
|
end
|
|
267
269
|
return
|
|
@@ -275,10 +277,7 @@ module HTTPX
|
|
|
275
277
|
end]
|
|
276
278
|
@max_requests = parameters["max"].to_i - 1 if parameters.key?("max")
|
|
277
279
|
|
|
278
|
-
if parameters.key?("timeout")
|
|
279
|
-
keep_alive_timeout = parameters["timeout"].to_i
|
|
280
|
-
emit(:timeout, keep_alive_timeout)
|
|
281
|
-
end
|
|
280
|
+
emit(:keep_alive_timeout, parameters["timeout"].to_i) if parameters.key?("timeout")
|
|
282
281
|
@handshake_completed = true
|
|
283
282
|
when /close/i
|
|
284
283
|
disable
|