httpx 1.6.1 → 1.6.3

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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_6_2.md +11 -0
  3. data/doc/release_notes/1_6_3.md +47 -0
  4. data/lib/httpx/adapters/datadog.rb +15 -11
  5. data/lib/httpx/adapters/sentry.rb +1 -1
  6. data/lib/httpx/connection/http1.rb +9 -9
  7. data/lib/httpx/connection/http2.rb +14 -15
  8. data/lib/httpx/connection.rb +119 -102
  9. data/lib/httpx/extensions.rb +0 -14
  10. data/lib/httpx/io/ssl.rb +1 -1
  11. data/lib/httpx/loggable.rb +12 -2
  12. data/lib/httpx/options.rb +20 -0
  13. data/lib/httpx/plugins/callbacks.rb +15 -1
  14. data/lib/httpx/plugins/digest_auth.rb +1 -1
  15. data/lib/httpx/plugins/proxy/http.rb +37 -9
  16. data/lib/httpx/plugins/response_cache/file_store.rb +1 -0
  17. data/lib/httpx/plugins/response_cache.rb +13 -2
  18. data/lib/httpx/plugins/stream_bidi.rb +15 -6
  19. data/lib/httpx/pool.rb +53 -19
  20. data/lib/httpx/request.rb +3 -13
  21. data/lib/httpx/resolver/https.rb +35 -19
  22. data/lib/httpx/resolver/multi.rb +9 -32
  23. data/lib/httpx/resolver/native.rb +46 -38
  24. data/lib/httpx/resolver/resolver.rb +45 -28
  25. data/lib/httpx/resolver/system.rb +63 -39
  26. data/lib/httpx/selector.rb +35 -20
  27. data/lib/httpx/session.rb +18 -28
  28. data/lib/httpx/transcoder/deflate.rb +13 -8
  29. data/lib/httpx/transcoder/utils/body_reader.rb +1 -2
  30. data/lib/httpx/transcoder/utils/deflater.rb +1 -2
  31. data/lib/httpx/version.rb +1 -1
  32. data/sig/connection.rbs +12 -3
  33. data/sig/loggable.rbs +5 -1
  34. data/sig/options.rbs +5 -1
  35. data/sig/plugins/callbacks.rbs +3 -0
  36. data/sig/plugins/stream_bidi.rbs +3 -5
  37. data/sig/resolver/https.rbs +2 -0
  38. data/sig/resolver/multi.rbs +0 -9
  39. data/sig/resolver/native.rbs +0 -2
  40. data/sig/resolver/resolver.rbs +9 -8
  41. data/sig/resolver/system.rbs +4 -2
  42. data/sig/selector.rbs +2 -0
  43. data/sig/session.rbs +5 -3
  44. metadata +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 947d1ad4fd269f30707292be54687f7be2054147fb3fa471c76395e92c55820e
4
- data.tar.gz: d6e321ace4211e8bf8215eff799b5c98aad627990db5b9b2cc091fe5b1b95df7
3
+ metadata.gz: f49b29ea3703f6f40abe3cd82d455235b0a0b50a694bd8fa55839ac471d32bbb
4
+ data.tar.gz: ce933bb3c35d9434f810d4fc83acb244a1ad0db187123d5f01dcbbbd83622a47
5
5
  SHA512:
