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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_8_1.md +1 -1
  3. data/doc/release_notes/1_8_2.md +4 -4
  4. data/doc/release_notes/1_8_3.md +3 -5
  5. data/doc/release_notes/1_8_4.md +1 -1
  6. data/doc/release_notes/1_9_0.md +75 -0
  7. data/doc/release_notes/1_9_1.md +32 -0
  8. data/lib/httpx/adapters/faraday.rb +1 -1
  9. data/lib/httpx/adapters/webmock.rb +15 -2
  10. data/lib/httpx/altsvc.rb +29 -15
  11. data/lib/httpx/chainable.rb +4 -1
  12. data/lib/httpx/connection/http1.rb +34 -35
  13. data/lib/httpx/connection/http2.rb +146 -33
  14. data/lib/httpx/connection.rb +282 -162
  15. data/lib/httpx/headers.rb +2 -2
  16. data/lib/httpx/io/tcp.rb +42 -21
  17. data/lib/httpx/options.rb +86 -35
  18. data/lib/httpx/parser/http1.rb +3 -6
  19. data/lib/httpx/plugins/auth/digest.rb +1 -2
  20. data/lib/httpx/plugins/auth.rb +11 -2
  21. data/lib/httpx/plugins/aws_sdk_authentication.rb +7 -3
  22. data/lib/httpx/plugins/aws_sigv4.rb +1 -1
  23. data/lib/httpx/plugins/basic_auth.rb +2 -0
  24. data/lib/httpx/plugins/cache.rb +1 -1
  25. data/lib/httpx/plugins/callbacks.rb +8 -6
  26. data/lib/httpx/plugins/circuit_breaker.rb +1 -0
  27. data/lib/httpx/plugins/content_digest.rb +3 -5
  28. data/lib/httpx/plugins/digest_auth.rb +2 -0
  29. data/lib/httpx/plugins/expect.rb +10 -1
  30. data/lib/httpx/plugins/fiber_concurrency.rb +59 -16
  31. data/lib/httpx/plugins/follow_redirects.rb +1 -1
  32. data/lib/httpx/plugins/grpc.rb +21 -0
  33. data/lib/httpx/plugins/h2c.rb +12 -11
  34. data/lib/httpx/plugins/hedging.rb +284 -0
  35. data/lib/httpx/plugins/ntlm_auth.rb +5 -3
  36. data/lib/httpx/plugins/ntlm_v2_auth.rb +5 -3
  37. data/lib/httpx/plugins/oauth.rb +2 -2
  38. data/lib/httpx/plugins/proxy/http.rb +67 -26
  39. data/lib/httpx/plugins/proxy/socks5.rb +4 -4
  40. data/lib/httpx/plugins/proxy/ssh.rb +30 -11
  41. data/lib/httpx/plugins/proxy.rb +68 -54
  42. data/lib/httpx/plugins/push_promise.rb +4 -2
  43. data/lib/httpx/plugins/query.rb +1 -0
  44. data/lib/httpx/plugins/retries.rb +2 -2
  45. data/lib/httpx/plugins/server_sent_events.rb +1 -0
  46. data/lib/httpx/plugins/ssrf_filter.rb +1 -1
  47. data/lib/httpx/plugins/stream.rb +12 -10
  48. data/lib/httpx/plugins/stream_bidi.rb +4 -0
  49. data/lib/httpx/plugins/tracing.rb +99 -11
  50. data/lib/httpx/plugins/upgrade/h2.rb +3 -3
  51. data/lib/httpx/plugins/upgrade.rb +2 -0
  52. data/lib/httpx/plugins/webdav.rb +8 -0
  53. data/lib/httpx/request/body.rb +2 -1
  54. data/lib/httpx/request.rb +6 -6
  55. data/lib/httpx/resolver/https.rb +3 -2
  56. data/lib/httpx/resolver/native.rb +19 -9
  57. data/lib/httpx/resolver/resolver.rb +2 -2
  58. data/lib/httpx/resolver/system.rb +4 -0
  59. data/lib/httpx/resolver.rb +10 -6
  60. data/lib/httpx/response.rb +19 -7
  61. data/lib/httpx/selector.rb +111 -10
  62. data/lib/httpx/session.rb +36 -10
  63. data/lib/httpx/transcoder/json.rb +1 -1
  64. data/lib/httpx/version.rb +1 -1
  65. data/lib/httpx.rb +1 -0
  66. data/sig/altsvc.rbs +3 -3
  67. data/sig/callbacks.rbs +1 -0
  68. data/sig/chainable.rbs +3 -1
  69. data/sig/connection/http1.rbs +4 -2
  70. data/sig/connection/http2.rbs +23 -17
  71. data/sig/connection.rbs +29 -7
  72. data/sig/headers.rbs +1 -1
  73. data/sig/httpx.rbs +2 -0
  74. data/sig/io/tcp.rbs +5 -0
  75. data/sig/options.rbs +43 -12
  76. data/sig/parser/http1.rbs +1 -1
  77. data/sig/plugins/auth/digest.rbs +0 -1
  78. data/sig/plugins/auth.rbs +12 -5
  79. data/sig/plugins/expect.rbs +6 -0
  80. data/sig/plugins/fiber_concurrency.rbs +10 -1
  81. data/sig/plugins/hedging.rbs +44 -0
  82. data/sig/plugins/proxy/http.rbs +9 -2
  83. data/sig/plugins/proxy.rbs +3 -2
  84. data/sig/plugins/retries.rbs +2 -2
  85. data/sig/plugins/stream_bidi.rbs +0 -2
  86. data/sig/plugins/tracing.rbs +22 -5
  87. data/sig/request/body.rbs +1 -1
  88. data/sig/resolver/entry.rbs +2 -2
  89. data/sig/resolver/https.rbs +2 -0
  90. data/sig/resolver/native.rbs +3 -0
  91. data/sig/resolver/resolver.rbs +1 -1
  92. data/sig/resolver/system.rbs +2 -0
  93. data/sig/resolver.rbs +1 -1
  94. data/sig/response.rbs +1 -2
  95. data/sig/selector.rbs +17 -3
  96. data/sig/session.rbs +8 -6
  97. metadata +7 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: daca70f040a0f0afcc413a6eb3b2e586ae56a5405477c0045cf226aaddf5b49c
