httpx 1.3.0 → 1.3.2

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/1_3_1.md +17 -0
  3. data/doc/release_notes/1_3_2.md +6 -0
  4. data/lib/httpx/adapters/datadog.rb +7 -3
  5. data/lib/httpx/adapters/faraday.rb +2 -1
  6. data/lib/httpx/adapters/webmock.rb +1 -1
  7. data/lib/httpx/connection/http1.rb +9 -5
  8. data/lib/httpx/connection/http2.rb +9 -5
  9. data/lib/httpx/connection.rb +46 -21
  10. data/lib/httpx/options.rb +1 -0
  11. data/lib/httpx/plugins/aws_sdk_authentication.rb +3 -0
  12. data/lib/httpx/plugins/aws_sigv4.rb +4 -0
  13. data/lib/httpx/plugins/circuit_breaker.rb +10 -0
  14. data/lib/httpx/plugins/cookies.rb +3 -0
  15. data/lib/httpx/plugins/digest_auth.rb +3 -0
  16. data/lib/httpx/plugins/expect.rb +5 -0
  17. data/lib/httpx/plugins/follow_redirects.rb +43 -6
  18. data/lib/httpx/plugins/proxy/http.rb +7 -2
  19. data/lib/httpx/plugins/proxy.rb +26 -13
  20. data/lib/httpx/plugins/retries.rb +25 -4
  21. data/lib/httpx/plugins/ssrf_filter.rb +3 -0
  22. data/lib/httpx/pool.rb +1 -1
  23. data/lib/httpx/request.rb +7 -4
  24. data/lib/httpx/resolver/https.rb +6 -4
  25. data/lib/httpx/resolver/native.rb +1 -1
  26. data/lib/httpx/resolver/system.rb +1 -1
  27. data/lib/httpx/session.rb +14 -2
  28. data/lib/httpx/session2.rb +1 -1
  29. data/lib/httpx/transcoder/body.rb +2 -0
  30. data/lib/httpx/transcoder/form.rb +2 -0
  31. data/lib/httpx/transcoder/json.rb +2 -0
  32. data/lib/httpx/version.rb +1 -1
  33. data/sig/connection/http1.rbs +1 -1
  34. data/sig/connection/http2.rbs +1 -1
  35. data/sig/connection.rbs +8 -2
  36. data/sig/plugins/proxy.rbs +2 -0
  37. data/sig/pool.rbs +1 -1
  38. data/sig/resolver/resolver.rbs +1 -1
  39. data/sig/session.rbs +2 -0
  40. metadata +6 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99138c8286fb426f6bd70a62357cbaf1b54f3ceb030f1ff3c78c9e2bffd8e75a
4
- data.tar.gz: 59a28cbadff4830ed047eba007b5f71455b5a8d6aa3e111cfb5897b82b1350fa
3
+ metadata.gz: e417c7a6d7564807d040374d9174d5b1fd3945ce924062397fdc0ad95d12bd31
4
+ data.tar.gz: bf001578d74624abc4d05e74563c2d9a5388cbd6315cd29fb0cf176d2e4e01e2
5
5
  SHA512:
6
- metadata.gz: 78b08826c993b9a2c5d1164a2bf6c7bb4123cb9599a1ddc44bd38b827c91da73abf4fee18ae39d67f9370834b3a8d04eb32999cf2c68f9fe654a0605e6d7ca5b
7
- data.tar.gz: 6b22a1e473abc7967314785f6fb517bf5d663d87951a7a2cba5855913a348b95dc50e2fddaa801fbcf25aef96b4d7d9b4e4d0dda8388696c0b628ba0c5dd7c1b
6
+ metadata.gz: dc88e4afa96c336a79ba37e6fb35521cc189db0e2981bad0d5354253f2f55e320e951656da4cc59ad5baab709ef933ed98c52ff12b116c2a9021d039e42e3db8
7
+ data.tar.gz: 2cbdcc54076a34b66a413b6aa380a25e3243cedd98882774ac02fc3f345178197b01097b2b615bf174d0e0affe1b9d4dd03881305b7c16c33183dc77c27f1f9d
@@ -0,0 +1,17 @@
1
+ # 1.3.1
2
+
3
+ ## Improvements
4
+
5
+ * `:request_timeout` will be applied to all HTTP interactions until the final responses returned to the caller. That includes:
6
+ * all redirect requests/responses (when using the `:follow_redirects` plugin)
7
+ * all retried requests/responses (when using the `:retries` plugin)
8
+ * intermediate requests (such as "100-continue")
9
+ * faraday adapter: allow further plugins of internal session (ex: `builder.adapter(:httpx) { |sess| sess.plugin(:follow_redirects) }...`)
10
+
11
+ ## Bugfixes
12
+
13
+ * fix connection leak on proxy auth failed (407) handling
14
+ * fix busy loop on deferred requests for the duration interval
15
+ * do not further enqueue deferred requests if they have terminated meanwhile.
16
+ * fix busy loop caused by coalescing connections when one of them is on the DNS resolution phase still.
17
+ * faraday adapter: on parallel mode, skip calling `on_complete` when not defined.
@@ -0,0 +1,6 @@
1
+ # 1.3.2
2
+
3
+ ## Bugfixes
4
+
5
+ * Prevent `NoMethodError` in an edge case when the `:proxy` plugin is autoloaded via env vars and webmock adapter are used in tandem, and a real request fails.
6
+ * raise invalid uri error if passed request uri does not contain the host part (ex: `"https:/get"`)
@@ -195,15 +195,19 @@ module Datadog::Tracing
195
195
  # that the tracing logic hasn't been injected yet; in such cases, the approximate
