httpx 0.24.6 → 1.0.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/LICENSE.txt +0 -48
- data/README.md +4 -13
- data/doc/release_notes/0_24_4.md +3 -3
- data/doc/release_notes/1_0_0.md +60 -0
- data/lib/httpx/adapters/datadog.rb +28 -97
- data/lib/httpx/adapters/faraday.rb +9 -52
- data/lib/httpx/adapters/webmock.rb +2 -7
- data/lib/httpx/altsvc.rb +4 -22
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/chainable.rb +0 -23
- data/lib/httpx/connection.rb +11 -32
- data/lib/httpx/domain_name.rb +5 -12
- data/lib/httpx/errors.rb +0 -2
- data/lib/httpx/extensions.rb +0 -124
- data/lib/httpx/io/ssl.rb +26 -59
- data/lib/httpx/io/tcp.rb +27 -68
- data/lib/httpx/io/udp.rb +13 -48
- data/lib/httpx/io/unix.rb +1 -8
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +24 -84
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +2 -5
- data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
- data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
- data/lib/httpx/plugins/auth.rb +25 -0
- data/lib/httpx/plugins/aws_sigv4.rb +0 -1
- data/lib/httpx/plugins/{basic_authentication.rb → basic_auth.rb} +5 -6
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +16 -5
- data/lib/httpx/plugins/circuit_breaker.rb +11 -4
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +1 -1
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +5 -5
- data/lib/httpx/plugins/follow_redirects.rb +21 -24
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +82 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -39
- data/lib/httpx/plugins/grpc.rb +15 -21
- data/lib/httpx/plugins/h2c.rb +0 -1
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +5 -5
- data/lib/httpx/plugins/oauth.rb +2 -2
- data/lib/httpx/plugins/proxy/http.rb +0 -2
- data/lib/httpx/plugins/proxy/socks4.rb +0 -4
- data/lib/httpx/plugins/proxy/socks5.rb +1 -5
- data/lib/httpx/plugins/proxy.rb +3 -32
- data/lib/httpx/plugins/retries.rb +3 -4
- data/lib/httpx/plugins/stream.rb +4 -6
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +145 -0
- data/lib/httpx/request.rb +2 -119
- data/lib/httpx/resolver/https.rb +1 -1
- data/lib/httpx/resolver/native.rb +6 -14
- data/lib/httpx/resolver/resolver.rb +1 -1
- data/lib/httpx/resolver/system.rb +11 -9
- data/lib/httpx/response/body.rb +206 -0
- data/lib/httpx/response/buffer.rb +90 -0
- data/lib/httpx/response.rb +5 -208
- data/lib/httpx/selector.rb +0 -2
- data/lib/httpx/session.rb +0 -10
- data/lib/httpx/transcoder/body.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -32
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +2 -4
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
- data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
- data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
- data/lib/httpx/transcoder/multipart.rb +17 -0
- data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
- data/lib/httpx/transcoder/utils/deflater.rb +72 -0
- data/lib/httpx/transcoder/xml.rb +0 -2
- data/lib/httpx/transcoder.rb +2 -2
- data/lib/httpx/utils.rb +10 -20
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +0 -8
- data/sig/chainable.rbs +5 -6
- data/sig/connection.rbs +0 -1
- data/sig/errors.rbs +0 -3
- data/sig/httpx.rbs +2 -1
- data/sig/io/unix.rbs +1 -1
- data/sig/options.rbs +12 -8
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/auth.rbs +13 -0
- data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
- data/sig/plugins/brotli.rbs +22 -0
- data/sig/plugins/circuit_breaker.rbs +7 -3
- data/sig/plugins/compression.rbs +0 -2
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +0 -1
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +33 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +2 -32
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +1 -1
- data/sig/plugins/proxy/socks4.rbs +2 -3
- data/sig/plugins/proxy/socks5.rbs +0 -1
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/response_cache.rbs +5 -2
- data/sig/request/body.rbs +42 -0
- data/sig/request.rbs +1 -27
- data/sig/resolver/resolver.rbs +1 -1
- data/sig/response/body.rbs +52 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +0 -39
- data/sig/transcoder/body.rbs +4 -3
- data/sig/transcoder/deflate.rbs +11 -0
- data/sig/transcoder/form.rbs +5 -3
- data/sig/transcoder/gzip.rbs +24 -0
- data/sig/transcoder/json.rbs +4 -2
- data/sig/{plugins → transcoder}/multipart.rbs +3 -10
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder.rbs +18 -2
- metadata +50 -34
- data/lib/httpx/plugins/authentication.rb +0 -24
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -54
- data/lib/httpx/plugins/compression/gzip.rb +0 -90
- data/lib/httpx/plugins/compression.rb +0 -165
- data/lib/httpx/plugins/multipart/decoder.rb +0 -137
- data/lib/httpx/plugins/multipart.rb +0 -96
- data/sig/plugins/authentication.rbs +0 -13
- data/sig/plugins/compression/brotli.rbs +0 -21
- data/sig/plugins/compression/deflate.rbs +0 -17
- data/sig/plugins/compression/gzip.rbs +0 -29
- /data/sig/plugins/{authentication → auth}/digest.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/options.rb
CHANGED
|
@@ -7,11 +7,10 @@ module HTTPX
|
|
|
7
7
|
BUFFER_SIZE = 1 << 14
|
|
8
8
|
WINDOW_SIZE = 1 << 14 # 16K
|
|
9
9
|
MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
|
|
10
|
-
CONNECT_TIMEOUT = 60
|
|
11
|
-
OPERATION_TIMEOUT = 60
|
|
12
10
|
KEEP_ALIVE_TIMEOUT = 20
|
|
13
11
|
SETTINGS_TIMEOUT = 10
|
|
14
|
-
|
|
12
|
+
CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
|
|
13
|
+
REQUEST_TIMEOUT = OPERATION_TIMEOUT = Float::INFINITY
|
|
15
14
|
|
|
16
15
|
# https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
|
|
17
16
|
ip_address_families = begin
|
|
@@ -31,6 +30,9 @@ module HTTPX
|
|
|
31
30
|
:ssl => {},
|
|
32
31
|
:http2_settings => { settings_enable_push: 0 },
|
|
33
32
|
:fallback_protocol => "http/1.1",
|
|
33
|
+
:supported_compression_formats => %w[gzip deflate],
|
|
34
|
+
:decompress_response_body => true,
|
|
35
|
+
:compress_request_body => true,
|
|
34
36
|
:timeout => {
|
|
35
37
|
connect_timeout: CONNECT_TIMEOUT,
|
|
36
38
|
settings_timeout: SETTINGS_TIMEOUT,
|
|
@@ -52,7 +54,6 @@ module HTTPX
|
|
|
52
54
|
:connection_class => Class.new(Connection),
|
|
53
55
|
:options_class => Class.new(self),
|
|
54
56
|
:transport => nil,
|
|
55
|
-
:transport_options => nil,
|
|
56
57
|
:addresses => nil,
|
|
57
58
|
:persistent => false,
|
|
58
59
|
:resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
|
|
@@ -60,28 +61,6 @@ module HTTPX
|
|
|
60
61
|
:ip_families => ip_address_families,
|
|
61
62
|
}.freeze
|
|
62
63
|
|
|
63
|
-
begin
|
|
64
|
-
module HashExtensions
|
|
65
|
-
refine Hash do
|
|
66
|
-
def >=(other)
|
|
67
|
-
Hash[other] <= self
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def <=(other)
|
|
71
|
-
other = Hash[other]
|
|
72
|
-
return false unless size <= other.size
|
|
73
|
-
|
|
74
|
-
each do |k, v|
|
|
75
|
-
v2 = other.fetch(k) { return false }
|
|
76
|
-
return false unless v2 == v
|
|
77
|
-
end
|
|
78
|
-
true
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
using HashExtensions
|
|
83
|
-
end unless Hash.method_defined?(:>=)
|
|
84
|
-
|
|
85
64
|
class << self
|
|
86
65
|
def new(options = {})
|
|
87
66
|
# let enhanced options go through
|
|
@@ -100,38 +79,10 @@ module HTTPX
|
|
|
100
79
|
|
|
101
80
|
attr_reader(optname)
|
|
102
81
|
end
|
|
103
|
-
|
|
104
|
-
def def_option(optname, *args, &block)
|
|
105
|
-
if args.empty? && !block
|
|
106
|
-
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
107
|
-
def option_#{optname}(v); v; end # def option_smth(v); v; end
|
|
108
|
-
OUT
|
|
109
|
-
return
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
deprecated_def_option(optname, *args, &block)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def deprecated_def_option(optname, layout = nil, &interpreter)
|
|
116
|
-
warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
|
|
117
|
-
"Define module OptionsMethods and `def option_#{optname}(val)` instead."
|
|
118
|
-
|
|
119
|
-
if layout
|
|
120
|
-
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
121
|
-
def option_#{optname}(value) # def option_origin(v)
|
|
122
|
-
#{layout} # URI(v)
|
|
123
|
-
end # end
|
|
124
|
-
OUT
|
|
125
|
-
elsif interpreter
|
|
126
|
-
define_method(:"option_#{optname}") do |value|
|
|
127
|
-
instance_exec(value, &interpreter)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
82
|
end
|
|
132
83
|
|
|
133
84
|
def initialize(options = {})
|
|
134
|
-
|
|
85
|
+
do_initialize(options)
|
|
135
86
|
freeze
|
|
136
87
|
end
|
|
137
88
|
|
|
@@ -142,6 +93,7 @@ module HTTPX
|
|
|
142
93
|
@timeout.freeze
|
|
143
94
|
@headers.freeze
|
|
144
95
|
@addresses.freeze
|
|
96
|
+
@supported_compression_formats.freeze
|
|
145
97
|
end
|
|
146
98
|
|
|
147
99
|
def option_origin(value)
|
|
@@ -157,14 +109,11 @@ module HTTPX
|
|
|
157
109
|
end
|
|
158
110
|
|
|
159
111
|
def option_timeout(value)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if timeouts.key?(:loop_timeout)
|
|
163
|
-
warn ":loop_timeout is deprecated, use :operation_timeout instead"
|
|
164
|
-
timeouts[:operation_timeout] = timeouts.delete(:loop_timeout)
|
|
165
|
-
end
|
|
112
|
+
Hash[value]
|
|
113
|
+
end
|
|
166
114
|
|
|
167
|
-
|
|
115
|
+
def option_supported_compression_formats(value)
|
|
116
|
+
Array(value).map(&:to_s)
|
|
168
117
|
end
|
|
169
118
|
|
|
170
119
|
def option_max_concurrent_requests(value)
|
|
@@ -196,7 +145,10 @@ module HTTPX
|
|
|
196
145
|
end
|
|
197
146
|
|
|
198
147
|
def option_body_threshold_size(value)
|
|
199
|
-
Integer(value)
|
|
148
|
+
bytes = Integer(value)
|
|
149
|
+
raise TypeError, ":body_threshold_size must be positive" unless bytes.positive?
|
|
150
|
+
|
|
151
|
+
bytes
|
|
200
152
|
end
|
|
201
153
|
|
|
202
154
|
def option_transport(value)
|
|
@@ -218,10 +170,13 @@ module HTTPX
|
|
|
218
170
|
params form json xml body ssl http2_settings
|
|
219
171
|
request_class response_class headers_class request_body_class
|
|
220
172
|
response_body_class connection_class options_class
|
|
221
|
-
io fallback_protocol debug debug_level
|
|
173
|
+
io fallback_protocol debug debug_level resolver_class resolver_options
|
|
174
|
+
compress_request_body decompress_response_body
|
|
222
175
|
persistent
|
|
223
176
|
].each do |method_name|
|
|
224
|
-
|
|
177
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
178
|
+
def option_#{method_name}(v); v; end # def option_smth(v); v; end
|
|
179
|
+
OUT
|
|
225
180
|
end
|
|
226
181
|
|
|
227
182
|
REQUEST_IVARS = %i[@params @form @xml @json @body].freeze
|
|
@@ -270,30 +225,15 @@ module HTTPX
|
|
|
270
225
|
end
|
|
271
226
|
end
|
|
272
227
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
else
|
|
280
|
-
def initialize_dup(other)
|
|
281
|
-
instance_variables.each do |ivar|
|
|
282
|
-
value = other.instance_variable_get(ivar)
|
|
283
|
-
value = case value
|
|
284
|
-
when Symbol, Numeric, TrueClass, FalseClass
|
|
285
|
-
value
|
|
286
|
-
else
|
|
287
|
-
value.dup
|
|
288
|
-
end
|
|
289
|
-
instance_variable_set(ivar, value)
|
|
290
|
-
end
|
|
228
|
+
def initialize_dup(other)
|
|
229
|
+
instance_variables.each do |ivar|
|
|
230
|
+
instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
|
|
291
231
|
end
|
|
292
232
|
end
|
|
293
233
|
|
|
294
234
|
private
|
|
295
235
|
|
|
296
|
-
def
|
|
236
|
+
def do_initialize(options = {})
|
|
297
237
|
defaults = DEFAULT_OPTIONS.merge(options)
|
|
298
238
|
defaults.each do |k, v|
|
|
299
239
|
next if v.nil?
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "base64"
|
|
3
|
+
require "httpx/base64"
|
|
4
4
|
|
|
5
5
|
module HTTPX
|
|
6
6
|
module Plugins
|
|
@@ -11,10 +11,6 @@ module HTTPX
|
|
|
11
11
|
@password = password
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def can_authenticate?(authenticate)
|
|
15
|
-
authenticate && /Basic .*/.match?(authenticate)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
14
|
def authenticate(*)
|
|
19
15
|
"Basic #{Base64.strict_encode64("#{@user}:#{@password}")}"
|
|
20
16
|
end
|
|
@@ -8,8 +8,6 @@ module HTTPX
|
|
|
8
8
|
module Plugins
|
|
9
9
|
module Authentication
|
|
10
10
|
class Digest
|
|
11
|
-
using RegexpExtensions unless Regexp.method_defined?(:match?)
|
|
12
|
-
|
|
13
11
|
def initialize(user, password, hashed: false, **)
|
|
14
12
|
@user = user
|
|
15
13
|
@password = password
|
|
@@ -31,9 +29,8 @@ module HTTPX
|
|
|
31
29
|
# discard first token, it's Digest
|
|
32
30
|
auth_info = authenticate[/^(\w+) (.*)/, 2]
|
|
33
31
|
|
|
34
|
-
params =
|
|
35
|
-
|
|
36
|
-
.map { |k, v| [k, v.delete("\"")] }]
|
|
32
|
+
params = auth_info.split(/ *, */)
|
|
33
|
+
.to_h { |val| val.split("=") }.transform_values { |v| v.delete("\"") }
|
|
37
34
|
nonce = params["nonce"]
|
|
38
35
|
nc = next_nonce
|
|
39
36
|
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "base64"
|
|
3
|
+
require "httpx/base64"
|
|
4
4
|
require "ntlm"
|
|
5
5
|
|
|
6
6
|
module HTTPX
|
|
7
7
|
module Plugins
|
|
8
8
|
module Authentication
|
|
9
9
|
class Ntlm
|
|
10
|
-
using RegexpExtensions unless Regexp.method_defined?(:match?)
|
|
11
|
-
|
|
12
10
|
def initialize(user, password, domain: nil)
|
|
13
11
|
@user = user
|
|
14
12
|
@password = password
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
#
|
|
6
|
+
# This plugin adds a shim +authorization+ method to the session, which will fill
|
|
7
|
+
# the HTTP Authorization header, and another, +bearer_auth+, which fill the "Bearer " prefix
|
|
8
|
+
# in its value.
|
|
9
|
+
#
|
|
10
|
+
# https://gitlab.com/os85/httpx/wikis/Auth#authorization
|
|
11
|
+
#
|
|
12
|
+
module Auth
|
|
13
|
+
module InstanceMethods
|
|
14
|
+
def authorization(token)
|
|
15
|
+
with(headers: { "authorization" => token })
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def bearer_auth(token)
|
|
19
|
+
authorization("Bearer #{token}")
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
register_plugin :auth, Auth
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -5,26 +5,25 @@ module HTTPX
|
|
|
5
5
|
#
|
|
6
6
|
# This plugin adds helper methods to implement HTTP Basic Auth (https://tools.ietf.org/html/rfc7617)
|
|
7
7
|
#
|
|
8
|
-
# https://gitlab.com/os85/httpx/wikis/
|
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Authorization#basic-auth
|
|
9
9
|
#
|
|
10
10
|
module BasicAuth
|
|
11
11
|
class << self
|
|
12
12
|
def load_dependencies(_klass)
|
|
13
|
-
require_relative "
|
|
13
|
+
require_relative "auth/basic"
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def configure(klass)
|
|
17
|
-
klass.plugin(:
|
|
17
|
+
klass.plugin(:auth)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
module InstanceMethods
|
|
22
22
|
def basic_auth(user, password)
|
|
23
|
-
|
|
23
|
+
authorization(Authentication::Basic.new(user, password).authenticate)
|
|
24
24
|
end
|
|
25
|
-
alias_method :basic_authentication, :basic_auth
|
|
26
25
|
end
|
|
27
26
|
end
|
|
28
|
-
register_plugin :
|
|
27
|
+
register_plugin :basic_auth, BasicAuth
|
|
29
28
|
end
|
|
30
29
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
module Brotli
|
|
6
|
+
class Deflater < Transcoder::Deflater
|
|
7
|
+
def deflate(chunk)
|
|
8
|
+
return unless chunk
|
|
9
|
+
|
|
10
|
+
::Brotli.deflate(chunk)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module RequestBodyClassMethods
|
|
15
|
+
def initialize_deflater_body(body, encoding)
|
|
16
|
+
return Brotli.encode(body) if encoding == "br"
|
|
17
|
+
|
|
18
|
+
super
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
module ResponseBodyClassMethods
|
|
23
|
+
def initialize_inflater_by_encoding(encoding, response, **kwargs)
|
|
24
|
+
return Brotli.decode(response, **kwargs) if encoding == "br"
|
|
25
|
+
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
def load_dependencies(*)
|
|
33
|
+
require "brotli"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.extra_options(options)
|
|
37
|
+
options.merge(supported_compression_formats: %w[br] + options.supported_compression_formats)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def encode(body)
|
|
41
|
+
Deflater.new(body)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def decode(_response, **)
|
|
45
|
+
::Brotli.method(:inflate)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
register_plugin :brotli, Brotli
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -15,8 +15,11 @@ module HTTPX
|
|
|
15
15
|
@max_attempts = max_attempts
|
|
16
16
|
@reset_attempts_in = reset_attempts_in
|
|
17
17
|
@break_in = break_in
|
|
18
|
-
@circuit_breaker_half_open_drip_rate =
|
|
18
|
+
@circuit_breaker_half_open_drip_rate = circuit_breaker_half_open_drip_rate
|
|
19
19
|
@attempts = 0
|
|
20
|
+
|
|
21
|
+
total_real_attempts = @max_attempts * @circuit_breaker_half_open_drip_rate
|
|
22
|
+
@drip_factor = (@max_attempts / total_real_attempts).round
|
|
20
23
|
@state = :closed
|
|
21
24
|
end
|
|
22
25
|
|
|
@@ -27,8 +30,13 @@ module HTTPX
|
|
|
27
30
|
when :closed
|
|
28
31
|
nil
|
|
29
32
|
when :half_open
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
@attempts += 1
|
|
34
|
+
|
|
35
|
+
# do real requests while drip rate valid
|
|
36
|
+
if (@real_attempts % @drip_factor).zero?
|
|
37
|
+
@real_attempts += 1
|
|
38
|
+
return
|
|
39
|
+
end
|
|
32
40
|
|
|
33
41
|
@response
|
|
34
42
|
when :open
|
|
@@ -38,23 +46,31 @@ module HTTPX
|
|
|
38
46
|
end
|
|
39
47
|
|
|
40
48
|
def try_open(response)
|
|
41
|
-
|
|
49
|
+
case @state
|
|
50
|
+
when :closed
|
|
51
|
+
now = Utils.now
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
if @attempts.positive?
|
|
54
|
+
# reset if error happened long ago
|
|
55
|
+
@attempts = 0 if now - @attempted_at > @reset_attempts_in
|
|
56
|
+
else
|
|
57
|
+
@attempted_at = now
|
|
58
|
+
end
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
@attempts = 0 if now - @attempted_at > @reset_attempts_in
|
|
47
|
-
else
|
|
48
|
-
@attempted_at = now
|
|
49
|
-
end
|
|
60
|
+
@attempts += 1
|
|
50
61
|
|
|
51
|
-
|
|
62
|
+
return unless @attempts >= @max_attempts
|
|
52
63
|
|
|
53
|
-
|
|
64
|
+
@state = :open
|
|
65
|
+
@opened_at = now
|
|
66
|
+
@response = response
|
|
67
|
+
when :half_open
|
|
68
|
+
# open immediately
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
70
|
+
@state = :open
|
|
71
|
+
@attempted_at = @opened_at = Utils.now
|
|
72
|
+
@response = response
|
|
73
|
+
end
|
|
58
74
|
end
|
|
59
75
|
|
|
60
76
|
def try_close
|
|
@@ -62,13 +78,21 @@ module HTTPX
|
|
|
62
78
|
when :closed
|
|
63
79
|
nil
|
|
64
80
|
when :half_open
|
|
81
|
+
|
|
82
|
+
# do not close circuit unless attempts exhausted
|
|
83
|
+
return unless @attempts >= @max_attempts
|
|
84
|
+
|
|
65
85
|
# reset!
|
|
66
86
|
@attempts = 0
|
|
67
87
|
@opened_at = @attempted_at = @response = nil
|
|
68
88
|
@state = :closed
|
|
69
89
|
|
|
70
90
|
when :open
|
|
71
|
-
|
|
91
|
+
if Utils.elapsed_time(@opened_at) > @break_in
|
|
92
|
+
@state = :half_open
|
|
93
|
+
@attempts = 0
|
|
94
|
+
@real_attempts = 0
|
|
95
|
+
end
|
|
72
96
|
end
|
|
73
97
|
end
|
|
74
98
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "mutex_m"
|
|
4
|
+
|
|
3
5
|
module HTTPX::Plugins::CircuitBreaker
|
|
4
6
|
using HTTPX::URIExtensions
|
|
5
7
|
|
|
@@ -13,18 +15,29 @@ module HTTPX::Plugins::CircuitBreaker
|
|
|
13
15
|
options.circuit_breaker_half_open_drip_rate
|
|
14
16
|
)
|
|
15
17
|
end
|
|
18
|
+
@circuits.extend(Mutex_m)
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def try_open(uri, response)
|
|
19
|
-
circuit = get_circuit_for_uri(uri)
|
|
22
|
+
circuit = @circuits.synchronize { get_circuit_for_uri(uri) }
|
|
20
23
|
|
|
21
24
|
circuit.try_open(response)
|
|
22
25
|
end
|
|
23
26
|
|
|
27
|
+
def try_close(uri)
|
|
28
|
+
circuit = @circuits.synchronize do
|
|
29
|
+
return unless @circuits.key?(uri.origin) || @circuits.key?(uri.to_s)
|
|
30
|
+
|
|
31
|
+
get_circuit_for_uri(uri)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
circuit.try_close
|
|
35
|
+
end
|
|
36
|
+
|
|
24
37
|
# if circuit is open, it'll respond with the stored response.
|
|
25
38
|
# if not, nil.
|
|
26
39
|
def try_respond(request)
|
|
27
|
-
circuit = get_circuit_for_uri(request.uri)
|
|
40
|
+
circuit = @circuits.synchronize { get_circuit_for_uri(request.uri) }
|
|
28
41
|
|
|
29
42
|
circuit.respond
|
|
30
43
|
end
|
|
@@ -32,9 +45,7 @@ module HTTPX::Plugins::CircuitBreaker
|
|
|
32
45
|
private
|
|
33
46
|
|
|
34
47
|
def get_circuit_for_uri(uri)
|
|
35
|
-
uri
|
|
36
|
-
|
|
37
|
-
if @circuits.key?(uri.origin)
|
|
48
|
+
if uri.respond_to?(:origin) && @circuits.key?(uri.origin)
|
|
38
49
|
@circuits[uri.origin]
|
|
39
50
|
else
|
|
40
51
|
@circuits[uri.to_s]
|
|
@@ -16,8 +16,12 @@ module HTTPX
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def self.extra_options(options)
|
|
19
|
-
options.merge(
|
|
20
|
-
|
|
19
|
+
options.merge(
|
|
20
|
+
circuit_breaker_max_attempts: 3,
|
|
21
|
+
circuit_breaker_reset_attempts_in: 60,
|
|
22
|
+
circuit_breaker_break_in: 60,
|
|
23
|
+
circuit_breaker_half_open_drip_rate: 1
|
|
24
|
+
)
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
module InstanceMethods
|
|
@@ -47,13 +51,13 @@ module HTTPX
|
|
|
47
51
|
|
|
48
52
|
# run all requests through the circuit breaker, see if the circuit is
|
|
49
53
|
# open for any of them.
|
|
50
|
-
real_requests = requests.
|
|
54
|
+
real_requests = requests.each_with_index.with_object([]) do |(req, idx), real_reqs|
|
|
51
55
|
short_circuit_response = @circuit_store.try_respond(req)
|
|
52
56
|
if short_circuit_response.nil?
|
|
53
57
|
real_reqs << req
|
|
54
58
|
next
|
|
55
59
|
end
|
|
56
|
-
short_circuit_responses[
|
|
60
|
+
short_circuit_responses[idx] = short_circuit_response
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
# run requests for the remainder
|
|
@@ -84,6 +88,9 @@ module HTTPX
|
|
|
84
88
|
end
|
|
85
89
|
elsif (break_on = request.options.circuit_breaker_break_on) && break_on.call(response)
|
|
86
90
|
@circuit_store.try_open(request.uri, response)
|
|
91
|
+
else
|
|
92
|
+
@circuit_store.try_close(request.uri)
|
|
93
|
+
nil
|
|
87
94
|
end
|
|
88
95
|
end
|
|
89
96
|
end
|
|
@@ -5,7 +5,7 @@ module HTTPX
|
|
|
5
5
|
#
|
|
6
6
|
# This plugin adds helper methods to implement HTTP Digest Auth (https://tools.ietf.org/html/rfc7616)
|
|
7
7
|
#
|
|
8
|
-
# https://gitlab.com/os85/httpx/wikis/
|
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Authorization#digest-auth
|
|
9
9
|
#
|
|
10
10
|
module DigestAuth
|
|
11
11
|
DigestError = Class.new(Error)
|
|
@@ -16,7 +16,7 @@ module HTTPX
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def load_dependencies(*)
|
|
19
|
-
require_relative "
|
|
19
|
+
require_relative "auth/digest"
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -29,11 +29,11 @@ module HTTPX
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
module InstanceMethods
|
|
32
|
-
def
|
|
32
|
+
def digest_auth(user, password, hashed: false)
|
|
33
33
|
with(digest: Authentication::Digest.new(user, password, hashed: hashed))
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
private
|
|
37
37
|
|
|
38
38
|
def send_requests(*requests)
|
|
39
39
|
requests.flat_map do |request|
|
|
@@ -57,6 +57,6 @@ module HTTPX
|
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
register_plugin :
|
|
60
|
+
register_plugin :digest_auth, DigestAuth
|
|
61
61
|
end
|
|
62
62
|
end
|
|
@@ -48,7 +48,27 @@ module HTTPX
|
|
|
48
48
|
return response unless REDIRECT_STATUS.include?(response.status) && response.headers.key?("location")
|
|
49
49
|
return response unless max_redirects.positive?
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
# build redirect request
|
|
52
|
+
redirect_uri = __get_location_from_response(response)
|
|
53
|
+
|
|
54
|
+
if response.status == 305 && options.respond_to?(:proxy)
|
|
55
|
+
# The requested resource MUST be accessed through the proxy given by
|
|
56
|
+
# the Location field. The Location field gives the URI of the proxy.
|
|
57
|
+
retry_options = options.merge(headers: redirect_request.headers,
|
|
58
|
+
proxy: { uri: redirect_uri },
|
|
59
|
+
body: redirect_request.body,
|
|
60
|
+
max_redirects: max_redirects - 1)
|
|
61
|
+
redirect_uri = redirect_request.uri
|
|
62
|
+
options = retry_options
|
|
63
|
+
else
|
|
64
|
+
|
|
65
|
+
# redirects are **ALWAYS** GET
|
|
66
|
+
retry_options = options.merge(headers: redirect_request.headers,
|
|
67
|
+
body: redirect_request.body,
|
|
68
|
+
max_redirects: max_redirects - 1)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
retry_request = build_request("GET", redirect_uri, retry_options)
|
|
52
72
|
|
|
53
73
|
request.redirect_request = retry_request
|
|
54
74
|
|
|
@@ -83,29 +103,6 @@ module HTTPX
|
|
|
83
103
|
nil
|
|
84
104
|
end
|
|
85
105
|
|
|
86
|
-
def build_redirect_request(request, response, options)
|
|
87
|
-
redirect_uri = __get_location_from_response(response)
|
|
88
|
-
max_redirects = request.max_redirects
|
|
89
|
-
|
|
90
|
-
if response.status == 305 && options.respond_to?(:proxy)
|
|
91
|
-
# The requested resource MUST be accessed through the proxy given by
|
|
92
|
-
# the Location field. The Location field gives the URI of the proxy.
|
|
93
|
-
retry_options = options.merge(headers: request.headers,
|
|
94
|
-
proxy: { uri: redirect_uri },
|
|
95
|
-
body: request.body,
|
|
96
|
-
max_redirects: max_redirects - 1)
|
|
97
|
-
redirect_uri = request.url
|
|
98
|
-
else
|
|
99
|
-
|
|
100
|
-
# redirects are **ALWAYS** GET
|
|
101
|
-
retry_options = options.merge(headers: request.headers,
|
|
102
|
-
body: request.body,
|
|
103
|
-
max_redirects: max_redirects - 1)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
build_request("GET", redirect_uri, retry_options)
|
|
107
|
-
end
|
|
108
|
-
|
|
109
106
|
def __get_location_from_response(response)
|
|
110
107
|
location_uri = URI(response.headers["location"])
|
|
111
108
|
location_uri = response.uri.merge(location_uri) if location_uri.relative?
|