httpx 1.2.3 → 1.2.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8803154870d8bd0f45f67ce543072fcc42d789400ca0c9251d3ea99f5f214562
4
- data.tar.gz: 87024f70caa91f93711b6a185a8e3dc7b8987d695472abd13a5a4cb92243d37c
3
+ metadata.gz: e80b08847467e6ba67e57afef2787c2c617167624ae510e255dd23998b25210e
4
+ data.tar.gz: b11ea8d914872689bc1710680c9962b0362c3bf28b62f6e7209f7e2a54a34674
5
5
  SHA512:
6
- metadata.gz: ba28ac993c5b17d4d13db24240214a40cfbae5eac5fad40e6420e42725356a289ee33fcb02492bc382d2c26ce93a0696acf7f5f8d64ddf430136ee829f717b70
7
- data.tar.gz: 93c174e3d210681f1c76127f7a3a69adf43d8a5b3c31432ae0b6e08abd96ca7d189399081efeed710f8c43566e3290931d04b2c38df7de0251508933b2fca129
6
+ metadata.gz: 3fc7994fd3b460e267bce129fdcf6536d844fd5dd9695833c4075028cf128fe954c599ee96eacdf722756c6615c703c96f2df44628e34cd1964fee9f3f92e5e9
7
+ data.tar.gz: 04c4ad1bcf05a3ee96de206a05c294f29f90fa0698fb094b982027cb09db8969a99bd7a324af23dcec18d227fe9708947211fc31ae9e4cddfb4b4360df94aa99
@@ -0,0 +1,8 @@
1
+ # 1.2.4
2
+
3
+ ## Bugfixes
4
+
5
+ * fixed issue related to inability to buffer payload to error responses (which may happen on certain error handling situations).
6
+ * fixed recovery from a lost persistent connection leaving process due to ping being sent while still marked as inactive.
7
+ * fixed datadog integration, which was not generating new spans on retried requests (when `:retries` plugin is enabled).
8
+ * fixed splitting strings into key value pairs in cases where the value would contain a "=", such as in certain base64 payloads.
@@ -0,0 +1,7 @@
1
+ # 1.2.5
2
+
3
+ ## Bugfixes
4
+
5
+ * fix for usage of correct `last-modified` header in `response_cache` plugin.
6
+ * fix usage of decoding helper methods (i.e. `response.json`) with `response_cache` plugin.
7
+ * `stream` plugin: reverted back to yielding buffered payloads for streamed responses (broke `down` integration)
@@ -7,6 +7,8 @@ require "datadog/tracing/contrib/patcher"
7
7
  module Datadog::Tracing
8
8
  module Contrib
9
9
  module HTTPX
10
+ DATADOG_VERSION = defined?(::DDTrace) ? ::DDTrace::VERSION : ::Datadog::VERSION
11
+
10
12
  METADATA_MODULE = Datadog::Tracing::Metadata
11
13
 
12
14
  TYPE_OUTBOUND = Datadog::Tracing::Metadata::Ext::HTTP::TYPE_OUTBOUND
@@ -22,9 +24,10 @@ module Datadog::Tracing
22
24
 
23
25
  # HTTPX Datadog Plugin
24
26
  #
25
- # Enables tracing for httpx requests. A span will be created for each individual requests,
26
- # and it'll trace since the moment it is fed to the connection, until the moment the response is
27
- # fed back to the session.
27
+ # Enables tracing for httpx requests.
28
+ #
29
+ # A span will be created for each request transaction; the span is created lazily only when
30
+ # receiving a response, and it is fed the start time stored inside the tracer object.
28
31
  #
29
32
  module Plugin
30
33
  class RequestTracer
@@ -32,82 +35,174 @@ module Datadog::Tracing
32
35
 
33
36
  SPAN_REQUEST = "httpx.request"
34
37
 
38
+ # initializes the tracer object on the +request+.
35
39
  def initialize(request)
36
40
  @request = request