196
196
  # initial resolving time is collected from the connection, and used as span start time,
197
197
  # and the tracing object in inserted before the on response callback is called.
198
- def handle_error(error)
198
+ def handle_error(error, request = nil)
199
199
  return super unless Datadog::Tracing.enabled?
200
200
 
201
201
  return super unless error.respond_to?(:connection)
202
202
 
203
- @pending.each do |request|
204
- RequestTracer.new(request).call(error.connection.init_time)
203
+ @pending.each do |req|
204
+ next if request and request == req
205
+
206
+ RequestTracer.new(req).call(error.connection.init_time)
205
207
  end
206
208
 
209
+ RequestTracer.new(request).call(error.connection.init_time) if request
210
+
207
211
  super
208
212
  end
209
213
  end
@@ -30,6 +30,7 @@ module Faraday
30
30
  end
31
31
  @connection = @connection.plugin(OnDataPlugin) if env.request.stream_response?
32
32
 
33
+ @connection = @config_block.call(@connection) || @connection if @config_block
33
34
  @connection
34
35
  end
35
36
 
@@ -212,7 +213,7 @@ module Faraday
212
213
  Array(responses).each_with_index do |response, index|
213
214
  handler = @handlers[index]
214
215
  handler.on_response.call(response)
215
- handler.on_complete.call(handler.env)
216
+ handler.on_complete.call(handler.env) if handler.on_complete
216
217
  end
217
218
  end
218
219
  rescue ::HTTPX::TimeoutError => e
@@ -20,7 +20,7 @@ module WebMock
20
20
  WebMock::RequestSignature.new(
21
21
  request.verb.downcase.to_sym,
22
22
  uri.to_s,
23
- body: request.body.each.to_a.join,
23
+ body: request.body,
24
24
  headers: request.headers.to_h
25
25
  )
26
26
  end
@@ -197,7 +197,7 @@ module HTTPX
197
197
  end
198
198
  end
199
199
 
200
- def handle_error(ex)
200
+ def handle_error(ex, request = nil)
201
201
  if (ex.is_a?(EOFError) || ex.is_a?(TimeoutError)) && @request && @request.response &&
202
202
  !@request.response.headers.key?("content-length") &&
203
203
  !@request.response.headers.key?("transfer-encoding")
@@ -211,11 +211,15 @@ module HTTPX
211
211
  if @pipelining
212
212
  catch(:called) { disable }
213
213
  else
214
- @requests.each do |request|
215
- emit(:error, request, ex)
214
+ @requests.each do |req|
215
+ next if request && request == req
216
+
217
+ emit(:error, req, ex)
216
218
  end
217
- @pending.each do |request|
218
- emit(:error, request, ex)
219
+ @pending.each do |req|
220
+ next if request && request == req
221
+
222
+ emit(:error, req, ex)
219
223
  end
220
224
  end
221
225
  end
@@ -123,7 +123,7 @@ module HTTPX
123
123
  end
124
124
  end
125
125
 
126
- def handle_error(ex)
126
+ def handle_error(ex, request = nil)
127
127
  if ex.instance_of?(TimeoutError) && !@handshake_completed && @connection.state != :closed
128
128
  @connection.goaway(:settings_timeout, "closing due to settings timeout")
129
129
  emit(:close_handshake)
@@ -131,11 +131,15 @@ module HTTPX
131
131
  settings_ex.set_backtrace(ex.backtrace)
132
132
  ex = settings_ex
133
133
  end
134
- @streams.each_key do |request|
135
- emit(:error, request, ex)
134
+ @streams.each_key do |req|
135
+ next if request && request == req
136
+
137
+ emit(:error, req, ex)
136
138
  end
137
- @pending.each do |request|
138
- emit(:error, request, ex)
139
+ @pending.each do |req|
140
+ next if request && request == req
141
+
142
+ emit(:error, req, ex)
139
143
  end
140
144
  end
141
145
 
@@ -562,6 +562,9 @@ module HTTPX
562
562
  emit(:open)
563
563
  when :inactive
564
564
  return unless @state == :open
565
+
566
+ # do not deactivate connection in use
567
+ return if @inflight.positive?
565
568
  when :closing
566
569
  return unless @state == :idle || @state == :open
567
570
 
@@ -638,7 +641,7 @@ module HTTPX
638
641
  end
639
642
  end
640
643
 
641
- def on_error(error)
644
+ def on_error(error, request = nil)
642
645
  if error.instance_of?(TimeoutError)
643
646
 
644
647
  # inactive connections do not contribute to the select loop, therefore
@@ -652,39 +655,59 @@ module HTTPX
652
655
 
653
656
  error = error.to_connection_error if connecting?
654
657
  end
655
- handle_error(error)
658
+ handle_error(error, request)
656
659
  reset
657
660
  end
658
661
 
