httpx 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +7 -3
- data/lib/httpx/adapters/faraday.rb +2 -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 +24 -13
- data/lib/httpx/plugins/retries.rb +25 -4
- data/lib/httpx/plugins/ssrf_filter.rb +3 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +1 -1
- data/lib/httpx/request.rb +6 -3
- 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/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 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0162427fe818aafee88a35a12a821cf4f1016701f4513835c91556a7e8579e9e
|
4
|
+
data.tar.gz: f559d77efcdbf0e557c28c9a012cb67f45a95de8f8c2fac521800679ea30dc31
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1dd2fa825bdaef26137e8c5d993dcb426eab7897adc8570a343863e7842f3732948bdc827c58e1e361bb35b2c1d549b02d89051a592a836a51f39afb95a77263
|
7
|
+
data.tar.gz: 8a64f6fc512fd8fa60d7feeffff6447d3c8a4081fe6494873b4a527f425dad090ca84b810a2a91477b051a4dfeb0bbb12e0563ef37c20e8d5daab05f2a208138
|
@@ -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.
|
@@ -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,13 +159,7 @@ 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)
|
@@ -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)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thread"
|
4
|
+
|
5
|
+
module HTTPX
|
6
|
+
class SynchPool < Pool
|
7
|
+
def initialize(options)
|
8
|
+
super
|
9
|
+
|
10
|
+
@connections = ConnectionStore.new(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
# TODO: #wrap
|
14
|
+
def find_or_new_connection(uri, options, &blk)
|
15
|
+
@connections.find_or_new(uri, options) do |new_conn|
|
16
|
+
catch(:coalesced) do
|
17
|
+
init_connection(new_conn, options)
|
18
|
+
blk.call(new_conn) if blk
|
19
|
+
new_conn
|
20
|
+
end
|
21
|
+
end
|
22
|
+
find_connection(uri, options) || new_connection(uri, options, &blk)
|
23
|
+
end
|
24
|
+
|
25
|
+
class ConnectionManager
|
26
|
+
include Enumerable
|
27
|
+
|
28
|
+
def initialize(limit = 3)
|
29
|
+
@connections = []
|
30
|
+
@used = 0
|
31
|
+
@limit = limit
|
32
|
+
end
|
33
|
+
|
34
|
+
def each(*args, &blk)
|
35
|
+
@connections.each(*args, &blk)
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_or_new(uri, options, &blk)
|
39
|
+
raise "over limit" if @used >= @limit
|
40
|
+
|
41
|
+
@used += 1
|
42
|
+
conn = @connections.find do |connection|
|
43
|
+
connection.match?(uri, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
if conn
|
47
|
+
@connections.delete(conn)
|
48
|
+
else
|
49
|
+
conn = options.connection_class.new(uri, options)
|
50
|
+
blk[conn]
|
51
|
+
end
|
52
|
+
|
53
|
+
conn
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ConnectionStore
|
58
|
+
include Enumerable
|
59
|
+
|
60
|
+
def initialize(options)
|
61
|
+
@connections = Hash.new { |hs, k| hs[k] ||= ConnectionManager.new }
|
62
|
+
@conn_mtx = Thread::Mutex.new
|
63
|
+
@conn_waiter = ConditionVariable.new
|
64
|
+
@timeout = Float(options.fetch(:pool_timeout, 5))
|
65
|
+
end
|
66
|
+
|
67
|
+
def each(&block)
|
68
|
+
return enum_for(__meth__) unless block
|
69
|
+
|
70
|
+
@conn_mtx.synchronize do
|
71
|
+
@connections.each_value do |conns|
|
72
|
+
conns.each(&block)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_or_new(uri, options, &blk)
|
78
|
+
@connections[uri.origin].find_or_new(uri, options, &blk)
|
79
|
+
end
|
80
|
+
|
81
|
+
# def <<(conn)
|
82
|
+
# @conn_mtx.synchronize do
|
83
|
+
# origin, conns = @connections.find { |_orig, _| conn.origins.include?(origin) }
|
84
|
+
# (conns || @connections[conn.origin.to_s]) << conn
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
|
88
|
+
def empty?
|
89
|
+
@conn_mtx.synchronize { super }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
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
@@ -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/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.1
|
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-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2
|
@@ -145,6 +145,7 @@ 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
|
148
149
|
files:
|
149
150
|
- LICENSE.txt
|
150
151
|
- README.md
|
@@ -261,6 +262,7 @@ files:
|
|
261
262
|
- doc/release_notes/1_2_5.md
|
262
263
|
- doc/release_notes/1_2_6.md
|
263
264
|
- doc/release_notes/1_3_0.md
|
265
|
+
- doc/release_notes/1_3_1.md
|
264
266
|
- lib/httpx.rb
|
265
267
|
- lib/httpx/adapters/datadog.rb
|
266
268
|
- lib/httpx/adapters/faraday.rb
|
@@ -333,6 +335,7 @@ files:
|
|
333
335
|
- lib/httpx/plugins/webdav.rb
|
334
336
|
- lib/httpx/pmatch_extensions.rb
|
335
337
|
- lib/httpx/pool.rb
|
338
|
+
- lib/httpx/pool/synch_pool.rb
|
336
339
|
- lib/httpx/punycode.rb
|
337
340
|
- lib/httpx/request.rb
|
338
341
|
- lib/httpx/request/body.rb
|
@@ -477,7 +480,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
477
480
|
- !ruby/object:Gem::Version
|
478
481
|
version: '0'
|
479
482
|
requirements: []
|
480
|
-
rubygems_version: 3.
|
483
|
+
rubygems_version: 3.4.10
|
481
484
|
signing_key:
|
482
485
|
specification_version: 4
|
483
486
|
summary: HTTPX, to the future, and beyond
|