41
+ @start_time = nil
42
+
43
+ # request objects are reused, when already buffered requests get rerouted to a different
44
+ # connection due to connection issues, or when they already got a response, but need to
45
+ # be retried. In such situations, the original span needs to be extended for the former,
46
+ # while a new is required for the latter.
47
+ request.on(:idle) { reset }
48
+ # the span is initialized when the request is buffered in the parser, which is the closest
49
+ # one gets to actually sending the request.
50
+ request.on(:headers) { call }
37
51
  end
38
52
 
39
- def call
40
- return unless Datadog::Tracing.enabled?
53
+ # sets up the span start time, while preparing the on response callback.
54
+ def call(*args)
55
+ return if @start_time
56
+
57
+ start(*args)
58
+
59
+ @request.once(:response, &method(:finish))
60
+ end
61
+
62
+ private
63
+
64
+ # just sets the span init time. It can be passed a +start_time+ in cases where
65
+ # this is collected outside the request transaction.
66
+ def start(start_time = now)
67
+ @start_time = start_time
68
+ end
69
+
70
+ # resets the start time for already finished request transactions.
71
+ def reset
72
+ return unless @start_time
73
+
74
+ start
75
+ end
41
76
 
42
- @request.on(:response, &method(:finish))
77
+ # creates the span from the collected +@start_time+ to what the +response+ state
78
+ # contains. It also resets internal state to allow this object to be reused.
79
+ def finish(response)
80
+ return unless @start_time
81
+
82
+ span = initialize_span
43
83
 
84
+ return unless span
85
+
86
+ if response.is_a?(::HTTPX::ErrorResponse)
87
+ span.set_error(response.error)
88
+ else
89
+ span.set_tag(TAG_STATUS_CODE, response.status.to_s)
90
+
91
+ span.set_error(::HTTPX::HTTPError.new(response)) if response.status >= 400 && response.status <= 599
92
+ end
93
+
94
+ span.finish
95
+ ensure
96
+ @start_time = nil
97
+ end
98
+
99
+ # return a span initialized with the +@request+ state.
100
+ def initialize_span
44
101
  verb = @request.verb
45
102
  uri = @request.uri
46
103
 
47
- @span = Datadog::Tracing.trace(
48
- SPAN_REQUEST,
49
- service: service_name(@request.uri.host, configuration, Datadog.configuration_for(self)),
50
- span_type: TYPE_OUTBOUND
51
- )
104
+ span = create_span(@request)
52
105
 
53
- @span.resource = verb
106
+ span.resource = verb
54
107
 
55
108
  # Add additional request specific tags to the span.
56
109
 
57
- @span.set_tag(TAG_URL, @request.path)
58
- @span.set_tag(TAG_METHOD, verb)
110
+ span.set_tag(TAG_URL, @request.path)
111
+ span.set_tag(TAG_METHOD, verb)
59
112
 
60
- @span.set_tag(TAG_TARGET_HOST, uri.host)
61
- @span.set_tag(TAG_TARGET_PORT, uri.port.to_s)
113
+ span.set_tag(TAG_TARGET_HOST, uri.host)
114
+ span.set_tag(TAG_TARGET_PORT, uri.port.to_s)
62
115
 
63
116
  # Tag as an external peer service
64
- @span.set_tag(TAG_PEER_SERVICE, @span.service)
117
+ span.set_tag(TAG_PEER_SERVICE, span.service)
65
118
 
66
- Datadog::Tracing::Propagation::HTTP.inject!(Datadog::Tracing.active_trace,
67
- @request.headers) if @configuration[:distributed_tracing]
119
+ if configuration[:distributed_tracing]
120
+ propagate_trace_http(
121
+ Datadog::Tracing.active_trace.to_digest,
122
+ @request.headers
123
+ )
124
+ end
68
125
 
69
126
  # Set analytics sample rate
70
- if Contrib::Analytics.enabled?(@configuration[:analytics_enabled])
71
- Contrib::Analytics.set_sample_rate(@span, @configuration[:analytics_sample_rate])
127
+ if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
128
+ Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
72
129
  end
130
+
131
+ span
73
132
  rescue StandardError => e