659
- def handle_error(error)
660
- parser.handle_error(error) if @parser && parser.respond_to?(:handle_error)
661
- while (request = @pending.shift)
662
- response = ErrorResponse.new(request, error)
663
- request.response = response
664
- request.emit(:response, response)
662
+ def handle_error(error, request = nil)
663
+ parser.handle_error(error, request) if @parser && parser.respond_to?(:handle_error)
664
+ while (req = @pending.shift)
665
+ next if request && req == request
666
+
667
+ response = ErrorResponse.new(req, error)
668
+ req.response = response
669
+ req.emit(:response, response)
665
670
  end
671
+
672
+ return unless request
673
+
674
+ response = ErrorResponse.new(request, error)
675
+ request.response = response
676
+ request.emit(:response, response)
666
677
  end
667
678
 
668
679
  def set_request_timeouts(request)
669
- write_timeout = request.write_timeout
680
+ set_request_write_timeout(request)
681
+ set_request_read_timeout(request)
682
+ set_request_request_timeout(request)
683
+ end
684
+
685
+ def set_request_read_timeout(request)
670
686
  read_timeout = request.read_timeout
671
- request_timeout = request.request_timeout
672
687
 
673
- unless write_timeout.nil? || write_timeout.infinite?
674
- set_request_timeout(request, write_timeout, :headers, %i[done response]) do
675
- write_timeout_callback(request, write_timeout)
676
- end
688
+ return if read_timeout.nil? || read_timeout.infinite?
689
+
690
+ set_request_timeout(request, read_timeout, :done, :response) do
691
+ read_timeout_callback(request, read_timeout)
677
692
  end
693
+ end
678
694
 
679
- unless read_timeout.nil? || read_timeout.infinite?
680
- set_request_timeout(request, read_timeout, :done, :response) do
681
- read_timeout_callback(request, read_timeout)
682
- end
695
+ def set_request_write_timeout(request)
696
+ write_timeout = request.write_timeout
697
+
698
+ return if write_timeout.nil? || write_timeout.infinite?
699
+
700
+ set_request_timeout(request, write_timeout, :headers, %i[done response]) do
701
+ write_timeout_callback(request, write_timeout)
683
702
  end
703
+ end
704
+
705
+ def set_request_request_timeout(request)
706
+ request_timeout = request.request_timeout
684
707
 
685
708
  return if request_timeout.nil? || request_timeout.infinite?
686
709
 
687
- set_request_timeout(request, request_timeout, :headers, :response) do
710
+ set_request_timeout(request, request_timeout, :headers, :complete) do
688
711
  read_timeout_callback(request, request_timeout, RequestTimeoutError)
689
712
  end
690
713
  end
@@ -694,7 +717,8 @@ module HTTPX
694
717
 
695
718
  @write_buffer.clear
696
719
  error = WriteTimeoutError.new(request, nil, write_timeout)
697
- on_error(error)
720
+
721
+ on_error(error, request)
698
722
  end
699
723
 
700
724
  def read_timeout_callback(request, read_timeout, error_type = ReadTimeoutError)
@@ -704,7 +728,8 @@ module HTTPX
704
728
 
705
729
  @write_buffer.clear
706
730
  error = error_type.new(request, request.response, read_timeout)
707
- on_error(error)
731
+
732
+ on_error(error, request)
708
733
  end
709
734
 
710
735
  def set_request_timeout(request, timeout, start_event, finish_events, &callback)
data/lib/httpx/options.rb CHANGED
@@ -220,6 +220,7 @@ module HTTPX
220
220
  persistent
221
221
  ].each do |method_name|
222
222
  class_eval(<<-OUT, __FILE__, __LINE__ + 1)
223
+ # sets +v+ as the value of #{method_name}
223
224
  def option_#{method_name}(v); v; end # def option_smth(v); v; end
224
225
  OUT
225
226
  end
@@ -72,6 +72,9 @@ module HTTPX
72
72
  end
73
73
  end
74
74
 
75
+ # adds support for the following options:
76
+ #
77
+ # :aws_profile :: AWS account profile to retrieve credentials from.
75
78
  module OptionsMethods
76
79
  def option_aws_profile(value)
77
80
  String(value)
@@ -12,6 +12,7 @@ module HTTPX
12
12
  module AWSSigV4
13
13
  Credentials = Struct.new(:username, :password, :security_token)
14
14
 
15
+ # Signs requests using the AWS sigv4 signing.
15
16
  class Signer
