httpx 1.3.4 → 1.4.1

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 (89) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_4_0.md +43 -0
  3. data/doc/release_notes/1_4_1.md +19 -0
  4. data/lib/httpx/adapters/datadog.rb +55 -83
  5. data/lib/httpx/adapters/faraday.rb +2 -0
  6. data/lib/httpx/adapters/webmock.rb +18 -6
  7. data/lib/httpx/callbacks.rb +0 -5
  8. data/lib/httpx/chainable.rb +3 -1
  9. data/lib/httpx/connection/http2.rb +12 -8
  10. data/lib/httpx/connection.rb +192 -22
  11. data/lib/httpx/errors.rb +12 -0
  12. data/lib/httpx/loggable.rb +5 -5
  13. data/lib/httpx/options.rb +26 -16
  14. data/lib/httpx/plugins/aws_sigv4.rb +31 -16
  15. data/lib/httpx/plugins/callbacks.rb +12 -2
  16. data/lib/httpx/plugins/circuit_breaker.rb +0 -5
  17. data/lib/httpx/plugins/content_digest.rb +202 -0
  18. data/lib/httpx/plugins/expect.rb +4 -3
  19. data/lib/httpx/plugins/follow_redirects.rb +7 -8
  20. data/lib/httpx/plugins/grpc/grpc_encoding.rb +2 -0
  21. data/lib/httpx/plugins/h2c.rb +23 -20
  22. data/lib/httpx/plugins/internal_telemetry.rb +27 -0
  23. data/lib/httpx/plugins/persistent.rb +16 -0
  24. data/lib/httpx/plugins/proxy/http.rb +17 -19
  25. data/lib/httpx/plugins/proxy.rb +91 -93
  26. data/lib/httpx/plugins/retries.rb +5 -8
  27. data/lib/httpx/plugins/upgrade.rb +5 -10
  28. data/lib/httpx/plugins/webdav.rb +6 -0
  29. data/lib/httpx/plugins/xml.rb +76 -0
  30. data/lib/httpx/pool.rb +73 -244
  31. data/lib/httpx/request/body.rb +25 -26
  32. data/lib/httpx/request.rb +7 -1
  33. data/lib/httpx/resolver/https.rb +15 -20
  34. data/lib/httpx/resolver/multi.rb +34 -16
  35. data/lib/httpx/resolver/native.rb +66 -25
  36. data/lib/httpx/resolver/resolver.rb +59 -15
  37. data/lib/httpx/resolver/system.rb +31 -15
  38. data/lib/httpx/resolver.rb +21 -14
  39. data/lib/httpx/response.rb +5 -3
  40. data/lib/httpx/selector.rb +160 -95
  41. data/lib/httpx/session.rb +273 -140
  42. data/lib/httpx/transcoder/body.rb +15 -31
  43. data/lib/httpx/transcoder/gzip.rb +0 -3
  44. data/lib/httpx/transcoder/json.rb +14 -2
  45. data/lib/httpx/transcoder/multipart/part.rb +1 -1
  46. data/lib/httpx/transcoder/utils/deflater.rb +7 -4
  47. data/lib/httpx/transcoder/utils/inflater.rb +2 -0
  48. data/lib/httpx/transcoder.rb +0 -1
  49. data/lib/httpx/version.rb +1 -1
  50. data/lib/httpx.rb +20 -21
  51. data/sig/callbacks.rbs +0 -1
  52. data/sig/chainable.rbs +4 -0
  53. data/sig/connection/http2.rbs +1 -1
  54. data/sig/connection.rbs +29 -3
  55. data/sig/errors.rbs +6 -0
  56. data/sig/loggable.rbs +2 -0
  57. data/sig/options.rbs +7 -0
  58. data/sig/plugins/aws_sigv4.rbs +8 -2
  59. data/sig/plugins/content_digest.rbs +51 -0
  60. data/sig/plugins/cookies/cookie.rbs +9 -0
  61. data/sig/plugins/grpc/call.rbs +4 -0
  62. data/sig/plugins/persistent.rbs +4 -1
  63. data/sig/plugins/proxy/socks5.rbs +11 -3
  64. data/sig/plugins/proxy.rbs +18 -11
  65. data/sig/plugins/push_promise.rbs +3 -0
  66. data/sig/plugins/rate_limiter.rbs +2 -0
  67. data/sig/plugins/retries.rbs +1 -1
  68. data/sig/plugins/ssrf_filter.rbs +26 -0
  69. data/sig/plugins/webdav.rbs +23 -0
  70. data/sig/plugins/xml.rbs +37 -0
  71. data/sig/pool.rbs +25 -33
  72. data/sig/request/body.rbs +5 -9
  73. data/sig/resolver/multi.rbs +26 -1
  74. data/sig/resolver/native.rbs +2 -2
  75. data/sig/resolver/resolver.rbs +21 -2
  76. data/sig/resolver.rbs +5 -1
  77. data/sig/response/buffer.rbs +1 -1
  78. data/sig/selector.rbs +30 -4
  79. data/sig/session.rbs +47 -18
  80. data/sig/transcoder/body.rbs +2 -4
  81. data/sig/transcoder/chunker.rbs +1 -1
  82. data/sig/transcoder/deflate.rbs +1 -0
  83. data/sig/transcoder/form.rbs +8 -0
  84. data/sig/transcoder/gzip.rbs +4 -1
  85. data/sig/transcoder/utils/body_reader.rbs +3 -3
  86. data/sig/transcoder/utils/deflater.rbs +3 -3
  87. metadata +12 -4
  88. data/lib/httpx/transcoder/xml.rb +0 -52
  89. data/sig/transcoder/xml.rbs +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 59239a38c693a32e279f32e5f153f606fdc83e0cdce02e6079947cc8f8d6e0d0
