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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_7_4.md +1 -1
  3. data/doc/release_notes/1_7_6.md +24 -0
  4. data/doc/release_notes/1_7_7.md +17 -0
  5. data/lib/httpx/adapters/datadog.rb +14 -1
  6. data/lib/httpx/adapters/faraday.rb +0 -10
  7. data/lib/httpx/adapters/webmock.rb +1 -1
  8. data/lib/httpx/altsvc.rb +4 -2
  9. data/lib/httpx/connection/http1.rb +52 -43
  10. data/lib/httpx/connection/http2.rb +23 -16
  11. data/lib/httpx/connection.rb +80 -43
  12. data/lib/httpx/io/ssl.rb +24 -12
  13. data/lib/httpx/io/tcp.rb +18 -12
  14. data/lib/httpx/io/unix.rb +13 -9
  15. data/lib/httpx/loggable.rb +1 -1
  16. data/lib/httpx/options.rb +23 -7
  17. data/lib/httpx/parser/http1.rb +14 -5
  18. data/lib/httpx/plugins/auth/digest.rb +6 -0
  19. data/lib/httpx/plugins/auth.rb +23 -9
  20. data/lib/httpx/plugins/circuit_breaker/circuit.rb +1 -0
  21. data/lib/httpx/plugins/cookies/cookie.rb +0 -1
  22. data/lib/httpx/plugins/digest_auth.rb +3 -1
  23. data/lib/httpx/plugins/follow_redirects.rb +13 -1
  24. data/lib/httpx/plugins/h2c.rb +2 -12
  25. data/lib/httpx/plugins/proxy/http.rb +1 -1
  26. data/lib/httpx/plugins/proxy.rb +1 -1
  27. data/lib/httpx/plugins/response_cache.rb +11 -4
  28. data/lib/httpx/plugins/retries.rb +6 -6
  29. data/lib/httpx/plugins/ssrf_filter.rb +1 -1
  30. data/lib/httpx/plugins/tracing.rb +1 -6
  31. data/lib/httpx/plugins/upgrade/h2.rb +1 -11
  32. data/lib/httpx/plugins/upgrade.rb +17 -17
  33. data/lib/httpx/pool.rb +7 -9
  34. data/lib/httpx/request.rb +28 -3
  35. data/lib/httpx/resolver/native.rb +1 -1
  36. data/lib/httpx/response.rb +5 -1
  37. data/lib/httpx/selector.rb +18 -10
  38. data/lib/httpx/session.rb +32 -24
  39. data/lib/httpx/version.rb +1 -1
  40. data/sig/altsvc.rbs +2 -0
  41. data/sig/connection/http1.rbs +4 -2
  42. data/sig/connection/http2.rbs +1 -1
  43. data/sig/connection.rbs +9 -2
  44. data/sig/io/ssl.rbs +1 -0
  45. data/sig/io/tcp.rbs +2 -2
  46. data/sig/loggable.rbs +1 -1
  47. data/sig/options.rbs +8 -3
  48. data/sig/parser/http1.rbs +1 -1
  49. data/sig/plugins/auth/basic.rbs +1 -1
  50. data/sig/plugins/auth/digest.rbs +1 -1
  51. data/sig/plugins/auth/ntlm.rbs +2 -0
  52. data/sig/plugins/auth.rbs +5 -2
  53. data/sig/plugins/follow_redirects.rbs +1 -1
  54. data/sig/plugins/proxy.rbs +1 -0
  55. data/sig/plugins/response_cache.rbs +2 -0
  56. data/sig/plugins/retries.rbs +1 -1
  57. data/sig/plugins/tracing.rbs +1 -1
  58. data/sig/pool.rbs +1 -1
  59. data/sig/request.rbs +6 -0
  60. data/sig/session.rbs +0 -2
  61. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e9530bb05303e7e15a05a84a26650fc2dd0ec0c35c11c909b07d8deb499f00f
4
- data.tar.gz: 29043b781e3f12cf18ca80eeb6979833536b659dd13ce76d5312fe20f3b61342
3
+ metadata.gz: 64a496153f6b67985861f18f1ab86793d356bfb1f49045346e55c4df8a991881
4
+ data.tar.gz: 449bcbe6d7acf74e744f296dbfdf2530fe411b66bfecfba5552e5f6185465ea0
5
5
  SHA512:
6
- metadata.gz: dea67bc736ed82b32c6c81257d66ca2dac2fae2cf41cb923fdca28c4c9741e5f57b0b56b3bfa23f6b0990f6faf26d14258b296ef771681d405564e02fafaceb5
7
- data.tar.gz: 13dda593ac028e1d34b6ca0e22dd6a024705bf542da756a013f5da7f0abcccaab76860e80e5d8586ff95d4fe258542a295c004d33866a7986407c6cd4950f9e9
6
+ metadata.gz: '02781b0d9ce574816cb07722aa5f22383c75872b2d782ba9bfc651b9b94b5ffb03860f01cfebdd2de5d38d28e40f7388595b751d549f7ec791e5bf4a9fd3b9e9'
7
+ data.tar.gz: 7f5de973f936e7fa6265918d7d8facf2602367c85cbc90c3ad09b92b98c01f34f9f9513a0faff0498f945d12cb41938b232bf74b96a45eb03ab680fd721ed43f
@@ -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 datadag and OTel integrations will be built.
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
- # span.set_tag(TAG_PEER_SERVICE, span.service)
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.emit(:response, response)
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 == options unless @options.ssl.all? do |k, v|
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.options_equals?(options, Options::REQUEST_BODY_IVARS + %i[@ssl])
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
- @handshake_completed = false
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
- @pending.unshift(*@requests)
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 = true if @requests.size > 1
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 @request.response
124
+ return if request.response
119
125
 
120
- log(level: 2) { "headers received" }
121
- headers = @request.options.headers_class.new(h)
122
- response = @request.options.response_class.new(@request,
123
- @parser.status_code,
124
- @parser.http_version.join("."),
125
- headers)
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
- @request.response = response
135
+ request.response = response
130
136
  on_complete if response.finished?
131
137
  end
132
138
 
133
139
  def on_trailers(h)
134
- return unless @request
140
+ request = @request
135
141
 
136
- response = @request.response
137
- log(level: 2) { "trailer headers received" }
142
+ return unless request
138
143
 
139
- log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v.join(", "))}" }.join("\n") }
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
- log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
149
- log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
150
- response = request.response
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
- response << chunk
153
- rescue StandardError => e
154
- error_response = ErrorResponse.new(request, e)
155
- request.response = error_response
156
- dispatch
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.each do |req|
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.each do |req|
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.each_key do |req|
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, _request)
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
- @pending.unshift(*@streams.keys)
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?