16
17
  def initialize(
17
18
  service:,
@@ -149,6 +150,9 @@ module HTTPX
149
150
  end
150
151
  end
151
152
 
153
+ # adds support for the following options:
154
+ #
155
+ # :sigv4_signer :: instance of HTTPX::Plugins::AWSSigV4 used to sign requests.
152
156
  module OptionsMethods
153
157
  def option_sigv4_signer(value)
154
158
  value.is_a?(Signer) ? value : Signer.new(value)
@@ -97,6 +97,16 @@ module HTTPX
97
97
  end
98
98
  end
99
99
 
100
+ # adds support for the following options:
101
+ #
102
+ # :circuit_breaker_max_attempts :: the number of attempts the circuit allows, before it is opened (defaults to <tt>3</tt>).
103
+ # :circuit_breaker_reset_attempts_in :: the time a circuit stays open at most, before it resets (defaults to <tt>60</tt>).
104
+ # :circuit_breaker_break_on :: callable defining an alternative rule for a response to break
105
+ # (i.e. <tt>->(res) { res.status == 429 } </tt>)
106
+ # :circuit_breaker_break_in :: the time that must elapse before an open circuit can transit to the half-open state
107
+ # (defaults to <tt><60</tt>).
108
+ # :circuit_breaker_half_open_drip_rate :: the rate of requests a circuit allows to be performed when in an half-open state
109
+ # (defaults to <tt>1</tt>).
100
110
  module OptionsMethods
101
111
  def option_circuit_breaker_max_attempts(value)
102
112
  attempts = Integer(value)
@@ -70,6 +70,9 @@ module HTTPX
70
70
  end
71
71
  end
72
72
 
73
+ # adds support for the following options:
74
+ #
75
+ # :cookies :: cookie jar for the session (can be a Hash, an Array, an instance of HTTPX::Plugins::Cookies::CookieJar)
73
76
  module OptionsMethods
74
77
  def option_headers(*)
75
78
  value = super
@@ -20,6 +20,9 @@ module HTTPX
20
20
  end
21
21
  end
22
22
 
23
+ # adds support for the following options:
24
+ #
25
+ # :digest :: instance of HTTPX::Plugins::Authentication::Digest, used to authenticate requests in the session.
23
26
  module OptionsMethods
24
27
  def option_digest(value)
25
28
  raise TypeError, ":digest must be a #{Authentication::Digest}" unless value.is_a?(Authentication::Digest)
@@ -20,6 +20,11 @@ module HTTPX
20
20
  end
21
21
  end
22
22
 
23
+ # adds support for the following options:
24
+ #
25
+ # :expect_timeout :: time (in seconds) to wait for a 100-expect response,
26
+ # before retrying without the Expect header (defaults to <tt>2</tt>).
27
+ # :expect_threshold_size :: min threshold (in bytes) of the request payload to enable the 100-continue negotiation on.
23
28
  module OptionsMethods
24
29
  def option_expect_timeout(value)
25
30
  seconds = Float(value)
@@ -4,12 +4,17 @@ module HTTPX
4
4
  InsecureRedirectError = Class.new(Error)
5
5
  module Plugins
6
6
  #
7
- # This plugin adds support for following redirect (status 30X) responses.
7
+ # This plugin adds support for automatically following redirect (status 30X) responses.
8
8
  #
9
- # It has an upper bound of followed redirects (see *MAX_REDIRECTS*), after which it
10
- # will return the last redirect response. It will **not** raise an exception.
9
+ # It has a default upper bound of followed redirects (see *MAX_REDIRECTS* and the *max_redirects* option),
10
+ # after which it will return the last redirect response. It will **not** raise an exception.
11
11
  #
12
- # It also doesn't follow insecure redirects (https -> http) by default (see *follow_insecure_redirects*).
12
+ # It doesn't follow insecure redirects (https -> http) by default (see *follow_insecure_redirects*).
13
+ #
14
+ # It doesn't propagate authorization related headers to requests redirecting to different origins
15
+ # (see *allow_auth_to_other_origins*) to override.
16
+ #
17
+ # It allows customization of when to redirect via the *redirect_on* callback option).
13
18
  #
14
19
  # https://gitlab.com/os85/httpx/wikis/Follow-Redirects
15
20
  #
@@ -20,6 +25,14 @@ module HTTPX
20
25
 
21
26
  using URIExtensions
22
27
 
28
+ # adds support for the following options:
29
+ #
30
+ # :max_redirects :: max number of times a request will be redirected (defaults to <tt>3</tt>).
31
+ # :follow_insecure_redirects :: whether redirects to an "http://" URI, when coming from an "https//", are allowed
32
+ # (defaults to <tt>false</tt>).
33
+ # :allow_auth_to_other_origins :: whether auth-related headers, such as "Authorization", are propagated on redirection
34
+ # (defaults to <tt>false</tt>).
35
+ # :redirect_on :: optional callback which receives the redirect location and can halt the redirect chain if it returns <tt>false</tt>.
23
36
  module OptionsMethods
24
37
  def option_max_redirects(value)
25
38
  num = Integer(value)
@@ -44,6 +57,7 @@ module HTTPX
44
57
  end
45
58
 
46
59
  module InstanceMethods
60
+ # returns a session with the *max_redirects* option set to +n+
47
61
  def max_redirects(n)
48
62
  with(max_redirects: n.to_i)
49
63
  end
@@ -133,8 +147,16 @@ module HTTPX
133
147
  redirect_after = Utils.parse_retry_after(redirect_after)
134
148
 
135
149
  log { "redirecting after #{redirect_after} secs..." }
150
+
151
+ deactivate_connection(request, connections, options)
152
+
136
153
  pool.after(redirect_after) do
137
- send_request(retry_request, connections, options)
154
+ if request.response
155
+ # request has terminated abruptly meanwhile
156
+ retry_request.emit(:response, request.response)
157
+ else
158
+ send_request(retry_request, connections, options)
159
+ end
138
160
  end
139
161
  else
140
162
  send_request(retry_request, connections, options)
@@ -142,6 +164,7 @@ module HTTPX
142
164
  nil
143
165
  end
144
166
 
167
+ # :nodoc:
145
168
  def redirect_request_headers(original_uri, redirect_uri, headers, options)
146
169
  headers = headers.dup
147
170
 
@@ -156,6 +179,7 @@ module HTTPX
156
179
  headers
157
180
  end
158
181
 
182
+ # :nodoc:
159
183
  def __get_location_from_response(response)
160
184
  # @type var location_uri: http_uri
161
185
  location_uri = URI(response.headers["location"])
@@ -165,12 +189,15 @@ module HTTPX
165
189
  end
166
190
 
167
191
  module RequestMethods
192
+ # returns the top-most original HTTPX::Request from the redirect chain
168
193
  attr_accessor :root_request
169
194
 
195
+ # returns the follow-up redirect request, or itself
170
196
  def redirect_request
171
197
  @redirect_request || self
172
198
  end
173
199
 
200
+ # sets the follow-up redirect request
174
201
  def redirect_request=(req)
175
202
  @redirect_request = req
176
203
  req.root_request = @root_request || self
@@ -178,7 +205,7 @@ module HTTPX
178
205
  end
179
206
 
180
207
  def response
181
- return super unless @redirect_request
208
+ return super unless @redirect_request && @response.nil?
182
209
 
183
210
  @redirect_request.response
184
211
  end
@@ -187,6 +214,16 @@ module HTTPX
187
214
  @options.max_redirects || MAX_REDIRECTS
188
215
  end
189
216
  end
217
+
218
+ module ConnectionMethods
219
+ private
220
+
221
+ def set_request_request_timeout(request)
222
+ return unless request.root_request.nil?
223
+
224
+ super
225
+ end
226
+ end
190
227
  end
191
228
  register_plugin :follow_redirects, FollowRedirects
192
229
  end
@@ -32,9 +32,14 @@ module HTTPX
32
32
  !request.headers.key?("proxy-authorization") &&
33
33
  response.headers.key?("proxy-authenticate")
34
34
 
35
- connection = find_connection(request, connections, options)
35
+ uri = request.uri
36
36
 
37
- if connection.options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
37
+ proxy_options = proxy_options(uri, options)
38
+ connection = connections.find do |conn|
39
+ conn.match?(uri, proxy_options)
40
+ end
41
+
42
+ if connection && connection.options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
38
43
  request.transition(:idle)
39
44
  request.headers["proxy-authorization"] =
40
45
  connection.options.proxy.authenticate(request, response.headers["proxy-authenticate"])
@@ -89,6 +89,10 @@ module HTTPX
89
89
  end
90
90
  end
91
91
 
92
+ # adds support for the following options:
93
+ #
94
+ # :proxy :: proxy options defining *:uri*, *:username*, *:password* or
95
+ # *:scheme* (i.e. <tt>{ uri: "http://proxy" }</tt>)
92
96
  module OptionsMethods
93
97
  def option_proxy(value)
94
98
  value.is_a?(Parameters) ? value : Hash[value]
@@ -107,16 +111,29 @@ module HTTPX
107
111
  def find_connection(request, connections, options)
108
112
  return super unless options.respond_to?(:proxy)
109
113
 
110
- uri = URI(request.uri)
114
+ uri = request.uri
111
115
 
112
- proxy_opts = if (next_proxy = uri.find_proxy)
116
+ proxy_options = proxy_options(uri, options)
117
+
118
+ return super(request, connections, proxy_options) unless proxy_options.proxy
119
+
120
+ connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
121
+ unless connections.nil? || connections.include?(connection)
122
+ connections << connection
123
+ set_connection_callbacks(connection, connections, options)
124
+ end
125
+ connection
126
+ end
127
+
128
+ def proxy_options(request_uri, options)
129
+ proxy_opts = if (next_proxy = request_uri.find_proxy)
113
130
  { uri: next_proxy }
114
131
  else
115
132
  proxy = options.proxy
116
133
 
117
- return super unless proxy
134
+ return options unless proxy
118
135
 
119
- return super(request, connections, options.merge(proxy: nil)) unless proxy.key?(:uri)
136
+ return options.merge(proxy: nil) unless proxy.key?(:uri)
120
137
 
121
138
  @_proxy_uris ||= Array(proxy[:uri])
122
139
 
@@ -133,8 +150,8 @@ module HTTPX
133
150
  no_proxy = proxy[:no_proxy]
134
151
  no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
135
152
 
136
- return super(request, connections, options.merge(proxy: nil)) unless URI::Generic.use_proxy?(uri.host, next_proxy.host,
137
- next_proxy.port, no_proxy)
153
+ return options.merge(proxy: nil) unless URI::Generic.use_proxy?(request_uri.host, next_proxy.host,
154
+ next_proxy.port, no_proxy)
138
155
  end
139
156
 
140
157
  proxy.merge(uri: next_proxy)
@@ -142,19 +159,15 @@ module HTTPX
142
159
 
143
160
  proxy = Parameters.new(**proxy_opts)
144
161
 
145
- proxy_options = options.merge(proxy: proxy)
146
- connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
147
- unless connections.nil? || connections.include?(connection)
148
- connections << connection
149
- set_connection_callbacks(connection, connections, options)
150
- end
151
- connection
162
+ options.merge(proxy: proxy)
152
163
  end
153
164
 
154
165
  def fetch_response(request, connections, options)
155
166
  response = super
156
167
 
157
168
  if response.is_a?(ErrorResponse) && proxy_error?(request, response)
169
+ return response unless @_proxy_uris
170
+
158
171
  @_proxy_uris.shift
159
172
 
160
173
  # return last error response if no more proxies to try
@@ -3,7 +3,12 @@
3
3
  module HTTPX
4
4
  module Plugins
5
5
  #
6
- # This plugin adds support for retrying requests when certain errors happen.
6
+ # This plugin adds support for retrying requests when errors happen.
7
+ #
8
+ # It has a default max number of retries (see *MAX_RETRIES* and the *max_retries* option),
9
+ # after which it will return the last response, error or not. It will **not** raise an exception.
10
+ #
11
+ # It does not retry which are not considered idempotent (see *retry_change_requests* to override).
7
12
  #
8
13
  # https://gitlab.com/os85/httpx/wikis/Retries
9
14
  #
@@ -38,6 +43,14 @@ module HTTPX
38
43
  end
39
44
  end
40
45
 
46
+ # adds support for the following options:
47
+ #
48
+ # :max_retries :: max number of times a request will be retried (defaults to <tt>3</tt>).
49
+ # :retry_change_requests :: whether idempotent requests are retried (defaults to <tt>false</tt>).
50
+ # :retry_after:: seconds after which a request is retried; can also be a callable object (i.e. <tt>->(req, res) { ... } </tt>)
51
+ # :retry_jitter :: number of seconds applied to *:retry_after* (must be a callable, i.e. <tt>->(retry_after) { ... } </tt>).
52
+ # :retry_on :: callable which alternatively defines a different rule for when a response is to be retried
53
+ # (i.e. <tt>->(res) { ... }</tt>).
41
54
  module OptionsMethods
42
55
  def option_retry_after(value)
43
56
  # return early if callable
@@ -76,7 +89,7 @@ module HTTPX
76
89
 
77
90
  module InstanceMethods
78
91
  def max_retries(n)
79
- with(max_retries: n.to_i)
92
+ with(max_retries: n)
80
93
  end
81
94
 
82
95
  private
@@ -111,9 +124,17 @@ module HTTPX
111
124
 
112
125
  retry_start = Utils.now
113
126
  log { "retrying after #{retry_after} secs..." }
127
+
128
+ deactivate_connection(request, connections, options)
129
+
114
130
  pool.after(retry_after) do
115
- log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
116
- send_request(request, connections, options)
131
+ if request.response
132
+ # request has terminated abruptly meanwhile
133
+ request.emit(:response, request.response)
134
+ else
135
+ log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
136
+ send_request(request, connections, options)
137
+ end
117
138
  end
118
139
  else
119
140
  send_request(request, connections, options)
@@ -87,6 +87,9 @@ module HTTPX
87
87
  end
88
88
  end
89
89
 
90
+ # adds support for the following options:
91
+ #
92
+ # :allowed_schemes :: list of URI schemes allowed (defaults to <tt>["https", "http"]</tt>)
90
93
  module OptionsMethods
91
94
  def option_allowed_schemes(value)
92
95
  Array(value)
data/lib/httpx/pool.rb CHANGED
@@ -108,7 +108,7 @@ module HTTPX
108
108
  resolve_connection(connection) unless connection.family
109
109
  end
110
110
 
111
- def deactivate(connections)
111
+ def deactivate(*connections)
112
112
  connections.each do |connection|
113
113
  connection.deactivate
114
114
  deselect_connection(connection) if connection.state == :inactive
data/lib/httpx/request.rb CHANGED
@@ -83,7 +83,7 @@ module HTTPX
83
83
 
84
84
  @options = @body.options
85
85
 
86
- if @uri.relative?
86
+ if @uri.relative? || @uri.host.nil?
87
87
  origin = @options.origin
88
88
  raise(Error, "invalid URI: #{@uri}") unless origin
89
89
 
@@ -98,17 +98,17 @@ module HTTPX
98
98
  @persistent = @options.persistent
99
99
  end
100
100
 
101
- # the read timeout defied for this requet.
101
+ # the read timeout defined for this requet.
102
102
  def read_timeout
103
103
  @options.timeout[:read_timeout]
104
104
  end
105
105
 
106
- # the write timeout defied for this requet.
106
+ # the write timeout defined for this requet.
107
107
  def write_timeout
108
108
  @options.timeout[:write_timeout]
109
109
  end
110
110
 
111
- # the request timeout defied for this requet.
111
+ # the request timeout defined for this requet.
112
112
  def request_timeout
113
113
  @options.timeout[:request_timeout]
114
114
  end
@@ -117,10 +117,12 @@ module HTTPX
117
117
  @persistent
118
118
  end
119
119
 
120
+ # if the request contains trailer headers
120
121
  def trailers?
121
122
  defined?(@trailers)
122
123
  end
123
124
 
125
+ # returns an instance of HTTPX::Headers containing the trailer headers
124
126
  def trailers
125
127
  @trailers ||= @options.headers_class.new
126
128
  end
@@ -132,6 +134,7 @@ module HTTPX
132
134
  :w
133
135
  end
134
136
 
137
+ # merges +h+ into the instance of HTTPX::Headers of the request.
135
138
  def merge_headers(h)
136
139
  @headers = @headers.merge(h)
137
140
  end
@@ -71,9 +71,11 @@ module HTTPX
71
71
  connection = @options.connection_class.new(@uri, @options.merge(ssl: { alpn_protocols: %w[h2] }))
72
72
  @pool.init_connection(connection, @options)
73
73
  # only explicity emit addresses if connection didn't pre-resolve, i.e. it's not an IP.
74
- emit_addresses(connection, @family, @uri_addresses) unless connection.addresses
75
- @building_connection = false
76
- connection
74
+ catch(:coalesced) do
75
+ @building_connection = false
76
+ emit_addresses(connection, @family, @uri_addresses) unless connection.addresses
77
+ connection
78
+ end
77
79
  end
78
80
  end
79
81
 
@@ -199,7 +201,7 @@ module HTTPX
199
201
  @queries.delete_if { |_, conn| connection == conn }
200
202
 
201
203
  Resolver.cached_lookup_set(hostname, @family, addresses) if @resolver_options[:cache]
202
- emit_addresses(connection, @family, addresses.map { |addr| addr["data"] })
204
+ catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |addr| addr["data"] }) }
203
205
  end