4
- data.tar.gz: c386fbdd0409186f50390a2379c3945add29a7cafac805bbd0c780992e933437
3
+ metadata.gz: b8a0ae955506767cc7b2f0a8134dd920bc937f88aeba2f72f691489e4d2199ab
4
+ data.tar.gz: 96503529f27fddf76b41250f3a7a0686c93d9d6c1091255275ed1d5b2beada11
5
5
  SHA512:
6
- metadata.gz: 5d142fdfe649e0447f3043e5eebd52776c71c44c4b141607b061895c520e32a5d1da0c5c678c6fd6cdb9f572e1d3c43165d8c26bf006420c2b89ce66c547c986
7
- data.tar.gz: 77435864f6afd01fdb7fbc8e1019928411a50032c11751220062f1fff804355d0220823f18cbef430e47bc26f3c28fab826513a242222ccdb003eae949a41b97
6
+ metadata.gz: d16170323b9f5d016f496e848be6b6fb50046402ca644c2a016d05fe1af4a5b045a400b31530c8fa38f85a61351125062edce01bd04c975bc45c3205545aef71
7
+ data.tar.gz: a800e8814449fcf2d3149aadc5f335f458f7dc7fa537d7c676d8460b6bfcfd1a9cc0a96f3350b9492846fea563fca15605f228ef539b39ef36985df05431d193
@@ -0,0 +1,43 @@
1
+ # 1.4.0
2
+
3
+ ## Features
4
+
5
+ ### `:content_digest` plugin
6
+
7
+ The `:content_digest` can be used to calculate the digest of request payloads and set them in the `"content-digest"` header; it can also validate the integrity of responses which declare the same `"content-digest"` header.
8
+
9
+ More info under https://honeyryderchuck.gitlab.io/httpx/wiki/Content-Digest
10
+
11
+ ## Per-session connection pools
12
+
13
+ This architectural changes moves away from per-thread shared connection pools, and into per-session (also thread-safe) connection pools. Unlike before, this enables connections from a session to be reused across threads, as well as limiting the number of connections that can be open on a given origin peer. This fixes long-standing issues, such as reusing connections under a fiber scheduler loop (such as the one from the gem `async`).
14
+
15
+ A new `:pool_options` option is introduced, which can be passed an hash with the following sub-options:
16
+
17
+ * `:max_connections_per_origin`: maximum number of connections a pool allows (unbounded by default, for backwards compatibility).
18
+ * `:pool_timeout`: the number of seconds a session will wait for a connection to be checked out (default: 5)
19
+
20
+ More info under https://honeyryderchuck.gitlab.io/httpx/wiki/Connection-Pools
21
+
22
+
23
+ ## Improvements
24
+
25
+ * `:aws_sigv4` plugin: improved digest calculation on compressed request bodies by buffering content to a tempfile.
26
+ * `HTTPX::Response#json` will parse payload from extended json MIME types (like `application/ld+json`, `application/hal+json`, ...).
27
+
28
+ ## Bugfixes
29
+
30
+ * `:aws_sigv4` plugin: do not try to rewind a request body which yields chunks.
31
+ * fixed request encoding when `:json` param is passed, and the `oj` gem is used (by using the `:compat` flag).
32
+ * native resolver: on message truncation, bubble up tcp handshake errors as resolve errors.
33
+ * allow `HTTPX::Response#json` to accept extended JSON mime types (such as responses with `content-type: application/ld+json`)
34
+
35
+ ## Chore
36
+
37
+ * default options are now fully frozen (in case anyone relies on overriding them).
38
+
39
+ ### `:xml` plugin
40
+
41
+ XML encoding/decoding (via `:xml` request param, and `HTTPX::Response#xml`) is now available via the `:xml` plugin.
42
+
43
+ Using `HTTPX::Response#xml` without the plugin will issue a deprecation warning.
@@ -0,0 +1,19 @@
1
+ # 1.4.1
2
+
3
+ ## Bugfixes
4
+
5
+ * several `datadog` integration bugfixes
6
+ * only load the `datadog` integration when the `datadog` sdk is loaded (and not other gems that may define the `Datadog` module, like `dogstatsd`)
7
+ * do not trace if datadog integration is loaded but disabled
8
+ * distributed headers are now sent along (when the configuration is enabled, which it is by default)
9
+ * fix for handling multiple `GOAWAY` frames coming from the server (node.js servers seem to send multiple frames on connection timeout)
10
+ * fix regression for when a url is used with `httpx` which is not `http://` or `https://` (should raise `HTTPX::UnsupportedSchemaError`)
11
+ * worked around `IO.copy_stream` which was emitting incorrect bytes for HTTP/2 requests which bodies larger than the maximum supported frame size.
12
+ * multipart requests: make sure that a body declared as `Pathname` is opened for reading in binary mode.
13
+ * `webmock` integration: ensure that request events are emitted (such as plugins and integrations relying in it, such as `datadog` and the OTel integration)
14
+ * native resolver: do not propagate successful name resolutions for connections which were already closed.
15
+ * native resolver: fixed name resolution stalling, in a multi-request to multi-origin scenario, when a resolution timeout would happen.
16
+
17
+ ## Chore
18
+
19
+ * refactor of the happy eyeballs and connection coalescing logic to not rely on callbacks, and instead on instance variable management (makes code more straightforward to read).
@@ -27,62 +27,54 @@ module Datadog::Tracing
27
27
  # Enables tracing for httpx requests.