6
- metadata.gz: bd87db725b6da121b79f266a8a309816f5afea4ec1890a14720c16a667ea32632fdbdd9d74249b337672315fd2ac16c1488d8aa13f4e031b33751e1625f2c9a5
7
- data.tar.gz: 00027b822dff3985519aba56eb7b3bd28d0d81eacbdeb9d49f6e778023d60ae3eaf1a2469cf11287da12c0dde2a23b1557da33e77310ea6733e03e4f73b822e2
6
+ metadata.gz: 258cb32129840347a1a37633fb6273133187c98636b63ffcc0b8064f39e3b5642d693ee5da5d7776529625fbe4b68b5193b405c2e0658b2c9b1193b49e316bdc
7
+ data.tar.gz: 387397ab1954b6abf6a8a54d8764fb25e786fb1bd293401e8afd57f823464f67210527fee0b9e94b34cc3d2931143a5d94598fb6241f5728df77a8f1e045a5bf
@@ -0,0 +1,11 @@
1
+ # 1.6.2
2
+
3
+ ## Bugfixes
4
+
5
+ * revert of behaviour introduced in 1.6.1 around handling of `:ip_families` for name resolution.
6
+ * when no option is passed, do not assume no IPv6 connectivity if no available non-local IP is found, as the local network may still be reachable under `[::]`.
7
+ * bail out connection if the tcp connection was established but the ssl session failed.
8
+ * when alpn negotiation succeeded, this could still initialize the HTTP/2 connection and put bytes in the buffer, which would cause a busy loop trying to write to a non-open socket.
9
+ * datadog: fix initialization of spans in non-connection related errors
10
+ * past code was relying on the error being around DNS, but other errors could pop; the fix was moving the init time setup early to the session, when a request is first passed ot the associated connection.
11
+ * it can also fail earlier, so provide a workaround for that as well.
@@ -0,0 +1,47 @@
1
+ # 1.6.3
2
+
3
+ ## Features
4
+
5
+ * allow redacting only headers, or only the body, when using `debug_redact: :headers` or `debug_redact: :body` respectively.
6
+
7
+ ## Improvements
8
+
9
+ * `system` resolver now works in a non-blocking manner, initiating the dns query in a separate thread and waiting on the pipe after that (it was blocking the main thread during resolution before).
10
+ * reduce allocation to a sinfle shared option object when headers are passed as a session-level option, like `HTTPX.with(headers: geaders).get(...)`
11
+ * privilege using `String#replace` in buffer operations (instead of "clean-then-append").
12
+ * using `Array#unshift` instead of `Array#concat` in order to ensure that request ordering is respected in the face of an in-between error which requires reconnect-and-resend.
13
+ * replaced more internal callback indirection with plain method calls.
14
+
15
+
16
+ ## Bugfixes
17
+
18
+ * https: prevent modification of the ssl context object when performing a reconnection.
19
+ * compression: do not return early if the decompression buffer yields an empty string (more frequent under jruby 10).
20
+ * response cache: take query params into account when caching or retrieving cached responses.
21
+ * response cache: do not decompress cached responses on body consumption (the response bodies are cached in plaintext).
22
+ * native resolver: pick next timeout associated with the hostname being resolved (and not the hostnames in the queue).
23
+ * pool: assume that, even when signalled that a connection is available, context may be switched to a session which also checks the same connection out, before it's able to pick it up; in such a case, start from the beginning, until the pool timeout expires.
24
+ * session: forego bookkeeping when a connection is coalesced (instead, allow it to be dropped).
25
+ * digest_auth: make sure that an array is sent back if the probe response fails.
26
+ * alt-svc: when alt-svc handshake happens with more in-flight requests, defer termination to when these requests are made.
27
+ * http2: fix use of unexisting var `ex` when processing the connection closed callback.
28
+ * connection: fix potential session dereferencing, which allowed connections to be used across sessions, therefore bypassing needed synchronization and leading to the `undefined method 'after' for nil:NilClass` error.
29
+ * selector: close only selected connections (instead of all selectable connections) when an error occurs during IO readiness wait calls.
30
+ * resolvers: correctly propagate abrupt termination errors to the connection objects waiting for the answer.
31
+ * resolvers: when errors happenm force-close unresolved connections (and ensure they're both pinned to the corresponding session before the error happens, and are unpinned after error is propagated).
32
+ * resolvers: ensure resolvers transition to "closed" state, on all cases, when any error happens.
33
+ * resolvers: ensure that the next hostname is resolved when a timeout happens on the current one.
34
+ * native resolver: fixed duplication of the hostname to resolve in the list of candidates.
35
+ * https resolver: use a `system` resolver to resolve the DoH server hostname (instead of rerouting it to itself).
36
+ * https resolver: skip loop error reporting when error happens outside of it.
37
+ * https resolver: close connection on resolve errors, which prevents it from being around in the pool after termination; also deactivate it after successful use.
38
+ * multi resolver: do not check resolvers back into the pool if it's a multi resolver and the peer is still resolving (and do the check outside of the critical area).
39
+ * sentry adapter: removed usage of deprecated method which has been removed in sentry-ruby 6.0.0.
40
+ * selector: when coalescing connections, pin the current session before merging connections, to prevent it from registering in a selector being used in a different thread, and inadvertedly allowing it to be used across threads.
41
+ * session: fix: always pin connection before early-or-lazy resolution (fixes connection pool accounting under connection coalescing).
42
+
43
+ ## Chores
44
+
45
+ * logging emits a timestamp as well (to monitor timeouts).
46
+ * `:stream_bidi` plugin: extends HTTP2 module by using plugin extensions.
47
+ * connection: remove session/selector references when closing a connection (prevents leaking them beyond the usage scope).
@@ -180,7 +180,7 @@ module Datadog::Tracing
180
180
  end
181
181
 
182
182
  module RequestMethods
183
- attr_reader :init_time
183
+ attr_accessor :init_time
184
184
 
185
185
  # intercepts request initialization to inject the tracing logic.
186
186
  def initialize(*)
@@ -193,26 +193,30 @@ module Datadog::Tracing
193
193
  RequestTracer.call(self)
194
194
  end
195
195
 
196
- def response=(response)
197
- if response.is_a?(::HTTPX::ErrorResponse) && response.error.respond_to?(:connection)
198
- # handles the case when the +error+ happened during name resolution, which means
199
- # that the tracing start point hasn't been triggered yet; in such cases, the approximate
200
- # initial resolving time is collected from the connection, and used as span start time,
201
- # and the tracing object in inserted before the on response callback is called.
202
- @init_time = response.error.connection.init_time
203
- end
196
+ def response=(*)
197
+ # init_time should be set when it's send to a connection.
198
+ # However, there are situations where connection initialization fails.
199
+ # Example is the :ssrf_filter plugin, which raises an error on
200
+ # initialize if the host is an IP which matches against the known set.
201
+ # in such cases, we'll just set here right here.
202
+ @init_time ||= ::Datadog::Core::Utils::Time.now.utc
203
+
204
204
  super
205
205
  end
206
206
  end
207
207
 
208
208
  module ConnectionMethods
209
- attr_reader :init_time
210
-
211
209
  def initialize(*)
212
210
  super
213
211
 
214
212
  @init_time = ::Datadog::Core::Utils::Time.now.utc
215
213
  end
214
+
215
+ def send(request)
216
+ request.init_time ||= @init_time
217
+
218
+ super
219
+ end
216
220
  end
217
221
  end
218
222
 
@@ -32,7 +32,7 @@ module HTTPX::Plugins
32
32
 
33
33
  return unless config.propagate_traces && config.trace_propagation_targets.any? { |target| url.match?(target) }
34
34
 
35
- trace = ::Sentry.get_current_client.generate_sentry_trace(sentry_span)
35
+ trace = sentry_span.to_sentry_trace
36
36
  request.headers[::Sentry::SENTRY_TRACE_HEADER_NAME] = trace if trace
37
37
  end
38
38
 
@@ -44,12 +44,12 @@ module HTTPX
44
44
  @max_requests = @options.max_requests || MAX_REQUESTS
45
45
  @parser.reset!
46
46
  @handshake_completed = false
47
- @pending.concat(@requests) unless @requests.empty?
47
+ @pending.unshift(*@requests)
48
48
  end
49
49
 
50
50
  def close
51
51
  reset
52
- emit(:close, true)
52
+ emit(:close)
53
53
  end
54
54
 
55
55
  def exhausted?
@@ -114,7 +114,7 @@ module HTTPX
114
114
  @parser.http_version.join("."),
115
115
  headers)