204
206
  end
205
207
  return if @connections.empty?
@@ -329,7 +329,7 @@ module HTTPX
329
329
  @timeouts.delete(connection.origin.host)
330
330
  @connections.delete(connection)
331
331
  Resolver.cached_lookup_set(connection.origin.host, @family, addresses) if @resolver_options[:cache]
332
- emit_addresses(connection, @family, addresses.map { |addr| addr["data"] })
332
+ catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |addr| addr["data"] }) }
333
333
  end
334
334
  end
335
335
  return emit(:close) if @connections.empty?
@@ -127,7 +127,7 @@ module HTTPX
127
127
  @queries.delete(pair)
128
128
 
129
129
  family, connection = pair
130
- emit_addresses(connection, family, addrs)
130
+ catch(:coalesced) { emit_addresses(connection, family, addrs) }
131
131
  when ERROR
132
132
  *pair, error = @pipe_mutex.synchronize { @ips.pop }
133
133
  @queries.delete(pair)
data/lib/httpx/session.rb CHANGED
@@ -125,6 +125,7 @@ module HTTPX
125
125
  connection
126
126
  end
127
127
 
128
+ # sends the +request+ to the corresponding HTTPX::Connection
128
129
  def send_request(request, connections, options = request.options)
129
130
  error = catch(:resolve_error) do