28
28
  #
29
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.
30
+ # buffering a request, and it is fed the start time stored inside the tracer object.
31
31
  #
32
32
  module Plugin
33
- class RequestTracer
34
- include Contrib::HttpAnnotationHelper
33
+ module RequestTracer
34
+ extend Contrib::HttpAnnotationHelper
35
+
36
+ module_function
35
37
 
36
38
  SPAN_REQUEST = "httpx.request"
37
39
 
38
- # initializes the tracer object on the +request+.
39
- def initialize(request)
40
- @request = request
41
- @start_time = nil
40
+ # initializes tracing on the +request+.
41
+ def call(request)
42
+ return unless configuration(request).enabled
43
+
44
+ span = nil
42
45
 
43
46
  # request objects are reused, when already buffered requests get rerouted to a different
44
47
  # connection due to connection issues, or when they already got a response, but need to
45
48
  # be retried. In such situations, the original span needs to be extended for the former,
46
49
  # while a new is required for the latter.
47
- request.on(:idle) { reset }
50
+ request.on(:idle) do
51
+ span = nil
52
+ end
48
53
  # the span is initialized when the request is buffered in the parser, which is the closest
49
54
  # one gets to actually sending the request.
50
- request.on(:headers) { call }
51
- end
52
-
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)
55
+ request.on(:headers) do
56
+ next if span
58
57
 
