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.
- checksums.yaml +4 -4
- data/doc/release_notes/1_3_1.md +17 -0
- data/doc/release_notes/1_3_2.md +6 -0
- data/lib/httpx/adapters/datadog.rb +7 -3
- data/lib/httpx/adapters/faraday.rb +2 -1
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/connection/http1.rb +9 -5
- data/lib/httpx/connection/http2.rb +9 -5
- data/lib/httpx/connection.rb +46 -21
- data/lib/httpx/options.rb +1 -0
- data/lib/httpx/plugins/aws_sdk_authentication.rb +3 -0
- data/lib/httpx/plugins/aws_sigv4.rb +4 -0
- data/lib/httpx/plugins/circuit_breaker.rb +10 -0
- data/lib/httpx/plugins/cookies.rb +3 -0
- data/lib/httpx/plugins/digest_auth.rb +3 -0
- data/lib/httpx/plugins/expect.rb +5 -0
- data/lib/httpx/plugins/follow_redirects.rb +43 -6
- data/lib/httpx/plugins/proxy/http.rb +7 -2
- data/lib/httpx/plugins/proxy.rb +26 -13
- data/lib/httpx/plugins/retries.rb +25 -4
- data/lib/httpx/plugins/ssrf_filter.rb +3 -0
- data/lib/httpx/pool.rb +1 -1
- data/lib/httpx/request.rb +7 -4
- data/lib/httpx/resolver/https.rb +6 -4
- data/lib/httpx/resolver/native.rb +1 -1
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/session.rb +14 -2
- data/lib/httpx/session2.rb +1 -1
- data/lib/httpx/transcoder/body.rb +2 -0
- data/lib/httpx/transcoder/form.rb +2 -0
- data/lib/httpx/transcoder/json.rb +2 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/connection/http1.rbs +1 -1
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +8 -2
- data/sig/plugins/proxy.rbs +2 -0
- data/sig/pool.rbs +1 -1
- data/sig/resolver/resolver.rbs +1 -1
- data/sig/session.rbs +2 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e417c7a6d7564807d040374d9174d5b1fd3945ce924062397fdc0ad95d12bd31
|
4
|
+
data.tar.gz: bf001578d74624abc4d05e74563c2d9a5388cbd6315cd29fb0cf176d2e4e01e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 |
|
204
|
-
|
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
|
@@ -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 |
|
215
|
-
|
214
|
+
@requests.each do |req|
|
215
|
+
next if request && request == req
|
216
|
+
|
217
|
+
emit(:error, req, ex)
|
216
218
|
end
|
217
|
-
@pending.each do |
|
218
|
-
|
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 |
|
135
|
-
|
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 |
|
138
|
-
|
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
|
|
data/lib/httpx/connection.rb
CHANGED
@@ -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 (
|
662
|
-
|
663
|
-
|
664
|
-
|
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
|
-
|
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
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
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
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
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, :
|
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
|
-
|
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
|
-
|
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
@@ -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)
|
data/lib/httpx/plugins/expect.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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
|
-
|
35
|
+
uri = request.uri
|
36
36
|
|
37
|
-
|
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"])
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -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 =
|
114
|
+
uri = request.uri
|
111
115
|
|
112
|
-
|
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
|
134
|
+
return options unless proxy
|
118
135
|
|
119
|
-
return
|
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
|
137
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
116
|
-
|
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)
|
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
|
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
|
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
|
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
|
data/lib/httpx/resolver/https.rb
CHANGED
@@ -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
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
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
|
data/lib/httpx/session2.rb
CHANGED
data/lib/httpx/version.rb
CHANGED
data/sig/connection/http1.rbs
CHANGED
data/sig/connection/http2.rbs
CHANGED
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
|
data/sig/plugins/proxy.rbs
CHANGED
data/sig/pool.rbs
CHANGED
data/sig/resolver/resolver.rbs
CHANGED
@@ -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) ->
|
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.
|
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-
|
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
|