130
131
  connection = find_connection(request, connections, options)
@@ -231,6 +232,14 @@ module HTTPX
231
232
  end
232
233
  end
233
234
 
235
+ def deactivate_connection(request, connections, options)
236
+ conn = connections.find do |c|
237
+ c.match?(request.uri, options)
238
+ end
239
+
240
+ pool.deactivate(conn) if conn
241
+ end
242
+
234
243
  # sends an array of HTTPX::Request +requests+, returns the respective array of HTTPX::Response objects.
235
244
  def send_requests(*requests)
236
245
  connections = _send_requests(requests)
@@ -261,6 +270,7 @@ module HTTPX
261
270
  return responses unless request
262
271
 
263
272
  catch(:coalesced) { pool.next_tick } until (response = fetch_response(request, connections, request.options))
273
+ request.emit(:complete, response)
264
274
 
265
275
  responses << response
266
276
  requests.shift
@@ -274,14 +284,16 @@ module HTTPX
274
284
  # opportunity to traverse the requests, hence we're returning only a fraction of the errors
275
285
  # we were supposed to. This effectively fetches the existing responses and return them.
276
286
  while (request = requests.shift)
277
- responses << fetch_response(request, connections, request.options)
287
+ response = fetch_response(request, connections, request.options)
288
+ request.emit(:complete, response) if response
289
+ responses << response
278
290
  end