59
- @request.once(:response, &method(:finish))
60
- end
58
+ span = initialize_span(request, now)
59
+ end
61
60
 
62
- private
61
+ request.on(:response) do |response|
62
+ unless span
63
+ next unless response.is_a?(::HTTPX::ErrorResponse) && response.error.respond_to?(:connection)
63
64
 
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
65
+ # handles the case when the +error+ happened during name resolution, which means
66
+ # that the tracing start point hasn't been triggered yet; in such cases, the approximate
67
+ # initial resolving time is collected from the connection, and used as span start time,
68
+ # and the tracing object in inserted before the on response callback is called.
69
+ span = initialize_span(request, response.error.connection.init_time)
69
70
 
70
- # resets the start time for already finished request transactions.
71
- def reset
72
- return unless @start_time
71
+ end
73
72
 
74
- start
73
+ finish(response, span)
74
+ end
75
75
  end
76
76
 
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
83
-
84
- return unless span
85
-
77
+ def finish(response, span)
86
78
  if response.is_a?(::HTTPX::ErrorResponse)
87
79
  span.set_error(response.error)
88
80
  else
@@ -92,40 +84,40 @@ module Datadog::Tracing
92
84
  end
93
85
 
94
86
  span.finish
95
- ensure
96
- @start_time = nil
97
87
  end
98
88
 
99
89
  # return a span initialized with the +@request+ state.
100
- def initialize_span
101
- verb = @request.verb
102
- uri = @request.uri
90
+ def initialize_span(request, start_time)
91
+ verb = request.verb
92
+ uri = request.uri
93
+
94
+ config = configuration(request)
103
95
 
104
- span = create_span(@request)
96
+ span = create_span(request, config, start_time)
105
97
 
106
98
  span.resource = verb
107
99
 
108
100
  # Add additional request specific tags to the span.
109
101
 
110
- span.set_tag(TAG_URL, @request.path)
102
+ span.set_tag(TAG_URL, request.path)
111
103
  span.set_tag(TAG_METHOD, verb)
112
104
 
113
105
  span.set_tag(TAG_TARGET_HOST, uri.host)
114
- span.set_tag(TAG_TARGET_PORT, uri.port.to_s)
106
+ span.set_tag(TAG_TARGET_PORT, uri.port)
115
107
 
116
108
  # Tag as an external peer service
117
109
  span.set_tag(TAG_PEER_SERVICE, span.service)
118
110
 
119
- if configuration[:distributed_tracing]
111
+ if config[:distributed_tracing]
120
112
  propagate_trace_http(
121
- Datadog::Tracing.active_trace.to_digest,
122
- @request.headers
113
+ Datadog::Tracing.active_trace,
114
+ request.headers
123
115
  )
124
116
  end
125
117
 
126
118
  # Set analytics sample rate
127
- if Contrib::Analytics.enabled?(configuration[:analytics_enabled])
128
- Contrib::Analytics.set_sample_rate(span, configuration[:analytics_sample_rate])
119
+ if Contrib::Analytics.enabled?(config[:analytics_enabled])
120
+ Contrib::Analytics.set_sample_rate(span, config[:analytics_sample_rate])
129
121
  end
130
122
 
131
123
  span