116
116
  log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
117
- log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact(v)}" }.join("\n") }
117
+ log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v)}" }.join("\n") }
118
118
 
119
119
  @request.response = response
120
120
  on_complete if response.finished?
@@ -126,7 +126,7 @@ module HTTPX
126
126
  response = @request.response
127
127
  log(level: 2) { "trailer headers received" }
128
128
 
129
- log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact(v.join(", "))}" }.join("\n") }
129
+ log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{log_redact_headers(v.join(", "))}" }.join("\n") }
130
130
  response.merge_headers(h)
131
131
  end
132
132
 
@@ -136,7 +136,7 @@ module HTTPX
136
136
  return unless request
137
137
 
138
138
  log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
139
- log(level: 2, color: :green) { "-> #{log_redact(chunk.inspect)}" }
139
+ log(level: 2, color: :green) { "-> #{log_redact_body(chunk.inspect)}" }
140
140
  response = request.response
141
141
 
142
142
  response << chunk
@@ -182,7 +182,7 @@ module HTTPX
182
182
  end
183
183
 
184
184
  if exhausted?
185
- @pending.concat(@requests)
185
+ @pending.unshift(*@requests)
186
186
  @requests.clear
187
187
 
188
188
  emit(:exhausted)
@@ -236,7 +236,7 @@ module HTTPX
236
236
  when /keep-alive/i