279
291
  break
280
292
  end
281
293
  responses
282
294
  ensure
283
295
  if @persistent
284
- pool.deactivate(connections)
296
+ pool.deactivate(*connections)
285
297
  else
286
298
  close(connections)
287
299
  end
@@ -3,7 +3,7 @@
3
3
  require_relative "session"
4
4
  module HTTPX
5
5
  class Session
6
- def initialize(options = EMPTY_HASH, &blk)
6
+ def initialize(options = EMPTY, &blk)
7
7
  @options = self.class.default_options.merge(options)
8
8
  @responses = {}
9
9
  @persistent = @options.persistent
@@ -13,6 +13,8 @@ module HTTPX::Transcoder
13
13
 
14
14
  def_delegator :@raw, :to_s
15
15
 
16
+ def_delegator :@raw, :==
17
+
16
18
  def initialize(body)
17
19
  @raw = body
18
20
  end
@@ -20,6 +20,8 @@ module HTTPX
20
20
 
21
21
  def_delegator :@raw, :bytesize
22
22
 
23
+ def_delegator :@raw, :==
24
+
23
25
  def initialize(form)
24
26
  @raw = form.each_with_object("".b) do |(key, val), buf|
25
27
  HTTPX::Transcoder.normalize_keys(key, val) do |k, v|