74
133
  Datadog.logger.error("error preparing span for http request: #{e}")
75
134
  Datadog.logger.error(e.backtrace)
76
135
  end
77
136
 
78
- def finish(response)
79
- return unless @span
137
+ def now
138
+ ::Datadog::Core::Utils::Time.now.utc
139
+ end
80
140
 
81
- if response.is_a?(::HTTPX::ErrorResponse)
82
- @span.set_error(response.error)
83
- else
84
- @span.set_tag(TAG_STATUS_CODE, response.status.to_s)
141
+ def configuration
142
+ @configuration ||= Datadog.configuration.tracing[:httpx, @request.uri.host]
143
+ end
85
144
 
86
- @span.set_error(::HTTPX::HTTPError.new(response)) if response.status >= 400 && response.status <= 599
145
+ if Gem::Version.new(DATADOG_VERSION::STRING) >= Gem::Version.new("2.0.0.beta1")
146
+ def propagate_trace_http(digest, headers)
147
+ Datadog::Tracing::Contrib::HTTP.inject(digest, headers)
87
148
  end
88
149
 
89
- @span.finish
90
- end
91
-
92
- private
150
+ def create_span(request)
151
+ Datadog::Tracing.trace(
152
+ SPAN_REQUEST,
153
+ service: service_name(request.uri.host, configuration, Datadog.configuration_for(self)),
154
+ type: TYPE_OUTBOUND,
155
+ start_time: @start_time
156
+ )
157
+ end
158
+ else
159
+ def propagate_trace_http(digest, headers)
160
+ Datadog::Tracing::Propagation::HTTP.inject!(digest, headers)
161
+ end
93
162
 
94
- def configuration
95
- @configuration ||= Datadog.configuration.tracing[:httpx, @request.uri.host]
163
+ def create_span(request)
164
+ Datadog::Tracing.trace(
165
+ SPAN_REQUEST,
166
+ service: service_name(request.uri.host, configuration, Datadog.configuration_for(self)),
167
+ span_type: TYPE_OUTBOUND,
168
+ start_time: @start_time
169
+ )
170
+ end
96
171
  end
97
172
  end
98
173
 
99
174
  module RequestMethods
100
- def __datadog_enable_trace!
101
- return if @__datadog_enable_trace
175
+ # intercepts request initialization to inject the tracing logic.
176
+ def initialize(*)
177
+ super
102
178
 
103
- RequestTracer.new(self).call
104
- @__datadog_enable_trace = true
179
+ return unless Datadog::Tracing.enabled?
180
+
181
+ RequestTracer.new(self)
105
182
  end
106
183
  end
107
184
 
108
185
  module ConnectionMethods
109
- def send(request)
110
- request.__datadog_enable_trace!
186
+ attr_reader :init_time
187
+
188
+ def initialize(*)
189
+ super
190
+
191
+ @init_time = ::Datadog::Core::Utils::Time.now.utc
192
+ end
193
+
194
+ # handles the case when the +error+ happened during name resolution, which meanns
195
+ # that the tracing logic hasn't been injected yet; in such cases, the approximate
196
+ # initial resolving time is collected from the connection, and used as span start time,
197
+ # and the tracing object in inserted before the on response callback is called.
198
+ def handle_error(error)
199
+ return super unless Datadog::Tracing.enabled?
200
+
201
+ return super unless error.respond_to?(:connection)
202
+
203
+ @pending.each do |request|
204
+ RequestTracer.new(request).call(error.connection.init_time)
205
+ end
111
206
 
112
207
  super
113
208
  end
@@ -126,7 +221,7 @@ module Datadog::Tracing
126
221
  option :distributed_tracing, default: true
127
222
  option :split_by_domain, default: false
128
223
 
129
- if Gem::Version.new(DDTrace::VERSION::STRING) >= Gem::Version.new("1.13.0")
224
+ if Gem::Version.new(DATADOG_VERSION::STRING) >= Gem::Version.new("1.13.0")
130
225
  option :enabled do |o|
131
226
  o.type :bool
