httpx 0.15.4 → 0.18.0
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/0_16_0.md +93 -0
- data/doc/release_notes/0_16_1.md +5 -0
- data/doc/release_notes/0_17_0.md +49 -0
- data/doc/release_notes/0_18_0.md +69 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +8 -14
- data/lib/httpx/adapters/webmock.rb +9 -3
- data/lib/httpx/altsvc.rb +2 -2
- data/lib/httpx/buffer.rb +1 -1
- data/lib/httpx/callbacks.rb +1 -1
- data/lib/httpx/chainable.rb +18 -11
- data/lib/httpx/connection/http1.rb +21 -13
- data/lib/httpx/connection/http2.rb +20 -25
- data/lib/httpx/connection.rb +73 -77
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/errors.rb +11 -11
- data/lib/httpx/extensions.rb +50 -4
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io/ssl.rb +3 -3
- data/lib/httpx/io/tls.rb +8 -8
- data/lib/httpx/loggable.rb +5 -5
- data/lib/httpx/options.rb +108 -81
- data/lib/httpx/parser/http1.rb +11 -7
- data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
- data/lib/httpx/plugins/aws_sigv4.rb +19 -20
- data/lib/httpx/plugins/compression.rb +17 -14
- data/lib/httpx/plugins/cookies/cookie.rb +4 -2
- data/lib/httpx/plugins/cookies/jar.rb +21 -2
- data/lib/httpx/plugins/cookies.rb +20 -7
- data/lib/httpx/plugins/digest_authentication.rb +19 -15
- data/lib/httpx/plugins/expect.rb +26 -18
- data/lib/httpx/plugins/follow_redirects.rb +9 -9
- data/lib/httpx/plugins/grpc/call.rb +4 -1
- data/lib/httpx/plugins/grpc/message.rb +2 -2
- data/lib/httpx/plugins/grpc.rb +72 -46
- data/lib/httpx/plugins/h2c.rb +7 -3
- data/lib/httpx/plugins/internal_telemetry.rb +8 -8
- data/lib/httpx/plugins/multipart/decoder.rb +187 -0
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
- data/lib/httpx/plugins/multipart/part.rb +2 -2
- data/lib/httpx/plugins/multipart.rb +16 -2
- data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
- data/lib/httpx/plugins/proxy/socks4.rb +2 -1
- data/lib/httpx/plugins/proxy/socks5.rb +2 -1
- data/lib/httpx/plugins/proxy/ssh.rb +20 -13
- data/lib/httpx/plugins/proxy.rb +10 -10
- data/lib/httpx/plugins/response_cache/store.rb +55 -0
- data/lib/httpx/plugins/response_cache.rb +88 -0
- data/lib/httpx/plugins/retries.rb +46 -23
- data/lib/httpx/plugins/stream.rb +3 -4
- data/lib/httpx/plugins/upgrade.rb +7 -6
- data/lib/httpx/pool.rb +39 -13
- data/lib/httpx/registry.rb +2 -2
- data/lib/httpx/request.rb +16 -25
- data/lib/httpx/resolver/https.rb +4 -8
- data/lib/httpx/resolver/native.rb +19 -5
- data/lib/httpx/resolver/resolver_mixin.rb +2 -1
- data/lib/httpx/resolver/system.rb +2 -0
- data/lib/httpx/resolver.rb +2 -2
- data/lib/httpx/response.rb +91 -48
- data/lib/httpx/selector.rb +11 -24
- data/lib/httpx/session.rb +41 -23
- data/lib/httpx/session2.rb +23 -0
- data/lib/httpx/timers.rb +84 -0
- data/lib/httpx/transcoder/body.rb +3 -2
- data/lib/httpx/transcoder/chunker.rb +2 -1
- data/lib/httpx/transcoder/form.rb +20 -0
- data/lib/httpx/transcoder/json.rb +12 -0
- data/lib/httpx/transcoder.rb +62 -1
- data/lib/httpx/utils.rb +10 -2
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +7 -3
- data/sig/buffer.rbs +3 -1
- data/sig/chainable.rbs +31 -29
- data/sig/connection/http1.rbs +11 -5
- data/sig/connection/http2.rbs +16 -5
- data/sig/connection.rbs +31 -13
- data/sig/errors.rbs +35 -1
- data/sig/headers.rbs +20 -19
- data/sig/httpx.rbs +4 -1
- data/sig/loggable.rbs +3 -1
- data/sig/options.rbs +45 -34
- data/sig/parser/http1.rbs +3 -3
- data/sig/plugins/authentication.rbs +1 -1
- data/sig/plugins/aws_sdk_authentication.rbs +25 -3
- data/sig/plugins/aws_sigv4.rbs +13 -5
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/compression.rbs +4 -6
- data/sig/plugins/cookies/cookie.rbs +5 -7
- data/sig/plugins/cookies/jar.rbs +9 -10
- data/sig/plugins/cookies.rbs +4 -5
- data/sig/plugins/digest_authentication.rbs +2 -3
- data/sig/plugins/expect.rbs +2 -4
- data/sig/plugins/follow_redirects.rbs +3 -5
- data/sig/plugins/grpc.rbs +4 -7
- data/sig/plugins/h2c.rbs +0 -2
- data/sig/plugins/multipart.rbs +64 -10
- data/sig/plugins/ntlm_authentication.rbs +2 -3
- data/sig/plugins/persistent.rbs +3 -8
- data/sig/plugins/proxy/ssh.rbs +4 -4
- data/sig/plugins/proxy.rbs +13 -13
- data/sig/plugins/push_promise.rbs +0 -2
- data/sig/plugins/response_cache.rbs +35 -0
- data/sig/plugins/retries.rbs +7 -8
- data/sig/plugins/stream.rbs +1 -1
- data/sig/plugins/upgrade.rbs +2 -3
- data/sig/pool.rbs +7 -2
- data/sig/registry.rbs +1 -1
- data/sig/request.rbs +11 -8
- data/sig/resolver/native.rbs +10 -5
- data/sig/resolver/resolver_mixin.rbs +4 -5
- data/sig/resolver/system.rbs +4 -0
- data/sig/resolver.rbs +7 -0
- data/sig/response.rbs +26 -13
- data/sig/selector.rbs +11 -9
- data/sig/session.rbs +22 -23
- data/sig/timers.rbs +32 -0
- data/sig/transcoder/body.rbs +6 -1
- data/sig/transcoder/chunker.rbs +8 -2
- data/sig/transcoder/form.rbs +3 -1
- data/sig/transcoder/json.rbs +2 -0
- data/sig/transcoder.rbs +13 -5
- data/sig/utils.rbs +6 -0
- metadata +18 -18
- data/lib/httpx/request2.rb +0 -14
|
@@ -29,13 +29,14 @@ module HTTPX
|
|
|
29
29
|
# in order not to break legacy code, we'll keep loading http/form_data for them.
|
|
30
30
|
require "http/form_data"
|
|
31
31
|
warn "httpx: http/form_data is no longer a requirement to use HTTPX :multipart plugin. See migration instructions under" \
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
"https://honeyryderchuck.gitlab.io/httpx/wiki/Multipart-Uploads.html#notes. \n\n" \
|
|
33
|
+
"If you'd like to stop seeing this message, require 'http/form_data' yourself."
|
|
34
34
|
end
|
|
35
35
|
rescue LoadError
|
|
36
36
|
end
|
|
37
37
|
# :nocov:
|
|
38
38
|
require "httpx/plugins/multipart/encoder"
|
|
39
|
+
require "httpx/plugins/multipart/decoder"
|
|
39
40
|
require "httpx/plugins/multipart/part"
|
|
40
41
|
require "httpx/plugins/multipart/mime_type_detector"
|
|
41
42
|
end
|
|
@@ -56,6 +57,19 @@ module HTTPX
|
|
|
56
57
|
end
|
|
57
58
|
end
|
|
58
59
|
|
|
60
|
+
def decode(response)
|
|
61
|
+
content_type = response.content_type.mime_type
|
|
62
|
+
|
|
63
|
+
case content_type
|
|
64
|
+
when "application/x-www-form-urlencoded"
|
|
65
|
+
Transcoder::Form.decode(response)
|
|
66
|
+
when "multipart/form-data"
|
|
67
|
+
Decoder.new(response)
|
|
68
|
+
else
|
|
69
|
+
raise Error, "invalid form mime type (#{content_type})"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
59
73
|
def multipart?(data)
|
|
60
74
|
data.any? do |_, v|
|
|
61
75
|
MULTIPART_VALUE_COND.call(v) ||
|
|
@@ -15,13 +15,15 @@ module HTTPX
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def extra_options(options)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
options.merge(max_concurrent_requests: 1)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module OptionsMethods
|
|
23
|
+
def option_ntlm(value)
|
|
24
|
+
raise TypeError, ":ntlm must be a #{NTLMParams}" unless value.is_a?(NTLMParams)
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
OUT
|
|
24
|
-
end.new(options).merge(max_concurrent_requests: 1)
|
|
26
|
+
value
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
|
|
@@ -32,13 +34,13 @@ module HTTPX
|
|
|
32
34
|
|
|
33
35
|
alias_method :ntlm_auth, :ntlm_authentication
|
|
34
36
|
|
|
35
|
-
def send_requests(*requests
|
|
37
|
+
def send_requests(*requests)
|
|
36
38
|
requests.flat_map do |request|
|
|
37
39
|
ntlm = request.options.ntlm
|
|
38
40
|
|
|
39
41
|
if ntlm
|
|
40
42
|
request.headers["authorization"] = "NTLM #{NTLM.negotiate(domain: ntlm.domain).to_base64}"
|
|
41
|
-
probe_response = wrap { super(request
|
|
43
|
+
probe_response = wrap { super(request).first }
|
|
42
44
|
|
|
43
45
|
if !probe_response.is_a?(ErrorResponse) && probe_response.status == 401 &&
|
|
44
46
|
probe_response.headers.key?("www-authenticate") &&
|
|
@@ -50,12 +52,12 @@ module HTTPX
|
|
|
50
52
|
request.transition(:idle)
|
|
51
53
|
|
|
52
54
|
request.headers["authorization"] = "NTLM #{ntlm_challenge}"
|
|
53
|
-
super(request
|
|
55
|
+
super(request)
|
|
54
56
|
else
|
|
55
57
|
probe_response
|
|
56
58
|
end
|
|
57
59
|
else
|
|
58
|
-
super(request
|
|
60
|
+
super(request)
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -6,23 +6,27 @@ module HTTPX
|
|
|
6
6
|
module Plugins
|
|
7
7
|
module Proxy
|
|
8
8
|
module SSH
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
class << self
|
|
10
|
+
def load_dependencies(*)
|
|
11
|
+
require "net/ssh/gateway"
|
|
12
|
+
end
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
OUT
|
|
18
|
-
end.new(options)
|
|
15
|
+
module OptionsMethods
|
|
16
|
+
def option_proxy(value)
|
|
17
|
+
Hash[value]
|
|
18
|
+
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
module InstanceMethods
|
|
22
|
-
|
|
22
|
+
def request(*args, **options)
|
|
23
|
+
raise ArgumentError, "must perform at least one request" if args.empty?
|
|
24
|
+
|
|
25
|
+
requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
request = requests.first or return super
|
|
28
|
+
|
|
29
|
+
request_options = request.options
|
|
26
30
|
|
|
27
31
|
return super unless request_options.proxy
|
|
28
32
|
|
|
@@ -37,18 +41,21 @@ module HTTPX
|
|
|
37
41
|
if request_options.debug
|
|
38
42
|
ssh_options[:verbose] = request_options.debug_level == 2 ? :debug : :info
|
|
39
43
|
end
|
|
44
|
+
|
|
40
45
|
request_uri = URI(requests.first.uri)
|
|
41
46
|
@_gateway = Net::SSH::Gateway.new(ssh_uri.host, ssh_username, ssh_options)
|
|
42
47
|
begin
|
|
43
48
|
@_gateway.open(request_uri.host, request_uri.port) do |local_port|
|
|
44
49
|
io = build_gateway_socket(local_port, request_uri, request_options)
|
|
45
|
-
super(*
|
|
50
|
+
super(*args, **options.merge(io: io))
|
|
46
51
|
end
|
|
47
52
|
ensure
|
|
48
53
|
@_gateway.shutdown!
|
|
49
54
|
end
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
private
|
|
58
|
+
|
|
52
59
|
def build_gateway_socket(port, request_uri, options)
|
|
53
60
|
case request_uri.scheme
|
|
54
61
|
when "https"
|
|
@@ -65,7 +72,7 @@ module HTTPX
|
|
|
65
72
|
when "http"
|
|
66
73
|
TCPSocket.open("localhost", port)
|
|
67
74
|
else
|
|
68
|
-
raise
|
|
75
|
+
raise TypeError, "unexpected scheme: #{request_uri.scheme}"
|
|
69
76
|
end
|
|
70
77
|
end
|
|
71
78
|
end
|
data/lib/httpx/plugins/proxy.rb
CHANGED
|
@@ -5,7 +5,8 @@ require "ipaddr"
|
|
|
5
5
|
require "forwardable"
|
|
6
6
|
|
|
7
7
|
module HTTPX
|
|
8
|
-
HTTPProxyError
|
|
8
|
+
class HTTPProxyError < Error; end
|
|
9
|
+
|
|
9
10
|
module Plugins
|
|
10
11
|
#
|
|
11
12
|
# This plugin adds support for proxies. It ships with support for:
|
|
@@ -64,13 +65,11 @@ module HTTPX
|
|
|
64
65
|
klass.plugin(:"proxy/socks4")
|
|
65
66
|
klass.plugin(:"proxy/socks5")
|
|
66
67
|
end
|
|
68
|
+
end
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
value.is_a?(#{Parameters}) ? value : Hash[value]
|
|
72
|
-
OUT
|
|
73
|
-
end.new(options)
|
|
70
|
+
module OptionsMethods
|
|
71
|
+
def option_proxy(value)
|
|
72
|
+
value.is_a?(Parameters) ? value : Hash[value]
|
|
74
73
|
end
|
|
75
74
|
end
|
|
76
75
|
|
|
@@ -138,10 +137,11 @@ module HTTPX
|
|
|
138
137
|
def __proxy_error?(response)
|
|
139
138
|
error = response.error
|
|
140
139
|
case error
|
|
141
|
-
when
|
|
140
|
+
when NativeResolveError
|
|
142
141
|
# failed resolving proxy domain
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
error.connection.origin.to_s == @_proxy_uris.first
|
|
143
|
+
when ResolveError
|
|
144
|
+
error.message.end_with?(@_proxy_uris.first)
|
|
145
145
|
when *PROXY_ERRORS
|
|
146
146
|
# timeout errors connecting to proxy
|
|
147
147
|
true
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module HTTPX::Plugins
|
|
6
|
+
module ResponseCache
|
|
7
|
+
class Store
|
|
8
|
+
extend Forwardable
|
|
9
|
+
|
|
10
|
+
def_delegator :@store, :clear
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@store = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def lookup(uri)
|
|
17
|
+
@store[uri]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cached?(uri)
|
|
21
|
+
@store.key?(uri)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def cache(uri, response)
|
|
25
|
+
@store[uri] = response
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def prepare(request)
|
|
29
|
+
cached_response = @store[request.uri]
|
|
30
|
+
|
|
31
|
+
return unless cached_response
|
|
32
|
+
|
|
33
|
+
original_request = cached_response.instance_variable_get(:@request)
|
|
34
|
+
|
|
35
|
+
if (vary = cached_response.headers["vary"])
|
|
36
|
+
if vary == "*"
|
|
37
|
+
return unless request.headers.same_headers?(original_request.headers)
|
|
38
|
+
else
|
|
39
|
+
return unless vary.split(/ *, */).all? do |cache_field|
|
|
40
|
+
!original_request.headers.key?(cache_field) || request.headers[cache_field] == original_request.headers[cache_field]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if !request.headers.key?("if-modified-since") && (last_modified = cached_response.headers["last-modified"])
|
|
46
|
+
request.headers.add("if-modified-since", last_modified)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if !request.headers.key?("if-none-match") && (etag = cached_response.headers["etag"]) # rubocop:disable Style/GuardClause
|
|
50
|
+
request.headers.add("if-none-match", etag)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
#
|
|
6
|
+
# This plugin adds support for retrying requests when certain errors happen.
|
|
7
|
+
#
|
|
8
|
+
# https://gitlab.com/honeyryderchuck/httpx/wikis/Response-Cache
|
|
9
|
+
#
|
|
10
|
+
module ResponseCache
|
|
11
|
+
CACHEABLE_VERBS = %i[get head].freeze
|
|
12
|
+
private_constant :CACHEABLE_VERBS
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def load_dependencies(*)
|
|
16
|
+
require_relative "response_cache/store"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def cacheable_request?(request)
|
|
20
|
+
CACHEABLE_VERBS.include?(request.verb)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def cacheable_response?(response)
|
|
24
|
+
response.is_a?(Response) &&
|
|
25
|
+
# partial responses shall not be cached, only full ones.
|
|
26
|
+
response.status != 206 && (
|
|
27
|
+
response.headers.key?("etag") || response.headers.key?("last-modified-at")
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def cached_response?(response)
|
|
32
|
+
response.is_a?(Response) && response.status == 304
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def extra_options(options)
|
|
36
|
+
options.merge(response_cache_store: Store.new)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
module OptionsMethods
|
|
41
|
+
def option_response_cache_store(value)
|
|
42
|
+
raise TypeError, "must be an instance of #{Store}" unless value.is_a?(Store)
|
|
43
|
+
|
|
44
|
+
value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module InstanceMethods
|
|
49
|
+
def clear_response_cache
|
|
50
|
+
@options.response_cache_store.clear
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def build_request(*)
|
|
54
|
+
request = super
|
|
55
|
+
return request unless ResponseCache.cacheable_request?(request) && @options.response_cache_store.cached?(request.uri)
|
|
56
|
+
|
|
57
|
+
@options.response_cache_store.prepare(request)
|
|
58
|
+
|
|
59
|
+
request
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def fetch_response(request, *)
|
|
63
|
+
response = super
|
|
64
|
+
|
|
65
|
+
if response && ResponseCache.cached_response?(response)
|
|
66
|
+
log { "returning cached response for #{request.uri}" }
|
|
67
|
+
cached_response = @options.response_cache_store.lookup(request.uri)
|
|
68
|
+
|
|
69
|
+
response.copy_from_cached(cached_response)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
@options.response_cache_store.cache(request.uri, response) if response && ResponseCache.cacheable_response?(response)
|
|
73
|
+
|
|
74
|
+
response
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
module ResponseMethods
|
|
79
|
+
def copy_from_cached(other)
|
|
80
|
+
@body = other.body
|
|
81
|
+
|
|
82
|
+
@body.__send__(:rewind)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
register_plugin :response_cache, ResponseCache
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -17,39 +17,57 @@ module HTTPX
|
|
|
17
17
|
Errno::ECONNRESET,
|
|
18
18
|
Errno::ECONNABORTED,
|
|
19
19
|
Errno::EPIPE,
|
|
20
|
-
|
|
20
|
+
TLSError,
|
|
21
21
|
TimeoutError,
|
|
22
22
|
Parser::Error,
|
|
23
23
|
Errno::EINVAL,
|
|
24
24
|
Errno::ETIMEDOUT].freeze
|
|
25
|
+
DEFAULT_JITTER = ->(interval) { interval * (0.5 * (1 + rand)) }
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
if ENV.key?("HTTPX_NO_JITTER")
|
|
28
|
+
def self.extra_options(options)
|
|
29
|
+
options.merge(max_retries: MAX_RETRIES)
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
def self.extra_options(options)
|
|
33
|
+
options.merge(max_retries: MAX_RETRIES, retry_jitter: DEFAULT_JITTER)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module OptionsMethods
|
|
38
|
+
def option_retry_after(value)
|
|
39
|
+
# return early if callable
|
|
40
|
+
unless value.respond_to?(:call)
|
|
41
|
+
value = Integer(value)
|
|
42
|
+
raise TypeError, ":retry_after must be positive" unless value.positive?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
value
|
|
46
|
+
end
|
|
34
47
|
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
def option_retry_jitter(value)
|
|
49
|
+
# return early if callable
|
|
50
|
+
raise TypeError, ":retry_jitter must be callable" unless value.respond_to?(:call)
|
|
37
51
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
raise Error, ":max_retries must be positive" unless num.positive?
|
|
52
|
+
value
|
|
53
|
+
end
|
|
41
54
|
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
def option_max_retries(value)
|
|
56
|
+
num = Integer(value)
|
|
57
|
+
raise TypeError, ":max_retries must be positive" unless num.positive?
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
num
|
|
60
|
+
end
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
def option_retry_change_requests(v)
|
|
63
|
+
v
|
|
64
|
+
end
|
|
49
65
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
def option_retry_on(value)
|
|
67
|
+
raise ":retry_on must be called with the response" unless value.respond_to?(:call)
|
|
68
|
+
|
|
69
|
+
value
|
|
70
|
+
end
|
|
53
71
|
end
|
|
54
72
|
|
|
55
73
|
module InstanceMethods
|
|
@@ -83,10 +101,15 @@ module HTTPX
|
|
|
83
101
|
retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
|
|
84
102
|
|
|
85
103
|
if retry_after
|
|
104
|
+
# apply jitter
|
|
105
|
+
if (jitter = request.options.retry_jitter)
|
|
106
|
+
retry_after = jitter.call(retry_after)
|
|
107
|
+
end
|
|
86
108
|
|
|
109
|
+
retry_start = Utils.now
|
|
87
110
|
log { "retrying after #{retry_after} secs..." }
|
|
88
111
|
pool.after(retry_after) do
|
|
89
|
-
log { "retrying!!" }
|
|
112
|
+
log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
|
|
90
113
|
connection = find_connection(request, connections, options)
|
|
91
114
|
connection.send(request)
|
|
92
115
|
end
|
data/lib/httpx/plugins/stream.rb
CHANGED
|
@@ -6,11 +6,10 @@ module HTTPX
|
|
|
6
6
|
@request = request
|
|
7
7
|
@session = session
|
|
8
8
|
@connections = connections
|
|
9
|
-
@options = @request.options
|
|
10
9
|
end
|
|
11
10
|
|
|
12
11
|
def each(&block)
|
|
13
|
-
return enum_for(__method__) unless
|
|
12
|
+
return enum_for(__method__) unless block
|
|
14
13
|
|
|
15
14
|
raise Error, "response already streamed" if @response
|
|
16
15
|
|
|
@@ -72,7 +71,7 @@ module HTTPX
|
|
|
72
71
|
private
|
|
73
72
|
|
|
74
73
|
def response
|
|
75
|
-
@session.__send__(:receive_requests, [@request], @connections
|
|
74
|
+
@session.__send__(:receive_requests, [@request], @connections) until @request.response
|
|
76
75
|
|
|
77
76
|
@request.response
|
|
78
77
|
end
|
|
@@ -106,7 +105,7 @@ module HTTPX
|
|
|
106
105
|
|
|
107
106
|
request = requests.first
|
|
108
107
|
|
|
109
|
-
connections = _send_requests(requests
|
|
108
|
+
connections = _send_requests(requests)
|
|
110
109
|
|
|
111
110
|
StreamResponse.new(request, self, connections)
|
|
112
111
|
end
|
|
@@ -18,14 +18,15 @@ module HTTPX
|
|
|
18
18
|
upgrade_handlers = Module.new do
|
|
19
19
|
extend Registry
|
|
20
20
|
end
|
|
21
|
+
options.merge(upgrade_handlers: upgrade_handlers)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
module OptionsMethods
|
|
26
|
+
def option_upgrade_handlers(value)
|
|
27
|
+
raise TypeError, ":upgrade_handlers must be a registry" unless value.respond_to?(:registry)
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
OUT
|
|
28
|
-
end.new(options).merge(upgrade_handlers: upgrade_handlers)
|
|
29
|
+
value
|
|
29
30
|
end
|
|
30
31
|
end
|
|
31
32
|
|
data/lib/httpx/pool.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "forwardable"
|
|
4
|
-
require "timers"
|
|
5
4
|
require "httpx/selector"
|
|
6
5
|
require "httpx/connection"
|
|
7
6
|
require "httpx/resolver"
|
|
8
7
|
|
|
9
8
|
module HTTPX
|
|
10
9
|
class Pool
|
|
10
|
+
using ArrayExtensions
|
|
11
11
|
extend Forwardable
|
|
12
12
|
|
|
13
13
|
def_delegator :@timers, :after
|
|
@@ -15,7 +15,7 @@ module HTTPX
|
|
|
15
15
|
def initialize
|
|
16
16
|
@resolvers = {}
|
|
17
17
|
@_resolver_ios = {}
|
|
18
|
-
@timers = Timers
|
|
18
|
+
@timers = Timers.new
|
|
19
19
|
@selector = Selector.new
|
|
20
20
|
@connections = []
|
|
21
21
|
@connected_connections = 0
|
|
@@ -27,15 +27,18 @@ module HTTPX
|
|
|
27
27
|
|
|
28
28
|
def next_tick
|
|
29
29
|
catch(:jump_tick) do
|
|
30
|
-
timeout =
|
|
30
|
+
timeout = next_timeout
|
|
31
31
|
if timeout && timeout.negative?
|
|
32
32
|
@timers.fire
|
|
33
33
|
throw(:jump_tick)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
begin
|
|
37
|
+
@selector.select(timeout, &:call)
|
|
38
|
+
@timers.fire
|
|
39
|
+
rescue TimeoutError => e
|
|
40
|
+
@timers.fire(e)
|
|
41
|
+
end
|
|
39
42
|
end
|
|
40
43
|
rescue StandardError => e
|
|
41
44
|
@connections.each do |connection|
|
|
@@ -64,6 +67,16 @@ module HTTPX
|
|
|
64
67
|
connection.on(:open) do
|
|
65
68
|
@connected_connections += 1
|
|
66
69
|
end
|
|
70
|
+
connection.on(:activate) do
|
|
71
|
+
select_connection(connection)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def deactivate(connections)
|
|
76
|
+
connections.each do |connection|
|
|
77
|
+
connection.deactivate
|
|
78
|
+
deselect_connection(connection) if connection.state == :inactive
|
|
79
|
+
end
|
|
67
80
|
end
|
|
68
81
|
|
|
69
82
|
# opens a connection to the IP reachable through +uri+.
|
|
@@ -81,7 +94,7 @@ module HTTPX
|
|
|
81
94
|
def resolve_connection(connection)
|
|
82
95
|
@connections << connection unless @connections.include?(connection)
|
|
83
96
|
|
|
84
|
-
if connection.addresses || connection.
|
|
97
|
+
if connection.addresses || connection.open?
|
|
85
98
|
#
|
|
86
99
|
# there are two cases in which we want to activate initialization of
|
|
87
100
|
# connection immediately:
|
|
@@ -98,7 +111,7 @@ module HTTPX
|
|
|
98
111
|
resolver << connection
|
|
99
112
|
return if resolver.empty?
|
|
100
113
|
|
|
101
|
-
@_resolver_ios[resolver] ||=
|
|
114
|
+
@_resolver_ios[resolver] ||= select_connection(resolver)
|
|
102
115
|
end
|
|
103
116
|
|
|
104
117
|
def on_resolver_connection(connection)
|
|
@@ -107,7 +120,7 @@ module HTTPX
|
|
|
107
120
|
end
|
|
108
121
|
return register_connection(connection) unless found_connection
|
|
109
122
|
|
|
110
|
-
if found_connection.
|
|
123
|
+
if found_connection.open?
|
|
111
124
|
coalesce_connections(found_connection, connection)
|
|
112
125
|
throw(:coalesced, found_connection)
|
|
113
126
|
else
|
|
@@ -129,7 +142,7 @@ module HTTPX
|
|
|
129
142
|
|
|
130
143
|
@resolvers.delete(resolver_type)
|
|
131
144
|
|
|
132
|
-
|
|
145
|
+
deselect_connection(resolver)
|
|
133
146
|
@_resolver_ios.delete(resolver)
|
|
134
147
|
resolver.close unless resolver.closed?
|
|
135
148
|
end
|
|
@@ -140,7 +153,7 @@ module HTTPX
|
|
|
140
153
|
# consider it connected already.
|
|
141
154
|
@connected_connections += 1
|
|
142
155
|
end
|
|
143
|
-
|
|
156
|
+
select_connection(connection)
|
|
144
157
|
connection.on(:close) do
|
|
145
158
|
unregister_connection(connection)
|
|
146
159
|
end
|
|
@@ -148,10 +161,18 @@ module HTTPX
|
|
|
148
161
|
|
|
149
162
|
def unregister_connection(connection)
|
|
150
163
|
@connections.delete(connection)
|
|
151
|
-
|
|
164
|
+
deselect_connection(connection)
|
|
152
165
|
@connected_connections -= 1
|
|
153
166
|
end
|
|
154
167
|
|
|
168
|
+
def select_connection(connection)
|
|
169
|
+
@selector.register(connection)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def deselect_connection(connection)
|
|
173
|
+
@selector.deregister(connection)
|
|
174
|
+
end
|
|
175
|
+
|
|
155
176
|
def coalesce_connections(conn1, conn2)
|
|
156
177
|
if conn1.coalescable?(conn2)
|
|
157
178
|
conn1.merge(conn2)
|
|
@@ -162,7 +183,11 @@ module HTTPX
|
|
|
162
183
|
end
|
|
163
184
|
|
|
164
185
|
def next_timeout
|
|
165
|
-
|
|
186
|
+
[
|
|
187
|
+
@timers.wait_interval,
|
|
188
|
+
*@resolvers.values.reject(&:closed?).filter_map(&:timeout),
|
|
189
|
+
*@connections.filter_map(&:timeout),
|
|
190
|
+
].compact.min
|
|
166
191
|
end
|
|
167
192
|
|
|
168
193
|
def find_resolver_for(connection)
|
|
@@ -172,6 +197,7 @@ module HTTPX
|
|
|
172
197
|
|
|
173
198
|
@resolvers[resolver_type] ||= begin
|
|
174
199
|
resolver = resolver_type.new(connection_options)
|
|
200
|
+
resolver.pool = self if resolver.respond_to?(:pool=)
|
|
175
201
|
resolver.on(:resolve, &method(:on_resolver_connection))
|
|
176
202
|
resolver.on(:error, &method(:on_resolver_error))
|
|
177
203
|
resolver.on(:close) { on_resolver_close(resolver) }
|
data/lib/httpx/registry.rb
CHANGED
|
@@ -31,7 +31,7 @@ module HTTPX
|
|
|
31
31
|
#
|
|
32
32
|
module Registry
|
|
33
33
|
# Base Registry Error
|
|
34
|
-
Error
|
|
34
|
+
class Error < Error; end
|
|
35
35
|
|
|
36
36
|
def self.extended(klass)
|
|
37
37
|
super
|
|
@@ -59,7 +59,7 @@ module HTTPX
|
|
|
59
59
|
@registry ||= {}
|
|
60
60
|
return @registry if tag.nil?
|
|
61
61
|
|
|
62
|
-
handler = @registry
|
|
62
|
+
handler = @registry[tag]
|
|
63
63
|
raise(Error, "#{tag} is not registered in #{self}") unless handler
|
|
64
64
|
|
|
65
65
|
handler
|