@@ -15,6 +15,8 @@ module HTTPX::Transcoder
15
15
 
16
16
  def_delegator :@raw, :bytesize
17
17
 
18
+ def_delegator :@raw, :==
19
+
18
20
  def initialize(json)
19
21
  @raw = JSON.json_dump(json)
20
22
  @charset = @raw.encoding.name.downcase
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.3.0"
4
+ VERSION = "1.3.2"
5
5
  end
@@ -38,7 +38,7 @@ module HTTPX
38
38
 
39
39
  def consume: () -> void
40
40
 
41
- def handle_error: (StandardError ex) -> void
41
+ def handle_error: (StandardError ex, ?Request? request) -> void
42
42
 
43
43
  def on_start: () -> void
44
44
 
@@ -32,7 +32,7 @@ module HTTPX
32
32
 
33
33
  def consume: () -> void
34
34
 
35
- def handle_error: (StandardError ex) -> void
35
+ def handle_error: (StandardError ex, ?Request? request) -> void
36
36
 
37
37
  def ping: () -> void
38
38
 
data/sig/connection.rbs CHANGED
@@ -119,14 +119,20 @@ module HTTPX
119
119
 
120
120
  def build_socket: (?Array[ipaddr]? addrs) -> (TCP | SSL | UNIX)
121
121
 
122
- def on_error: (HTTPX::TimeoutError | Error | StandardError error) -> void
122
+ def on_error: (HTTPX::TimeoutError | Error | StandardError error, ?Request? request) -> void
123
123
 
124
- def handle_error: (StandardError error) -> void
124
+ def handle_error: (StandardError error, ?Request? request) -> void
125
125
 
126
126
  def purge_after_closed: () -> void
127
127
 
128
128
  def set_request_timeouts: (Request request) -> void
129
129
 
130
+ def set_request_read_timeout: (Request request) -> void
131
+
132
+ def set_request_write_timeout: (Request request) -> void
133
+
134
+ def set_request_request_timeout: (Request request) -> void
135
+
130
136
  def write_timeout_callback: (Request request, Numeric write_timeout) -> void
131
137
 
132
138
  def read_timeout_callback: (Request request, Numeric read_timeout, ?singleton(RequestTimeoutError) error_type) -> void
@@ -43,6 +43,8 @@ module HTTPX
43
43
  private
44
44
 
45
45
  def proxy_error?: (Request request, response) -> bool
46
+
47
+ def proxy_options: (http_uri request_uri, Options & _ProxyOptions options) -> (Options & _ProxyOptions)
46
48
  end
47
49
 
48
50
  module ConnectionMethods
data/sig/pool.rbs CHANGED
@@ -19,7 +19,7 @@ module HTTPX
19
19
 
20
20
  def find_connection: (URI::Generic uri, Options options) -> Connection?
21
21
 
22
- def deactivate: (*Array[Connection]) -> void
22
+ def deactivate: (*Connection connections) -> void
23
23
 
24
24
  private
25
25
 
@@ -28,7 +28,7 @@ module HTTPX
28
28
 
29
29
  def initialize: (ip_family? family, Options options) -> void
30
30
 
31
- def early_resolve: (Connection connection, ?hostname: String) -> void
31
+ def early_resolve: (Connection connection, ?hostname: String) -> boolish
32
32
 
33
33
  def emit_resolve_error: (Connection connection, ?String hostname, ?StandardError) -> void
34
34
 
data/sig/session.rbs CHANGED
@@ -32,6 +32,8 @@ module HTTPX
32
32
 
33
33
  def find_connection: (Request request, Array[Connection] connections, Options options) -> Connection
34
34
 
35
+ def deactivate_connection: (Request request, Array[Connection] connections, Options options) -> void
36
+
35
37
  def send_request: (Request request, Array[Connection] connections, ?Options options) -> void
36
38
 
37
39
  def set_connection_callbacks: (Connection connection, Array[Connection] connections, Options options, ?cloned: bool) -> void
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.3.0
4
+ version: 1.3.2
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-07-10 00:00:00.000000000 Z
11
+ date: 2024-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2
@@ -145,6 +145,8 @@ extra_rdoc_files:
145
145
  - doc/release_notes/1_2_5.md
146
146
  - doc/release_notes/1_2_6.md
147
147
  - doc/release_notes/1_3_0.md
148
+ - doc/release_notes/1_3_1.md
149
+ - doc/release_notes/1_3_2.md
148
150
  files:
149
151
  - LICENSE.txt
150
152
  - README.md
@@ -261,6 +263,8 @@ files:
261
263
  - doc/release_notes/1_2_5.md
262
264
  - doc/release_notes/1_2_6.md
263
265
  - doc/release_notes/1_3_0.md
266
+ - doc/release_notes/1_3_1.md
267
+ - doc/release_notes/1_3_2.md
264
268
  - lib/httpx.rb
265
269
  - lib/httpx/adapters/datadog.rb
266
270
  - lib/httpx/adapters/faraday.rb