@@ -138,34 +130,34 @@ module Datadog::Tracing
138
130
  ::Datadog::Core::Utils::Time.now.utc
139
131
  end
140
132
 
141
- def configuration
142
- @configuration ||= Datadog.configuration.tracing[:httpx, @request.uri.host]
133
+ def configuration(request)
134
+ Datadog.configuration.tracing[:httpx, request.uri.host]
143
135
  end
144
136
 
145
137
  if Gem::Version.new(DATADOG_VERSION::STRING) >= Gem::Version.new("2.0.0")
146
- def propagate_trace_http(digest, headers)
147
- Datadog::Tracing::Contrib::HTTP.inject(digest, headers)
138
+ def propagate_trace_http(trace, headers)
139
+ Datadog::Tracing::Contrib::HTTP.inject(trace, headers)
148
140
  end
149
141
 
150
- def create_span(request)
142
+ def create_span(request, configuration, start_time)
151
143
  Datadog::Tracing.trace(
152
144
  SPAN_REQUEST,
153
- service: service_name(request.uri.host, configuration, Datadog.configuration_for(self)),
145
+ service: service_name(request.uri.host, configuration),
154
146
  type: TYPE_OUTBOUND,
155
- start_time: @start_time
147
+ start_time: start_time
156
148
  )
157
149
  end
158
150
  else
159
- def propagate_trace_http(digest, headers)
160
- Datadog::Tracing::Propagation::HTTP.inject!(digest, headers)
151
+ def propagate_trace_http(trace, headers)
152
+ Datadog::Tracing::Propagation::HTTP.inject!(trace.to_digest, headers)
161
153
  end
162
154
 
163
- def create_span(request)
155
+ def create_span(request, configuration, start_time)
164
156
  Datadog::Tracing.trace(
165
157
  SPAN_REQUEST,
166
- service: service_name(request.uri.host, configuration, Datadog.configuration_for(self)),
158
+ service: service_name(request.uri.host, configuration),
167
159
  span_type: TYPE_OUTBOUND,
168
- start_time: @start_time
160
+ start_time: start_time
169
161
  )
170
162
  end
171
163
  end
@@ -178,7 +170,7 @@ module Datadog::Tracing
178
170
 
179
171
  return unless Datadog::Tracing.enabled?
180
172
 
181
- RequestTracer.new(self)
173
+ RequestTracer.call(self)
182
174
  end
183
175
  end
184
176
 
@@ -190,26 +182,6 @@ module Datadog::Tracing
190
182
 
191
183
  @init_time = ::Datadog::Core::Utils::Time.now.utc
192
184
  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, request = nil)
199
- return super unless Datadog::Tracing.enabled?
200
-
201
- return super unless error.respond_to?(:connection)
202
-
203
- @pending.each do |req|
204
- next if request and request == req
205
-
206
- RequestTracer.new(req).call(error.connection.init_time)
207
- end
208
-
209
- RequestTracer.new(request).call(error.connection.init_time) if request
210
-
211
- super
212
- end
213
185
  end
214
186
  end
215
187
 
@@ -108,9 +108,11 @@ module Faraday
108
108
  ssl_options
109
109
  end
110
110
  else
111
+ # :nocov:
111
112
  def ssl_options_from_env(*)
112
113
  {}
113
114
  end
115
+ # :nocov:
114
116
  end
115
117
  end
116
118
 
@@ -52,16 +52,20 @@ module WebMock
52
52
  end
53
53
 
54
54
  module InstanceMethods
55
- def init_connection(*)
56
- connection = super
55
+ private
56
+
57
+ def do_init_connection(connection, selector)
58
+ super
59
+
57
60
  connection.once(:unmock_connection) do
58
61
  unless connection.addresses
59
- connection.__send__(:callbacks)[:connect_error].clear
60
- pool.__send__(:unregister_connection, connection)
62
+ # reset Happy Eyeballs, fail early
63
+ connection.sibling = nil
64
+
65
+ deselect_connection(connection, selector)
61
66
  end