237
237
  if @handshake_completed
238
238
  if @max_requests.zero?
239
- @pending.concat(@requests)
239
+ @pending.unshift(*@requests)
240
240
  @requests.clear
241
241
  emit(:exhausted)
242
242
  end
@@ -360,7 +360,7 @@ module HTTPX
360
360
 
361
361
  while (chunk = request.drain_body)
362
362
  log(color: :green) { "<- DATA: #{chunk.bytesize} bytes..." }
363
- log(level: 2, color: :green) { "<- #{log_redact(chunk.inspect)}" }
363
+ log(level: 2, color: :green) { "<- #{log_redact_body(chunk.inspect)}" }
364
364
  @buffer << chunk
365
365
  throw(:buffer_full, request) if @buffer.full?
366
366
  end
@@ -381,7 +381,7 @@ module HTTPX
381
381
  def join_headers2(headers)
382
382
  headers.each do |field, value|
383
383
  field = capitalized(field)
384
- log(color: :yellow) { "<- HEADER: #{[field, log_redact(value)].join(": ")}" }
384
+ log(color: :yellow) { "<- HEADER: #{[field, log_redact_headers(value)].join(": ")}" }
385
385
  @buffer << "#{field}: #{value}#{CRLF}"
386
386
  end
387
387
  end
@@ -89,7 +89,7 @@ module HTTPX
89
89
  @connection.goaway
90
90
  emit(:timeout, @options.timeout[:close_handshake_timeout])
91
91
  end
92
- emit(:close, true)
92
+ emit(:close)
93
93
  end
94
94
 
95
95
  def empty?
@@ -234,12 +234,12 @@ module HTTPX
234
234
  extra_headers = set_protocol_headers(request)
235
235
 
236
236
  if request.headers.key?("host")
237
- log { "forbidden \"host\" header found (#{log_redact(request.headers["host"])}), will use it as authority..." }
237
+ log { "forbidden \"host\" header found (#{log_redact_headers(request.headers["host"])}), will use it as authority..." }
238
238
  extra_headers[":authority"] = request.headers["host"]
239
239
  end
240
240
 
241
241
  log(level: 1, color: :yellow) do
242
- "\n#{request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact(v)}" }.join("\n")}"
242
+ "\n#{request.headers.merge(extra_headers).each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")}"
243
243
  end
244
244
  stream.headers(request.headers.each(extra_headers), end_stream: request.body.empty?)
245
245
  end
@@ -251,7 +251,7 @@ module HTTPX
251
251
  end
252
252
 
253
253
  log(level: 1, color: :yellow) do
