httpx 1.7.5 → 1.7.7
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_7_4.md +1 -1
- data/doc/release_notes/1_7_6.md +24 -0
- data/doc/release_notes/1_7_7.md +17 -0
- data/lib/httpx/adapters/datadog.rb +14 -1
- data/lib/httpx/adapters/faraday.rb +0 -10
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/altsvc.rb +4 -2
- data/lib/httpx/connection/http1.rb +52 -43
- data/lib/httpx/connection/http2.rb +23 -16
- data/lib/httpx/connection.rb +80 -43
- data/lib/httpx/io/ssl.rb +24 -12
- data/lib/httpx/io/tcp.rb +18 -12
- data/lib/httpx/io/unix.rb +13 -9
- data/lib/httpx/loggable.rb +1 -1
- data/lib/httpx/options.rb +23 -7
- data/lib/httpx/parser/http1.rb +14 -5
- data/lib/httpx/plugins/auth/digest.rb +6 -0
- data/lib/httpx/plugins/auth.rb +23 -9
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +1 -0
- data/lib/httpx/plugins/cookies/cookie.rb +0 -1
- data/lib/httpx/plugins/digest_auth.rb +3 -1
- data/lib/httpx/plugins/follow_redirects.rb +13 -1
- data/lib/httpx/plugins/h2c.rb +2 -12
- data/lib/httpx/plugins/proxy/http.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +1 -1
- data/lib/httpx/plugins/response_cache.rb +11 -4
- data/lib/httpx/plugins/retries.rb +6 -6
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/tracing.rb +1 -6
- data/lib/httpx/plugins/upgrade/h2.rb +1 -11
- data/lib/httpx/plugins/upgrade.rb +17 -17
- data/lib/httpx/pool.rb +7 -9
- data/lib/httpx/request.rb +28 -3
- data/lib/httpx/resolver/native.rb +1 -1
- data/lib/httpx/response.rb +5 -1
- data/lib/httpx/selector.rb +18 -10
- data/lib/httpx/session.rb +32 -24
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +2 -0
- data/sig/connection/http1.rbs +4 -2
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +9 -2
- data/sig/io/ssl.rbs +1 -0
- data/sig/io/tcp.rbs +2 -2
- data/sig/loggable.rbs +1 -1
- data/sig/options.rbs +8 -3
- data/sig/parser/http1.rbs +1 -1
- data/sig/plugins/auth/basic.rbs +1 -1
- data/sig/plugins/auth/digest.rbs +1 -1
- data/sig/plugins/auth/ntlm.rbs +2 -0
- data/sig/plugins/auth.rbs +5 -2
- data/sig/plugins/follow_redirects.rbs +1 -1
- data/sig/plugins/proxy.rbs +1 -0
- data/sig/plugins/response_cache.rbs +2 -0
- data/sig/plugins/retries.rbs +1 -1
- data/sig/plugins/tracing.rbs +1 -1
- data/sig/pool.rbs +1 -1
- data/sig/request.rbs +6 -0
- data/sig/session.rbs +0 -2
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 64a496153f6b67985861f18f1ab86793d356bfb1f49045346e55c4df8a991881
|
|
4
|
+
data.tar.gz: 449bcbe6d7acf74e744f296dbfdf2530fe411b66bfecfba5552e5f6185465ea0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '02781b0d9ce574816cb07722aa5f22383c75872b2d782ba9bfc651b9b94b5ffb03860f01cfebdd2de5d38d28e40f7388595b751d549f7ec791e5bf4a9fd3b9e9'
|
|
7
|
+
data.tar.gz: 7f5de973f936e7fa6265918d7d8facf2602367c85cbc90c3ad09b92b98c01f34f9f9513a0faff0498f945d12cb41938b232bf74b96a45eb03ab680fd721ed43f
|
data/doc/release_notes/1_7_4.md
CHANGED
|
@@ -17,7 +17,7 @@ You can pass chain several tracers, and callbacks will be relayed to all of them
|
|
|
17
17
|
HTTP.plugin(:tracing).with(tracer: telemetry_platform_tracer).with(tracer: telemetry2_platform_tracer)
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
This was developed to be the foundation on top of which the
|
|
20
|
+
This was developed to be the foundation on top of which the datadog and OTel integrations will be built.
|
|
21
21
|
|
|
22
22
|
## Improvements
|
|
23
23
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# 1.7.6
|
|
2
|
+
|
|
3
|
+
## Improvements
|
|
4
|
+
|
|
5
|
+
* `datadog` adapter: support setting custom `peer.service`.
|
|
6
|
+
* stopped doing `Thread.pass` after checking connections back into the pool. The goal of this was to allow threads potentially waiting on a connection for the same origin to have the opportunity to take it before the same thread could check the same connection out, thereby preventing starvation on limited pool sizes; this however backfired for the common case of unbounded pool sizes (the default), and was specially bad when used for multiple concurrent requests on multiple origins, where avoidable lag was introduced on the whole processing every time a connection was checked back in. Thereby, the initial problem needs to be solved at the mutex implementation of CRuby.
|
|
7
|
+
* connection: after keep alive timeout expired, try writing `PING` frame to the socket as soon as it's emitted (previously, the frame was being buffered and the socket would be probed for readiness before writing it, whereas in most cases this is avoidable).
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Bugfixes
|
|
11
|
+
|
|
12
|
+
* reuse the same resolver instance when resolving multiple concurrent requests in the same option (it was previously instantiating different ones based on option heuristics, such as when requests had different `:origin`).
|
|
13
|
+
* on successful early name resolution (from cache or hosts file, for example), check the resolver back to the pool (the object was being dereferenced).
|
|
14
|
+
* http1 parser: turn buffer back into a string on reset, instead of preserving its current type (in cases where it was a chunked transfer decoder and connection was kept-alive, it was keeping it around and using it to parse the next request, which broke).
|
|
15
|
+
* http1 parser: enable pipelining based on the max concurrent requests, which the connection may have had pre-set to 1 based on prior knowledge of origin or other heuristics.
|
|
16
|
+
* http1 parser: when disabling pipelining, mark incomplete inlined requests as idle before sending them back to the pending queue.
|
|
17
|
+
* http1 and http2 parser: remove references to requests escalating an error; in scenarios of connection reuse (such as when using the `:persistent` plugin), these requests could be, on a transition to idle state, resent even in the absence of an external session reference, causing avoidable overhead or contention leading to timeouts or errors.
|
|
18
|
+
* connection: rescue and ignore exceptions when calling `OpenSSL::SSL::SSLSocket#close` or `Socket#close`.
|
|
19
|
+
* connection: when sending multiple requests into an open HTTP/2 connection which keep alive may have expired, only a single (instead of multiple, as before) `PING` frame will be sent (in the prior state, only the first `PING` was being acknowledged, which made the connection accumulate unacknowledged ping payloads).
|
|
20
|
+
* connection: on handling errors, preventing potential race condition by copying pending requests into a local var before calling `parser.handle_error` (this may trigger a callback which calls `#reset`, which may call `#disconnect`, which would check the connection back into the pool and make it available for another thread, which could change the state of the ivar containing pending requests).
|
|
21
|
+
* connection: skip resetting a connection when idle and having pending requests; this may happen in situation where parsers may have reset-and-back-to-idle the connection to resend failed requests as protocol (such as in the case of failed HTTP/1 pipelinining requests where incomplete requests are to be resent to the connection once pipelining is disabled).
|
|
22
|
+
* connection: raise request timeout errors for the request the connection is currently in, instead of the connection where it was initially sent on and set timeouts on; this happens in situations where the request may be retried and sent to a different connection, which forces the request to keep a reference to the connection it's currently in (and discard it when it no longer needs it to prevent it from being GC'ed).
|
|
23
|
+
* `:auth` plugin: when used alongside the `:retries` plugin for multiple concurrent requests, it was calling the dynamic token generator block once for each request; fixed to call the block only once per retry window, so retrying requests will reuse the same.
|
|
24
|
+
* `:retries` plugin: retry request immediately if `:retry_after` is negative (while the option is validated for numbers, it can't control proc-based calculation).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# 1.7.7
|
|
2
|
+
|
|
3
|
+
## Improvements
|
|
4
|
+
|
|
5
|
+
* ssl cert info will also be redacted when `:debug_redact` option is enabled.
|
|
6
|
+
* fix performance regression of #receive_requests recent loop change (which also improved performance of previous baseline).
|
|
7
|
+
* header/data logs indexed by request (instead of parser).
|
|
8
|
+
|
|
9
|
+
## Bugfixes
|
|
10
|
+
|
|
11
|
+
* proxy: fixed issue when basic auth to proxy failed with 407 and it'd raise NoMethodError when retrying (marked as unretriable now).
|
|
12
|
+
* selector: prevent removal of wrong selectable when previous closed via `#on_close` during selectables traversal.
|
|
13
|
+
* http1: only reset requests which weren't completed (prevent issuing errors twice).
|
|
14
|
+
* http1: forego clearing buffer on reset (avoid deleting potential next request bytes).
|
|
15
|
+
* response_cache: reset cached response age on successful cache revalidation, as per RFC.
|
|
16
|
+
* rate_limiter: do not apply jitter when delay comes from `retry-after` header.
|
|
17
|
+
* digest: digest header parsing issues results in an error response (instead of an exception raised).
|
|
@@ -19,6 +19,7 @@ module Datadog::Tracing
|
|
|
19
19
|
Datadog::Tracing::Contrib::Ext::Metadata::TAG_BASE_SERVICE
|
|
20
20
|
end
|
|
21
21
|
TAG_PEER_HOSTNAME = Datadog::Tracing::Metadata::Ext::TAG_PEER_HOSTNAME
|
|
22
|
+
TAG_PEER_SERVICE = Datadog::Tracing::Metadata::Ext::TAG_PEER_SERVICE
|
|
22
23
|
|
|
23
24
|
TAG_KIND = Datadog::Tracing::Metadata::Ext::TAG_KIND
|
|
24
25
|
TAG_CLIENT = Datadog::Tracing::Metadata::Ext::SpanKind::TAG_CLIENT
|
|
@@ -108,7 +109,9 @@ module Datadog::Tracing
|
|
|
108
109
|
span.set_tag(TAG_PEER_HOSTNAME, uri.host)
|
|
109
110
|
|
|
110
111
|
# Tag as an external peer service
|
|
111
|
-
|
|
112
|
+
if (peer_service = config[:peer_service])
|
|
113
|
+
span.set_tag(TAG_PEER_SERVICE, peer_service)
|
|
114
|
+
end
|
|
112
115
|
|
|
113
116
|
if config[:distributed_tracing]
|
|
114
117
|
propagate_trace_http(
|
|
@@ -210,6 +213,11 @@ module Datadog::Tracing
|
|
|
210
213
|
o.env "DD_TRACE_HTTPX_ANALYTICS_SAMPLE_RATE"
|
|
211
214
|
o.default 1.0
|
|
212
215
|
end
|
|
216
|
+
|
|
217
|
+
option :peer_service do |o|
|
|
218
|
+
o.type :string, nilable: true
|
|
219
|
+
o.env "DD_TRACE_HTTPX_PEER_SERVICE"
|
|
220
|
+
end
|
|
213
221
|
else
|
|
214
222
|
option :enabled do |o|
|
|
215
223
|
o.default { env_to_bool("DD_TRACE_HTTPX_ENABLED", true) }
|
|
@@ -225,6 +233,11 @@ module Datadog::Tracing
|
|
|
225
233
|
o.default { env_to_float(%w[DD_TRACE_HTTPX_ANALYTICS_SAMPLE_RATE DD_HTTPX_ANALYTICS_SAMPLE_RATE], 1.0) }
|
|
226
234
|
o.lazy
|
|
227
235
|
end
|
|
236
|
+
|
|
237
|
+
option :peer_service do |o|
|
|
238
|
+
o.default { env_to_string("DD_TRACE_HTTPX_PEER_SERVICE", nil) }
|
|
239
|
+
o.lazy
|
|
240
|
+
end
|
|
228
241
|
end
|
|
229
242
|
|
|
230
243
|
if defined?(Datadog::Tracing::Contrib::SpanAttributeSchema)
|
|
@@ -182,15 +182,6 @@ module Faraday
|
|
|
182
182
|
@on_response
|
|
183
183
|
end
|
|
184
184
|
end
|
|
185
|
-
|
|
186
|
-
def on_complete(&blk)
|
|
187
|
-
if blk
|
|
188
|
-
@on_complete = blk
|
|
189
|
-
self
|
|
190
|
-
else
|
|
191
|
-
@on_complete
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
185
|
end
|
|
195
186
|
|
|
196
187
|
include RequestMixin
|
|
@@ -224,7 +215,6 @@ module Faraday
|
|
|
224
215
|
Array(responses).each_with_index do |response, index|
|
|
225
216
|
handler = @handlers[index]
|
|
226
217
|
handler.on_response.call(response)
|
|
227
|
-
handler.on_complete.call(handler.env) if handler.on_complete
|
|
228
218
|
end
|
|
229
219
|
end
|
|
230
220
|
end
|
|
@@ -132,7 +132,7 @@ module WebMock
|
|
|
132
132
|
request.transition(:done)
|
|
133
133
|
response.finish!
|
|
134
134
|
request.response = response
|
|
135
|
-
request.
|
|
135
|
+
request.emit_response(response)
|
|
136
136
|
request_signature.headers = request.headers.to_h
|
|
137
137
|
|
|
138
138
|
response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
|
data/lib/httpx/altsvc.rb
CHANGED
|
@@ -10,6 +10,8 @@ module HTTPX
|
|
|
10
10
|
|
|
11
11
|
H2_ALTSVC_SCHEMES = %w[https h2].freeze
|
|
12
12
|
|
|
13
|
+
ALTSVC_IGNORE_IVARS = %i[@ssl].freeze
|
|
14
|
+
|
|
13
15
|
def send(request)
|
|
14
16
|
request.headers["alt-used"] = @origin.authority if @parser && !@write_buffer.full? && match_altsvcs?(request.uri)
|
|
15
17
|
|
|
@@ -35,11 +37,11 @@ module HTTPX
|
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def match_altsvc_options?(uri, options)
|
|
38
|
-
return @options
|
|
40
|
+
return @options.connection_options_match?(options) unless @options.ssl.all? do |k, v|
|
|
39
41
|
v == (k == :hostname ? uri.host : options.ssl[k])
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
@options.
|
|
44
|
+
@options.connection_options_match?(options, ALTSVC_IGNORE_IVARS)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
def altsvc_match?(uri, other_uri)
|
|
@@ -28,7 +28,8 @@ module HTTPX
|
|
|
28
28
|
@version = [1, 1]
|
|
29
29
|
@pending = []
|
|
30
30
|
@requests = []
|
|
31
|
-
@
|
|
31
|
+
@request = nil
|
|
32
|
+
@handshake_completed = @pipelining = false
|
|
32
33
|
end
|
|
33
34
|
|
|
34
35
|
def timeout
|
|
@@ -53,7 +54,12 @@ module HTTPX
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def reset_requests
|
|
56
|
-
@
|
|
57
|
+
@requests.reverse_each do |request|
|
|
58
|
+
next if request.response
|
|
59
|
+
|
|
60
|
+
request.transition(:idle)
|
|
61
|
+
@pending.unshift(request)
|
|
62
|
+
end
|
|
57
63
|
@requests.clear
|
|
58
64
|
end
|
|
59
65
|
|
|
@@ -90,7 +96,7 @@ module HTTPX
|
|
|
90
96
|
return if @requests.include?(request)
|
|
91
97
|
|
|
92
98
|
@requests << request
|
|
93
|
-
@pipelining =
|
|
99
|
+
@pipelining = @max_concurrent_requests > 1 && @requests.size > 1
|
|
94
100
|
end
|
|
95
101
|
|
|
96
102
|
def consume
|
|
@@ -113,30 +119,32 @@ module HTTPX
|
|
|
113
119
|
end
|
|
114
120
|
|
|
115
121
|
def on_headers(h)
|
|
116
|
-
@request = @requests.first
|
|
122
|
+
request = @request = @requests.first
|
|
117
123
|
|
|
118
|
-
return if
|
|
124
|
+
return if request.response
|
|
119
125
|
|
|
120
|
-
log(level: 2) { "headers received" }
|
|
121
|
-
headers =
|
|
122
|
-
response =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
|
|
127
|
-
log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
|
|
126
|
+
request.log(level: 2) { "headers received" }
|
|
127
|
+
headers = request.options.headers_class.new(h)
|
|
128
|
+
response = request.options.response_class.new(request,
|
|
129
|
+
@parser.status_code,
|
|
130
|
+
@parser.http_version.join("."),
|
|
131
|
+
headers)
|
|
132
|
+
request.log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
|
|
133
|
+
request.log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
request.response = response
|
|
130
136
|
on_complete if response.finished?
|
|
131
137
|
end
|
|
132
138
|
|
|
133
139
|
def on_trailers(h)
|
|
134
|
-
|
|
140
|
+
request = @request
|
|
135
141
|
|
|
136
|
-
|
|
137
|
-
log(level: 2) { "trailer headers received" }
|
|
142
|
+
return unless request
|
|
138
143
|
|
|
139
|
-
|
|
144
|
+
response = request.response
|
|
145
|
+
|
|
146
|
+
request.log(level: 2) { "trailer headers received" }
|
|
147
|
+
request.log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v.join(", "))}" }.join("\n") }
|
|
140
148
|
response.merge_headers(h)
|
|
141
149
|
end
|
|
142
150
|
|
|
@@ -145,15 +153,18 @@ module HTTPX
|
|
|
145
153
|
|
|
146
154
|
return unless request
|
|
147
155
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
begin
|
|
157
|
+
request.log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
|
|
158
|
+
request.log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
|
|
159
|
+
|
|
160
|
+
response = request.response
|
|
151
161
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
response << chunk
|
|
163
|
+
rescue StandardError => e
|
|
164
|
+
error_response = ErrorResponse.new(request, e)
|
|
165
|
+
request.response = error_response
|
|
166
|
+
dispatch(request)
|
|
167
|
+
end
|
|
157
168
|
end
|
|
158
169
|
|
|
159
170
|
def on_complete
|
|
@@ -161,13 +172,11 @@ module HTTPX
|
|
|
161
172
|
|
|
162
173
|
return unless request
|
|
163
174
|
|
|
164
|
-
log(level: 2) { "parsing complete" }
|
|
165
|
-
dispatch
|
|
175
|
+
request.log(level: 2) { "parsing complete" }
|
|
176
|
+
dispatch(request)
|
|
166
177
|
end
|
|
167
178
|
|
|
168
|
-
def dispatch
|
|
169
|
-
request = @request
|
|
170
|
-
|
|
179
|
+
def dispatch(request)
|
|
171
180
|
if request.expects?
|
|
172
181
|
@parser.reset!
|
|
173
182
|
return handle(request)
|
|
@@ -217,12 +226,12 @@ module HTTPX
|
|
|
217
226
|
if @pipelining
|
|
218
227
|
catch(:called) { disable }
|
|
219
228
|
else
|
|
220
|
-
@requests.
|
|
229
|
+
while (req = @requests.shift)
|
|
221
230
|
next if request && request == req
|
|
222
231
|
|
|
223
232
|
emit(:error, req, ex)
|
|
224
233
|
end
|
|
225
|
-
@pending.
|
|
234
|
+
while (req = @pending.shift)
|
|
226
235
|
next if request && request == req
|
|
227
236
|
|
|
228
237
|
emit(:error, req, ex)
|
|
@@ -290,7 +299,7 @@ module HTTPX
|
|
|
290
299
|
return if @max_concurrent_requests == 1
|
|
291
300
|
|
|
292
301
|
@requests.each do |r|
|
|
293
|
-
r.transition(:idle)
|
|
302
|
+
r.transition(:idle) if r.response.nil?
|
|
294
303
|
|
|
295
304
|
# when we disable pipelining, we still want to try keep-alive.
|
|
296
305
|
# only when keep-alive with one request fails, do we fallback to
|
|
@@ -359,10 +368,10 @@ module HTTPX
|
|
|
359
368
|
def join_headers(request)
|
|
360
369
|
headline = join_headline(request)
|
|
361
370
|
@buffer << headline << CRLF
|
|
362
|
-
log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
|
|
371
|
+
request.log(color: :yellow) { "<- HEADLINE: #{headline.chomp.inspect}" }
|
|
363
372
|
extra_headers = set_protocol_headers(request)
|
|
364
|
-
join_headers2(request.headers.each(extra_headers))
|
|
365
|
-
log { "<- " }
|
|
373
|
+
join_headers2(request, request.headers.each(extra_headers))
|
|
374
|
+
request.log { "<- " }
|
|
366
375
|
@buffer << CRLF
|
|
367
376
|
end
|
|
368
377
|
|
|
@@ -370,8 +379,8 @@ module HTTPX
|
|
|
370
379
|
return if request.body.empty?
|
|
371
380
|
|
|
372
381
|
while (chunk = request.drain_body)
|
|
373
|
-
log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
|
|
374
|
-
log(level: 2, color: :green) { "<- #{log_redact_body(chunk.inspect)}" }
|
|
382
|
+
request.log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
|
|
383
|
+
request.log(level: 2, color: :green) { "<- #{log_redact_body(chunk.inspect)}" }
|
|
375
384
|
@buffer << chunk
|
|
376
385
|
throw(:buffer_full, request) if @buffer.full?
|
|
377
386
|
end
|
|
@@ -384,15 +393,15 @@ module HTTPX
|
|
|
384
393
|
def join_trailers(request)
|
|
385
394
|
return unless request.trailers? && request.callbacks_for?(:trailers)
|
|
386
395
|
|
|
387
|
-
join_headers2(request.trailers)
|
|
388
|
-
log { "<- " }
|
|
396
|
+
join_headers2(request, request.trailers)
|
|
397
|
+
request.log { "<- " }
|
|
389
398
|
@buffer << CRLF
|
|
390
399
|
end
|
|
391
400
|
|
|
392
|
-
def join_headers2(headers)
|
|
401
|
+
def join_headers2(request, headers)
|
|
393
402
|
headers.each do |field, value|
|
|
394
403
|
field = capitalized(field)
|
|
395
|
-
log(color: :yellow) { "<- HEADER: #{[field, log_redact_headers(value)].join(": ")}" }
|
|
404
|
+
request.log(color: :yellow) { "<- HEADER: #{[field, log_redact_headers(value)].join(": ")}" }
|
|
396
405
|
@buffer << "#{field}: #{value}#{CRLF}"
|
|
397
406
|
end
|
|
398
407
|
end
|
|
@@ -57,29 +57,37 @@ module HTTPX
|
|
|
57
57
|
|
|
58
58
|
return if @buffer.empty?
|
|
59
59
|
|
|
60
|
+
# HTTP/2 GOAWAY frame buffered.
|
|
60
61
|
return :w
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
unless @connection.state == :connected && @handshake_completed
|
|
65
|
+
# HTTP/2 in intermediate state or still completing initialization-
|
|
64
66
|
return @buffer.empty? ? :r : :rw
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
unless @connection.send_buffer.empty?
|
|
70
|
+
# HTTP/2 connection is buffering data chunks and failing to emit DATA frames,
|
|
71
|
+
# most likely because the flow control window is exhausted.
|
|
68
72
|
return :rw unless @buffer.empty?
|
|
69
73
|
|
|
70
74
|
# waiting for WINDOW_UPDATE frames
|
|
71
75
|
return :r
|
|
72
76
|
end
|
|
73
77
|
|
|
78
|
+
# there are pending bufferable requests
|
|
74
79
|
return :w if !@pending.empty? && can_buffer_more_requests?
|
|
75
80
|
|
|
81
|
+
# there are pending frames from the last run
|
|
76
82
|
return :w unless @drains.empty?
|
|
77
83
|
|
|
78
84
|
if @buffer.empty?
|
|
85
|
+
# skip if no more requests or pings to process
|
|
79
86
|
return if @streams.empty? && @pings.empty?
|
|
80
87
|
|
|
81
88
|
:r
|
|
82
89
|
else
|
|
90
|
+
# buffered frames
|
|
83
91
|
:w
|
|
84
92
|
end
|
|
85
93
|
end
|
|
@@ -138,7 +146,7 @@ module HTTPX
|
|
|
138
146
|
settings_ex.set_backtrace(ex.backtrace)
|
|
139
147
|
ex = settings_ex
|
|
140
148
|
end
|
|
141
|
-
@streams.
|
|
149
|
+
while (req, _ = @streams.shift)
|
|
142
150
|
next if request && request == req
|
|
143
151
|
|
|
144
152
|
emit(:error, req, ex)
|
|
@@ -234,11 +242,11 @@ module HTTPX
|
|
|
234
242
|
extra_headers = set_protocol_headers(request)
|
|
235
243
|
|
|
236
244
|
if request.headers.key?("host")
|
|
237
|
-
log { "forbidden \"host\" header found (#{log_redact_headers(request.headers["host"])}), will use it as authority..." }
|
|
245
|
+
request.log { "forbidden \"host\" header found (#{log_redact_headers(request.headers["host"])}), will use it as authority..." }
|
|
238
246
|
extra_headers[":authority"] = request.headers["host"]
|
|
239
247
|
end
|
|
240
248
|
|
|
241
|
-
log(level: 1, color: :yellow) do
|
|
249
|
+
request.log(level: 1, color: :yellow) do
|
|
242
250
|
"\n#{request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")}"
|
|
243
251
|
end
|
|
244
252
|
stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
|
|
@@ -250,7 +258,7 @@ module HTTPX
|
|
|
250
258
|
return
|
|
251
259
|
end
|
|
252
260
|
|
|
253
|
-
log(level: 1, color: :yellow) do
|
|
261
|
+
request.log(level: 1, color: :yellow) do
|
|
254
262
|
request.trailers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
|
|
255
263
|
end
|
|
256
264
|
stream.headers(request.trailers.each, end_stream: true)
|
|
@@ -278,8 +286,8 @@ module HTTPX
|
|
|
278
286
|
end
|
|
279
287
|
|
|
280
288
|
def send_chunk(request, stream, chunk, next_chunk)
|
|
281
|
-
log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
|
|
282
|
-
log(level: 2, color: :green) { "#{stream.id}: -> #{log_redact_body(chunk.inspect)}" }
|
|
289
|
+
request.log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
|
|
290
|
+
request.log(level: 2, color: :green) { "#{stream.id}: -> #{log_redact_body(chunk.inspect)}" }
|
|
283
291
|
stream.data(chunk, end_stream: end_stream?(request, next_chunk))
|
|
284
292
|
end
|
|
285
293
|
|
|
@@ -295,11 +303,11 @@ module HTTPX
|
|
|
295
303
|
response = request.response
|
|
296
304
|
|
|
297
305
|
if response.is_a?(Response) && response.version == "2.0"
|
|
298
|
-
on_stream_trailers(stream, response, h)
|
|
306
|
+
on_stream_trailers(stream, request, response, h)
|
|
299
307
|
return
|
|
300
308
|
end
|
|
301
309
|
|
|
302
|
-
log(color: :yellow) do
|
|
310
|
+
request.log(color: :yellow) do
|
|
303
311
|
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{k == ":status" ? v : log_redact_headers(v)}" }.join("\n")
|
|
304
312
|
end
|
|
305
313
|
_, status = h.shift
|
|
@@ -311,16 +319,16 @@ module HTTPX
|
|
|
311
319
|
handle(request, stream) if request.expects?
|
|
312
320
|
end
|
|
313
321
|
|
|
314
|
-
def on_stream_trailers(stream, response, h)
|
|
315
|
-
log(color: :yellow) do
|
|
322
|
+
def on_stream_trailers(stream, request, response, h)
|
|
323
|
+
request.log(color: :yellow) do
|
|
316
324
|
h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
|
|
317
325
|
end
|
|
318
326
|
response.merge_headers(h)
|
|
319
327
|
end
|
|
320
328
|
|
|
321
329
|
def on_stream_data(stream, request, data)
|
|
322
|
-
log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
|
|
323
|
-
log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact_body(data.inspect)}" }
|
|
330
|
+
request.log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
|
|
331
|
+
request.log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact_body(data.inspect)}" }
|
|
324
332
|
request.response << data
|
|
325
333
|
end
|
|
326
334
|
|
|
@@ -329,14 +337,14 @@ module HTTPX
|
|
|
329
337
|
stream.close
|
|
330
338
|
end
|
|
331
339
|
|
|
332
|
-
def on_stream_half_close(stream,
|
|
340
|
+
def on_stream_half_close(stream, request)
|
|
333
341
|
unless stream.send_buffer.empty?
|
|
334
342
|
stream.send_buffer.clear
|
|
335
343
|
stream.data("", end_stream: true)
|
|
336
344
|
end
|
|
337
345
|
|
|
338
346
|
# TODO: omit log line if response already here
|
|
339
|
-
log(level: 2) { "#{stream.id}: waiting for response..." }
|
|
347
|
+
request.log(level: 2) { "#{stream.id}: waiting for response..." }
|
|
340
348
|
end
|
|
341
349
|
|
|
342
350
|
def on_stream_close(stream, request, error)
|
|
@@ -399,10 +407,9 @@ module HTTPX
|
|
|
399
407
|
ex = GoawayError.new(error)
|
|
400
408
|
ex.set_backtrace(caller)
|
|
401
409
|
|
|
402
|
-
|
|
410
|
+
handle_error(ex)
|
|
403
411
|
teardown
|
|
404
412
|
|
|
405
|
-
handle_error(ex)
|
|
406
413
|
end
|
|
407
414
|
end
|
|
408
415
|
return unless is_connection_closed && @streams.empty?
|