62
- pool.__send__(:resolve_connection, connection)
67
+ resolve_connection(connection, selector)
63
68
  end
64
- connection
65
69
  end
66
70
  end
67
71
 
@@ -100,6 +104,10 @@ module WebMock
100
104
  super
101
105
  end
102
106
 
107
+ def terminate
108
+ force_reset
109
+ end
110
+
103
111
  def send(request)
104
112
  request_signature = Plugin.build_webmock_request_signature(request)
105
113
  WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
@@ -108,6 +116,10 @@ module WebMock
108
116
  response = Plugin.build_from_webmock_response(request, mock_response)
109
117
  WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, request_signature, mock_response)
110
118
  log { "mocking #{request.uri} with #{mock_response.inspect}" }
119
+ request.transition(:headers)
120
+ request.transition(:body)
121
+ request.transition(:trailers)
122
+ request.transition(:done)
111
123
  request.response = response
112
124
  request.emit(:response, response)
113
125
  response << mock_response.body.dup unless response.is_a?(HTTPX::ErrorResponse)
@@ -15,11 +15,6 @@ module HTTPX
15
15
  self
16
16
  end
17
17
 
18
- def only(type, &block)
19
- callbacks(type).clear
20
- on(type, &block)
21
- end
22
-
23
18
  def emit(type, *args)
24
19
  callbacks(type).delete_if { |pr| :delete == pr.call(*args) } # rubocop:disable Style/YodaCondition
25
20
  end
@@ -73,7 +73,7 @@ module HTTPX
73
73
  ].include?(callback)
74
74
 
75
75
  warn "DEPRECATION WARNING: calling `.#{meth}` on plain HTTPX sessions is deprecated. " \
76
- "Use HTTPX.plugin(:callbacks).#{meth} instead."
76
+ "Use `HTTPX.plugin(:callbacks).#{meth}` instead."
77
77
 
78
78
  plugin(:callbacks).__send__(meth, *args, **options, &blk)
79
79
  else
@@ -101,4 +101,6 @@ module HTTPX
101
101
  end
102
102
  end
103
103
  end
104
+
105
+ extend Chainable
104
106
  end
@@ -98,10 +98,10 @@ module HTTPX
98
98
  @streams.size < @max_requests
99
99
  end
100
100
 
101
- def send(request)
101
+ def send(request, head = false)
102
102
  unless can_buffer_more_requests?
103
- @pending << request
104
- return
103
+ head ? @pending.unshift(request) : @pending << request
104
+ return false
105
105
  end
106
106
  unless (stream = @streams[request])
107
107
  stream = @connection.new_stream
@@ -113,6 +113,7 @@ module HTTPX
113
113
  true
114
114
  rescue ::HTTP2::Error::StreamLimitExceeded
115
115
  @pending.unshift(request)
116
+ false
116
117
  end
117
118
 
118
119
  def consume
@@ -136,7 +137,7 @@ module HTTPX
136
137
 
137
138
  emit(:error, req, ex)
138
139
  end
139
- @pending.each do |req|
140
+ while (req = @pending.shift)
140
141
  next if request && request == req
141
142
 
142
143
  emit(:error, req, ex)
@@ -154,8 +155,7 @@ module HTTPX
154
155
 
155
156
  def send_pending
156
157
  while (request = @pending.shift)
157
- # TODO: this request should go back to top of stack
158
- break unless send(request)
158
+ break unless send(request, true)
159
159
  end
160
160
  end
161
161
 
@@ -327,10 +327,14 @@ module HTTPX
327
327
  end
328
328
  end
329
329
  send(@pending.shift) unless @pending.empty?
330
+
330
331
  return unless @streams.empty? && exhausted?
331
332
 
332
- close
333
- emit(:exhausted) unless @pending.empty?
333
+ if @pending.empty?
334
+ close
335
+ else
336
+ emit(:exhausted)
337
+ end
334
338
  end
335
339
 
336
340
  def on_frame(bytes)