httpx 1.2.3 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
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