132
227
  o.env "DD_TRACE_HTTPX_ENABLED"
@@ -169,25 +264,25 @@ module Datadog::Tracing
169
264
  "httpx"
170
265
  )
171
266
  end
172
- o.lazy unless Gem::Version.new(DDTrace::VERSION::STRING) >= Gem::Version.new("1.13.0")
267
+ o.lazy unless Gem::Version.new(DATADOG_VERSION::STRING) >= Gem::Version.new("1.13.0")
173
268
  end
174
269
  else
175
270
  option :service_name do |o|
176
271
  o.default do
177
272
  ENV.fetch("DD_TRACE_HTTPX_SERVICE_NAME", "httpx")
178
273
  end
179
- o.lazy unless Gem::Version.new(DDTrace::VERSION::STRING) >= Gem::Version.new("1.13.0")
274
+ o.lazy unless Gem::Version.new(DATADOG_VERSION::STRING) >= Gem::Version.new("1.13.0")
180
275
  end
181
276
  end
182
277
 
183
278
  option :distributed_tracing, default: true
184
279
 
185
- if Gem::Version.new(DDTrace::VERSION::STRING) >= Gem::Version.new("1.15.0")
280
+ if Gem::Version.new(DATADOG_VERSION::STRING) >= Gem::Version.new("1.15.0")
186
281
  option :error_handler do |o|
187
282
  o.type :proc
188
283
  o.default_proc(&DEFAULT_ERROR_HANDLER)
189
284
  end
190
- elsif Gem::Version.new(DDTrace::VERSION::STRING) >= Gem::Version.new("1.13.0")
285
+ elsif Gem::Version.new(DATADOG_VERSION::STRING) >= Gem::Version.new("1.13.0")
191
286
  option :error_handler do |o|
192
287
  o.type :proc
193
288
  o.experimental_default_proc(&DEFAULT_ERROR_HANDLER)
data/lib/httpx/altsvc.rb CHANGED
@@ -131,9 +131,9 @@ module HTTPX
131
131
  scanner.skip(/;/)
132
132
  break if scanner.eos? || scanner.scan(/ *, */)
133
133
  end
134
- alt_params = Hash[alt_params.map { |field| field.split("=") }]
134
+ alt_params = Hash[alt_params.map { |field| field.split("=", 2) }]
135
135
 
136
- alt_proto, alt_authority = alt_service.split("=")
136
+ alt_proto, alt_authority = alt_service.split("=", 2)
137
137
  alt_origin = parse_altsvc_origin(alt_proto, alt_authority)
138
138
  return unless alt_origin
139
139
 
@@ -245,7 +245,7 @@ module HTTPX
245
245
  return unless keep_alive
246
246
 
247
247
  parameters = Hash[keep_alive.split(/ *, */).map do |pair|
248
- pair.split(/ *= */)
248
+ pair.split(/ *= */, 2)
249
249
  end]
250
250
  @max_requests = parameters["max"].to_i - 1 if parameters.key?("max")
251
251
 
@@ -48,10 +48,10 @@ module HTTPX
48
48
  attr_accessor :family
49
49
 
50
50
  def initialize(uri, options)
51
- @origins = [uri.origin]
52
- @origin = Utils.to_uri(uri.origin)
53
51
  @options = Options.new(options)
54
52
  @type = initialize_type(uri, @options)
53
+ @origins = [uri.origin]
54
+ @origin = Utils.to_uri(uri.origin)
55
55
  @window_size = @options.window_size
56
56
  @read_buffer = Buffer.new(@options.buffer_size)
57
57
  @write_buffer = Buffer.new(@options.buffer_size)
@@ -241,8 +241,8 @@ module HTTPX
241
241
  # for such cases, we want to ping for availability before deciding to shovel requests.
242
242
  log(level: 3) { "keep alive timeout expired, pinging connection..." }
243
243
  @pending << request
244
- parser.ping
245
244
  transition(:active) if @state == :inactive
245
+ parser.ping
246
246
  return
247
247
  end
248
248
 