254
- request.trailers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact(v)}" }.join("\n")
254
+ request.trailers.each.map { |k, v| "#{stream.id}: -> HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
255
255
  end
256
256
  stream.headers(request.trailers.each, end_stream: true)
257
257
  end
@@ -279,7 +279,7 @@ module HTTPX
279
279
 
280
280
  def send_chunk(request, stream, chunk, next_chunk)
281
281
  log(level: 1, color: :green) { "#{stream.id}: -> DATA: #{chunk.bytesize} bytes..." }
282
- log(level: 2, color: :green) { "#{stream.id}: -> #{log_redact(chunk.inspect)}" }
282
+ log(level: 2, color: :green) { "#{stream.id}: -> #{log_redact_body(chunk.inspect)}" }
283
283
  stream.data(chunk, end_stream: end_stream?(request, next_chunk))
284
284
  end
285
285
 
@@ -300,7 +300,7 @@ module HTTPX
300
300
  end
301
301
 
302
302
  log(color: :yellow) do
303
- h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact(v)}" }.join("\n")
303
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
304
304
  end
305
305
  _, status = h.shift
306
306
  headers = request.options.headers_class.new(h)
@@ -313,14 +313,14 @@ module HTTPX
313
313
 
314
314
  def on_stream_trailers(stream, response, h)
315
315
  log(color: :yellow) do
316
- h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact(v)}" }.join("\n")
316
+ h.map { |k, v| "#{stream.id}: <- HEADER: #{k}: #{log_redact_headers(v)}" }.join("\n")
317
317
  end
318
318
  response.merge_headers(h)
319
319
  end
320
320
 
321
321
  def on_stream_data(stream, request, data)
322
322
  log(level: 1, color: :green) { "#{stream.id}: <- DATA: #{data.bytesize} bytes..." }
323
- log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact(data.inspect)}" }
323
+ log(level: 2, color: :green) { "#{stream.id}: <- #{log_redact_body(data.inspect)}" }
324
324
  request.response << data
325
325
  end
326
326
 
@@ -387,18 +387,17 @@ module HTTPX
387
387
  end
388
388
  else
389
389
  ex = GoawayError.new(error)
390
+ ex.set_backtrace(caller)
391
+
390
392
  @pending.unshift(*@streams.keys)
391
393
  teardown
392
- end
393
394
 
394
- if ex
395
- ex.set_backtrace(caller)
396
395
  handle_error(ex)
397
396
  end
398
397
  end
399
398
  return unless is_connection_closed && @streams.empty?
400
399
 
401
- emit(:close, is_connection_closed)
400
+ emit(:close) if is_connection_closed
402
401
  end
403
402
 
404
403
  def on_frame_sent(frame)
@@ -409,7 +408,7 @@ module HTTPX
409
408
  when :data
410
409
  frame.merge(payload: frame[:payload].bytesize)
411
410
  when :headers, :ping
412
- frame.merge(payload: log_redact(frame[:payload]))
411
+ frame.merge(payload: log_redact_headers(frame[:payload]))
413
412
  else
414
413
  frame
415
414
  end
@@ -425,7 +424,7 @@ module HTTPX
425
424
  when :data
426
425
  frame.merge(payload: frame[:payload].bytesize)
427
426
  when :headers, :ping
428
- frame.merge(payload: log_redact(frame[:payload]))
427
+ frame.merge(payload: log_redact_headers(frame[:payload]))
429
428
  else
430
429
  frame
431
430
  end
@@ -435,7 +434,7 @@ module HTTPX
435
434
 
436
435
  def on_altsvc(origin, frame)
437
436
  log(level: 2) { "#{frame[:stream]}: altsvc frame was received" }
438
- log(level: 2) { "#{frame[:stream]}: #{log_redact(frame.inspect)}" }
437
+ log(level: 2) { "#{frame[:stream]}: #{log_redact_headers(frame.inspect)}" }
439
438
  alt_origin = URI.parse("#{frame[:proto]}://#{frame[:host]}:#{frame[:port]}")
440
439
  params = { "ma" => frame[:max_age] }
441
440
  emit(:altsvc, origin, alt_origin, origin, params)