httpx 1.3.0 → 1.3.2

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