@@ -30,7 +30,8 @@ module HTTPX
30
30
  auth_info = authenticate[/^(\w+) (.*)/, 2]
31
31
 
32
32
  params = auth_info.split(/ *, */)
33
- .to_h { |val| val.split("=") }.transform_values { |v| v.delete("\"") }
33
+ .to_h { |val| val.split("=", 2) }
34
+ .transform_values { |v| v.delete("\"") }
34
35
  nonce = params["nonce"]
35
36
  nc = next_nonce
36
37
 
@@ -197,8 +197,8 @@ module HTTPX
197
197
  params.each.with_index.sort do |a, b|
198
198
  a, a_offset = a
199
199
  b, b_offset = b
200
- a_name, a_value = a.split("=")
201
- b_name, b_value = b.split("=")
200
+ a_name, a_value = a.split("=", 2)
201
+ b_name, b_value = b.split("=", 2)
202
202
  if a_name == b_name
203
203
  if a_value == b_value
204
204
  a_offset <=> b_offset
@@ -40,7 +40,7 @@ module HTTPX
40
40
  # the Range and Content-Range headers MUST NOT cache 206 (Partial
41
41
  # Content) responses.
42
42
  response.status != 206 && (
43
- response.headers.key?("etag") || response.headers.key?("last-modified-at") || response.fresh?
43
+ response.headers.key?("etag") || response.headers.key?("last-modified") || response.fresh?
44
44
  )
45
45
  end
46
46
 
@@ -102,6 +102,9 @@ module HTTPX
102
102
 
103
103
  module ResponseMethods
104
104
  def copy_from_cached(other)
105
+ # 304 responses do not have content-type, which are needed for decoding.
106
+ @headers = @headers.class.new(other.headers.merge(@headers))
107
+
105
108
  @body = other.body.dup
106
109
 
107
110
  @body.rewind
@@ -16,9 +16,18 @@ module HTTPX
16
16
  begin
17
17
  @on_chunk = block
18
18
 
19
+ if @request.response
20
+ # if we've already started collecting the payload, yield it first
21
+ # before proceeding.
22
+ body = @request.response.body
23
+
24
+ body.each do |chunk|
25
+ on_chunk(chunk)
26
+ end
27
+ end
28
+
19
29
  response.raise_for_status
20
30
  ensure
21
- response.close if @response
22
31
  @on_chunk = nil
23
32
  end
24
33
  end
@@ -274,6 +274,11 @@ module HTTPX
274
274
  def raise_for_status
275
275
  raise @error
276
276
  end
277
+
278
+ # buffers lost chunks to error response
279
+ def <<(data)
280
+ @response << data
281
+ end
277
282
  end
278
283
  end
279
284
 
data/lib/httpx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "1.2.3"
4
+ VERSION = "1.2.5"
5
5
  end
data/sig/response.rbs CHANGED
@@ -70,6 +70,7 @@ module HTTPX
70
70
 
71
71
  class ErrorResponse
72
72
  include _Response
73
+ include _Reader
73
74
  include Loggable
74
75
  extend Forwardable
75
76
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpx
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-04 00:00:00.000000000 Z
11
+ date: 2024-05-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2-next
@@ -141,6 +141,8 @@ extra_rdoc_files:
141
141
  - doc/release_notes/1_2_1.md
142
142
  - doc/release_notes/1_2_2.md
143
143
  - doc/release_notes/1_2_3.md
144
+ - doc/release_notes/1_2_4.md
145
+ - doc/release_notes/1_2_5.md
144
146
  files:
145
147
  - LICENSE.txt
146
148
  - README.md
@@ -253,6 +255,8 @@ files:
253
255
  - doc/release_notes/1_2_1.md
254
256
  - doc/release_notes/1_2_2.md
255
257
  - doc/release_notes/1_2_3.md
258
+ - doc/release_notes/1_2_4.md
259
+ - doc/release_notes/1_2_5.md
256
260
  - lib/httpx.rb
257
261
  - lib/httpx/adapters/datadog.rb
258
262
  - lib/httpx/adapters/faraday.rb