4
- data.tar.gz: 53910e19b4bf264a2d3cf4aed4298de57c4c6373f868e3d9e187140c15f2ed63
3
+ metadata.gz: 8eaea3d273dcba77ecae2abaf1979fa7ebbfddadf5c8f67b71636aac594b12e6
4
+ data.tar.gz: d4e50760994c7dff6d7ae963427f721ce349add9455055e01fc7074b2a9350ed
5
5
  SHA512:
6
- metadata.gz: 8b19e3219fb672bfbb45c4e5427c68b9f7ba715003f5b39796a2314bd8b991b58c5bc185767e303557159f04d61d84c19523f25b953b604a74753e55859defb8
7
- data.tar.gz: 844bf656335bdbc5599003d6ee9724ff54b05478bfa9de37d5e251acff788154d4d7cea4b866b56c7d4c42d07b1afa8ece6982b02c5236d981843f092ee25ceb
6
+ metadata.gz: ae04971cb261d34dd72b1cd724e66e57321909e087b122357ab4506dfb4955603fc3c2ea54c9e34e9e9dcf5484aa347c89ad8c72a9fe94db26706c6850fc5442
7
+ data.tar.gz: a1e5ed666d7419b80da97449150ee67c95d2b1b9855e77060fff123aeaeaf268fb4caafde3242c9421d9cd042e5df14a088ecad30c837449a9fdee2b3376f6cb
@@ -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 building the authorization header after the probe.
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)
@@ -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 bubble 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, 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 alive flow, as there's no ping and the connection is reestablished.
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.
@@ -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
- 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.
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.
@@ -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 bookkepping 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`).
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) if env.request.stream_response?
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.body.to_s,
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[@ssl].freeze
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
- alt_service = scanner.scan(/[^=]+=("[^"]+"|[^;,]+)/)
129
+ alt_services = [] #: Array[String]
130
130
 
131
- alt_params = []
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 { |field| field.split("=", 2) }]
145
+ alt_params = Hash[alt_params.map do |field|
146
+ field.split("=", 2)
147
+ end]
139
148
 
140
- alt_proto, alt_authority = alt_service.split("=", 2)
141
- alt_origin = parse_altsvc_origin(alt_proto, alt_authority)
142
- return unless alt_origin
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
- yield(alt_origin, alt_params.merge("proto" => alt_proto))
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
@@ -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("#{meth.upcase}", uri, **options) # request("GET", uri, **options)
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[:operation_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
- @requests.reverse_each do |request|
63
- next if request.response
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.join("."),
138
+ @parser.http_version,
136
139
  headers)
137
- request.log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
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
- if response.is_a?(ErrorResponse)
202
- disable
203
- else
204
- manage_connection(request, response)
205
- end
204
+ manage_connection(request, response)
206
205
 
207
206
  if exhausted?
208
- @pending.unshift(*@requests)
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
- @pending.unshift(*@requests)
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