httpx 0.21.0 → 1.2.1
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 +54 -45
- data/doc/release_notes/0_10_0.md +2 -2
- data/doc/release_notes/0_11_0.md +3 -5
- data/doc/release_notes/0_12_0.md +5 -5
- data/doc/release_notes/0_13_0.md +4 -4
- data/doc/release_notes/0_14_0.md +2 -2
- data/doc/release_notes/0_16_0.md +3 -3
- data/doc/release_notes/0_17_0.md +1 -1
- data/doc/release_notes/0_18_0.md +4 -4
- data/doc/release_notes/0_18_2.md +1 -1
- data/doc/release_notes/0_19_0.md +1 -1
- data/doc/release_notes/0_20_0.md +1 -1
- data/doc/release_notes/0_21_0.md +7 -5
- data/doc/release_notes/0_21_1.md +12 -0
- data/doc/release_notes/0_22_0.md +13 -0
- data/doc/release_notes/0_22_1.md +11 -0
- data/doc/release_notes/0_22_2.md +5 -0
- data/doc/release_notes/0_22_3.md +55 -0
- data/doc/release_notes/0_22_4.md +6 -0
- data/doc/release_notes/0_22_5.md +6 -0
- data/doc/release_notes/0_23_0.md +42 -0
- data/doc/release_notes/0_23_1.md +5 -0
- data/doc/release_notes/0_23_2.md +5 -0
- data/doc/release_notes/0_23_3.md +6 -0
- data/doc/release_notes/0_23_4.md +5 -0
- data/doc/release_notes/0_24_0.md +48 -0
- data/doc/release_notes/0_24_1.md +12 -0
- data/doc/release_notes/0_24_2.md +12 -0
- data/doc/release_notes/0_24_3.md +12 -0
- data/doc/release_notes/0_24_4.md +18 -0
- data/doc/release_notes/0_24_5.md +6 -0
- data/doc/release_notes/0_24_6.md +5 -0
- data/doc/release_notes/0_24_7.md +10 -0
- data/doc/release_notes/1_0_0.md +60 -0
- data/doc/release_notes/1_0_1.md +5 -0
- data/doc/release_notes/1_0_2.md +7 -0
- data/doc/release_notes/1_1_0.md +32 -0
- data/doc/release_notes/1_1_1.md +17 -0
- data/doc/release_notes/1_1_2.md +12 -0
- data/doc/release_notes/1_1_3.md +18 -0
- data/doc/release_notes/1_1_4.md +6 -0
- data/doc/release_notes/1_1_5.md +12 -0
- data/doc/release_notes/1_2_0.md +49 -0
- data/doc/release_notes/1_2_1.md +6 -0
- data/lib/httpx/adapters/datadog.rb +100 -106
- data/lib/httpx/adapters/faraday.rb +143 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +33 -17
- data/lib/httpx/altsvc.rb +61 -24
- data/lib/httpx/base64.rb +27 -0
- data/lib/httpx/buffer.rb +12 -0
- data/lib/httpx/callbacks.rb +5 -3
- data/lib/httpx/chainable.rb +54 -39
- data/lib/httpx/connection/http1.rb +62 -37
- data/lib/httpx/connection/http2.rb +16 -27
- data/lib/httpx/connection.rb +213 -120
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +34 -2
- data/lib/httpx/extensions.rb +4 -134
- data/lib/httpx/io/ssl.rb +77 -71
- data/lib/httpx/io/tcp.rb +46 -70
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +6 -13
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +168 -110
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +13 -14
- 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_sdk_authentication.rb +1 -3
- data/lib/httpx/plugins/aws_sigv4.rb +5 -6
- data/lib/httpx/plugins/basic_auth.rb +29 -0
- data/lib/httpx/plugins/brotli.rb +50 -0
- data/lib/httpx/plugins/callbacks.rb +91 -0
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +14 -5
- data/lib/httpx/plugins/circuit_breaker.rb +30 -7
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +20 -10
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +11 -12
- data/lib/httpx/plugins/expect.rb +15 -13
- data/lib/httpx/plugins/follow_redirects.rb +71 -29
- data/lib/httpx/plugins/grpc/call.rb +2 -3
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +88 -0
- data/lib/httpx/plugins/grpc/message.rb +7 -37
- data/lib/httpx/plugins/grpc.rb +35 -29
- data/lib/httpx/plugins/h2c.rb +25 -18
- data/lib/httpx/plugins/internal_telemetry.rb +16 -0
- data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +7 -5
- data/lib/httpx/plugins/oauth.rb +170 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +15 -10
- data/lib/httpx/plugins/proxy/socks4.rb +8 -6
- data/lib/httpx/plugins/proxy/socks5.rb +10 -8
- data/lib/httpx/plugins/proxy.rb +69 -67
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +3 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +34 -17
- data/lib/httpx/plugins/response_cache.rb +6 -6
- data/lib/httpx/plugins/retries.rb +61 -12
- data/lib/httpx/plugins/ssrf_filter.rb +142 -0
- data/lib/httpx/plugins/stream.rb +27 -32
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +10 -8
- data/lib/httpx/pool.rb +85 -23
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +158 -0
- data/lib/httpx/request.rb +86 -121
- data/lib/httpx/resolver/https.rb +54 -17
- data/lib/httpx/resolver/multi.rb +8 -12
- data/lib/httpx/resolver/native.rb +163 -70
- data/lib/httpx/resolver/resolver.rb +28 -13
- data/lib/httpx/resolver/system.rb +15 -10
- data/lib/httpx/resolver.rb +38 -16
- data/lib/httpx/response/body.rb +242 -0
- data/lib/httpx/response/buffer.rb +96 -0
- data/lib/httpx/response.rb +113 -211
- data/lib/httpx/selector.rb +2 -4
- data/lib/httpx/session.rb +91 -64
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +28 -8
- data/lib/httpx/transcoder/body.rb +0 -2
- data/lib/httpx/transcoder/chunker.rb +0 -1
- data/lib/httpx/transcoder/deflate.rb +37 -0
- data/lib/httpx/transcoder/form.rb +52 -33
- data/lib/httpx/transcoder/gzip.rb +74 -0
- data/lib/httpx/transcoder/json.rb +2 -5
- 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/utils/inflater.rb +19 -0
- data/lib/httpx/transcoder/xml.rb +0 -5
- data/lib/httpx/transcoder.rb +4 -6
- data/lib/httpx/utils.rb +36 -16
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +12 -14
- data/sig/altsvc.rbs +33 -0
- data/sig/buffer.rbs +1 -0
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +10 -9
- data/sig/connection/http1.rbs +5 -4
- data/sig/connection/http2.rbs +1 -1
- data/sig/connection.rbs +46 -24
- data/sig/errors.rbs +9 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +26 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +10 -0
- data/sig/options.rbs +28 -12
- data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
- data/sig/plugins/{authentication → auth}/digest.rbs +2 -1
- 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/callbacks.rbs +38 -0
- data/sig/plugins/circuit_breaker.rbs +13 -3
- data/sig/plugins/compression.rbs +6 -4
- data/sig/plugins/cookies/jar.rbs +2 -2
- data/sig/plugins/cookies.rbs +2 -0
- data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
- data/sig/plugins/follow_redirects.rbs +11 -2
- data/sig/plugins/grpc/call.rbs +19 -0
- data/sig/plugins/grpc/grpc_encoding.rbs +37 -0
- data/sig/plugins/grpc/message.rbs +17 -0
- data/sig/plugins/grpc.rbs +2 -32
- data/sig/plugins/h2c.rbs +1 -1
- data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
- data/sig/plugins/oauth.rbs +54 -0
- data/sig/plugins/proxy/socks4.rbs +4 -4
- data/sig/plugins/proxy/socks5.rbs +2 -2
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +10 -4
- data/sig/plugins/response_cache.rbs +12 -3
- data/sig/plugins/retries.rbs +28 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +5 -4
- data/sig/request/body.rbs +40 -0
- data/sig/request.rbs +12 -28
- data/sig/resolver/https.rbs +7 -2
- data/sig/resolver/native.rbs +10 -4
- data/sig/resolver/resolver.rbs +6 -4
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +9 -5
- data/sig/response/body.rbs +53 -0
- data/sig/response/buffer.rbs +24 -0
- data/sig/response.rbs +17 -38
- data/sig/session.rbs +24 -18
- data/sig/timers.rbs +17 -7
- 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 -12
- data/sig/transcoder/utils/body_reader.rbs +15 -0
- data/sig/transcoder/utils/deflater.rbs +29 -0
- data/sig/transcoder/utils/inflater.rbs +12 -0
- data/sig/transcoder/xml.rbs +1 -1
- data/sig/transcoder.rbs +22 -7
- data/sig/utils.rbs +2 -0
- metadata +127 -40
- data/lib/httpx/plugins/authentication.rb +0 -20
- data/lib/httpx/plugins/basic_authentication.rb +0 -30
- data/lib/httpx/plugins/compression/brotli.rb +0 -54
- data/lib/httpx/plugins/compression/deflate.rb +0 -49
- data/lib/httpx/plugins/compression/gzip.rb +0 -88
- data/lib/httpx/plugins/compression.rb +0 -164
- data/lib/httpx/plugins/multipart/decoder.rb +0 -187
- data/lib/httpx/plugins/multipart.rb +0 -84
- data/lib/httpx/registry.rb +0 -85
- data/sig/plugins/authentication.rbs +0 -11
- 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/registry.rbs +0 -13
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
|
@@ -8,12 +8,11 @@ module HTTPX
|
|
|
8
8
|
module Plugins
|
|
9
9
|
module Authentication
|
|
10
10
|
class Digest
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def initialize(user, password, **)
|
|
11
|
+
def initialize(user, password, hashed: false, **)
|
|
14
12
|
@user = user
|
|
15
13
|
@password = password
|
|
16
14
|
@nonce = 0
|
|
15
|
+
@hashed = hashed
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def can_authenticate?(authenticate)
|
|
@@ -21,7 +20,7 @@ module HTTPX
|
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def authenticate(request, authenticate)
|
|
24
|
-
"Digest #{generate_header(request.verb
|
|
23
|
+
"Digest #{generate_header(request.verb, request.path, authenticate)}"
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
private
|
|
@@ -30,9 +29,8 @@ module HTTPX
|
|
|
30
29
|
# discard first token, it's Digest
|
|
31
30
|
auth_info = authenticate[/^(\w+) (.*)/, 2]
|
|
32
31
|
|
|
33
|
-
params =
|
|
34
|
-
|
|
35
|
-
.map { |k, v| [k, v.delete("\"")] }]
|
|
32
|
+
params = auth_info.split(/ *, */)
|
|
33
|
+
.to_h { |val| val.split("=") }.transform_values { |v| v.delete("\"") }
|
|
36
34
|
nonce = params["nonce"]
|
|
37
35
|
nc = next_nonce
|
|
38
36
|
|
|
@@ -45,7 +43,6 @@ module HTTPX
|
|
|
45
43
|
raise DigestError, "unknown algorithm \"#{alg}\"" unless algorithm
|
|
46
44
|
|
|
47
45
|
sess = Regexp.last_match(2)
|
|
48
|
-
params.delete("algorithm")
|
|
49
46
|
else
|
|
50
47
|
algorithm = ::Digest::MD5
|
|
51
48
|
end
|
|
@@ -56,11 +53,13 @@ module HTTPX
|
|
|
56
53
|
end
|
|
57
54
|
|
|
58
55
|
a1 = if sess
|
|
59
|
-
[
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
[
|
|
57
|
+
(@hashed ? @password : algorithm.hexdigest("#{@user}:#{params["realm"]}:#{@password}")),
|
|
58
|
+
nonce,
|
|
59
|
+
cnonce,
|
|
60
|
+
].join ":"
|
|
62
61
|
else
|
|
63
|
-
"#{@user}:#{params["realm"]}:#{@password}"
|
|
62
|
+
@hashed ? @password : "#{@user}:#{params["realm"]}:#{@password}"
|
|
64
63
|
end
|
|
65
64
|
|
|
66
65
|
ha1 = algorithm.hexdigest(a1)
|
|
@@ -77,11 +76,11 @@ module HTTPX
|
|
|
77
76
|
%(response="#{algorithm.hexdigest(request_digest)}"),
|
|
78
77
|
]
|
|
79
78
|
header << %(realm="#{params["realm"]}") if params.key?("realm")
|
|
80
|
-
header << %(algorithm=#{params["algorithm"]}
|
|
81
|
-
header << %(opaque="#{params["opaque"]}") if params.key?("opaque")
|
|
79
|
+
header << %(algorithm=#{params["algorithm"]}) if params.key?("algorithm")
|
|
82
80
|
header << %(cnonce="#{cnonce}") if cnonce
|
|
83
81
|
header << %(nc=#{nc})
|
|
84
82
|
header << %(qop=#{qop}) if qop
|
|
83
|
+
header << %(opaque="#{params["opaque"]}") if params.key?("opaque")
|
|
85
84
|
header.join ", "
|
|
86
85
|
end
|
|
87
86
|
|
|
@@ -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#auth
|
|
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,9 +5,9 @@ module HTTPX
|
|
|
5
5
|
#
|
|
6
6
|
# This plugin adds AWS Sigv4 authentication.
|
|
7
7
|
#
|
|
8
|
-
# https://docs.aws.amazon.com/
|
|
8
|
+
# https://docs.aws.amazon.com/IAM/latest/UserGuide/signing-elements.html
|
|
9
9
|
#
|
|
10
|
-
# https://gitlab.com/
|
|
10
|
+
# https://gitlab.com/os85/httpx/wikis/AWS-SigV4
|
|
11
11
|
#
|
|
12
12
|
module AWSSigV4
|
|
13
13
|
Credentials = Struct.new(:username, :password, :security_token)
|
|
@@ -71,7 +71,7 @@ module HTTPX
|
|
|
71
71
|
end.join
|
|
72
72
|
|
|
73
73
|
# canonical request
|
|
74
|
-
creq = "#{request.verb
|
|
74
|
+
creq = "#{request.verb}" \
|
|
75
75
|
"\n#{request.canonical_path}" \
|
|
76
76
|
"\n#{request.canonical_query}" \
|
|
77
77
|
"\n#{canonical_headers}" \
|
|
@@ -115,7 +115,7 @@ module HTTPX
|
|
|
115
115
|
elsif value.respond_to?(:each)
|
|
116
116
|
digest = OpenSSL::Digest.new(@algorithm)
|
|
117
117
|
|
|
118
|
-
mb_buffer = value.each.
|
|
118
|
+
mb_buffer = value.each.with_object("".b) do |chunk, buffer|
|
|
119
119
|
buffer << chunk
|
|
120
120
|
break if buffer.bytesize >= 1024 * 1024
|
|
121
121
|
end
|
|
@@ -146,7 +146,6 @@ module HTTPX
|
|
|
146
146
|
|
|
147
147
|
def configure(klass)
|
|
148
148
|
klass.plugin(:expect)
|
|
149
|
-
klass.plugin(:compression)
|
|
150
149
|
end
|
|
151
150
|
end
|
|
152
151
|
|
|
@@ -186,7 +185,7 @@ module HTTPX
|
|
|
186
185
|
def canonical_query
|
|
187
186
|
params = query.split("&")
|
|
188
187
|
# params = params.map { |p| p.match(/=/) ? p : p + '=' }
|
|
189
|
-
# From: https://docs.aws.amazon.com/
|
|
188
|
+
# From: https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html#create-canonical-request
|
|
190
189
|
# Sort the parameter names by character code point in ascending order.
|
|
191
190
|
# Parameters with duplicate names should be sorted by value.
|
|
192
191
|
#
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
#
|
|
6
|
+
# This plugin adds helper methods to implement HTTP Basic Auth (https://datatracker.ietf.org/doc/html/rfc7617)
|
|
7
|
+
#
|
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Auth#basic-auth
|
|
9
|
+
#
|
|
10
|
+
module BasicAuth
|
|
11
|
+
class << self
|
|
12
|
+
def load_dependencies(_klass)
|
|
13
|
+
require_relative "auth/basic"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def configure(klass)
|
|
17
|
+
klass.plugin(:auth)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
module InstanceMethods
|
|
22
|
+
def basic_auth(user, password)
|
|
23
|
+
authorization(Authentication::Basic.new(user, password).authenticate)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
register_plugin :basic_auth, BasicAuth
|
|
28
|
+
end
|
|
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
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Plugins
|
|
5
|
+
#
|
|
6
|
+
# This plugin adds suppoort for callbacks around the request/response lifecycle.
|
|
7
|
+
#
|
|
8
|
+
# https://gitlab.com/os85/httpx/-/wikis/Events
|
|
9
|
+
#
|
|
10
|
+
module Callbacks
|
|
11
|
+
# connection closed user-space errors happen after errors can be surfaced to requests,
|
|
12
|
+
# so they need to pierce through the scheduler, which is only possible by simulating an
|
|
13
|
+
# interrupt.
|
|
14
|
+
class CallbackError < Exception; end # rubocop:disable Lint/InheritException
|
|
15
|
+
|
|
16
|
+
module InstanceMethods
|
|
17
|
+
include HTTPX::Callbacks
|
|
18
|
+
|
|
19
|
+
%i[
|
|
20
|
+
connection_opened connection_closed
|
|
21
|
+
request_error
|
|
22
|
+
request_started request_body_chunk request_completed
|
|
23
|
+
response_started response_body_chunk response_completed
|
|
24
|
+
].each do |meth|
|
|
25
|
+
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
|
26
|
+
def on_#{meth}(&blk) # def on_connection_opened(&blk)
|
|
27
|
+
on(:#{meth}, &blk) # on(:connection_opened, &blk)
|
|
28
|
+
end # end
|
|
29
|
+
MOD
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def init_connection(uri, options)
|
|
35
|
+
connection = super
|
|
36
|
+
connection.on(:open) do
|
|
37
|
+
emit_or_callback_error(:connection_opened, connection.origin, connection.io.socket)
|
|
38
|
+
end
|
|
39
|
+
connection.on(:close) do
|
|
40
|
+
emit_or_callback_error(:connection_closed, connection.origin) if connection.used?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
connection
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_request_callbacks(request)
|
|
47
|
+
super
|
|
48
|
+
|
|
49
|
+
request.on(:headers) do
|
|
50
|
+
emit_or_callback_error(:request_started, request)
|
|
51
|
+
end
|
|
52
|
+
request.on(:body_chunk) do |chunk|
|
|
53
|
+
emit_or_callback_error(:request_body_chunk, request, chunk)
|
|
54
|
+
end
|
|
55
|
+
request.on(:done) do
|
|
56
|
+
emit_or_callback_error(:request_completed, request)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
request.on(:response_started) do |res|
|
|
60
|
+
if res.is_a?(Response)
|
|
61
|
+
emit_or_callback_error(:response_started, request, res)
|
|
62
|
+
res.on(:chunk_received) do |chunk|
|
|
63
|
+
emit_or_callback_error(:response_body_chunk, request, res, chunk)
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
emit_or_callback_error(:request_error, request, res.error)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
request.on(:response) do |res|
|
|
70
|
+
emit_or_callback_error(:response_completed, request, res)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def emit_or_callback_error(*args)
|
|
75
|
+
emit(*args)
|
|
76
|
+
rescue StandardError => e
|
|
77
|
+
ex = CallbackError.new(e.message)
|
|
78
|
+
ex.set_backtrace(e.backtrace)
|
|
79
|
+
raise ex
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def receive_requests(*)
|
|
83
|
+
super
|
|
84
|
+
rescue CallbackError => e
|
|
85
|
+
raise e.cause
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
register_plugin :callbacks, Callbacks
|
|
90
|
+
end
|
|
91
|
+
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
|
|
@@ -13,18 +13,29 @@ module HTTPX::Plugins::CircuitBreaker
|
|
|
13
13
|
options.circuit_breaker_half_open_drip_rate
|
|
14
14
|
)
|
|
15
15
|
end
|
|
16
|
+
@circuits_mutex = Thread::Mutex.new
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def try_open(uri, response)
|
|
19
|
-
circuit = get_circuit_for_uri(uri)
|
|
20
|
+
circuit = @circuits_mutex.synchronize { get_circuit_for_uri(uri) }
|
|
20
21
|
|
|
21
22
|
circuit.try_open(response)
|
|
22
23
|
end
|
|
23
24
|
|
|
25
|
+
def try_close(uri)
|
|
26
|
+
circuit = @circuits_mutex.synchronize do
|
|
27
|
+
return unless @circuits.key?(uri.origin) || @circuits.key?(uri.to_s)
|
|
28
|
+
|
|
29
|
+
get_circuit_for_uri(uri)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
circuit.try_close
|
|
33
|
+
end
|
|
34
|
+
|
|
24
35
|
# if circuit is open, it'll respond with the stored response.
|
|
25
36
|
# if not, nil.
|
|
26
37
|
def try_respond(request)
|
|
27
|
-
circuit = get_circuit_for_uri(request.uri)
|
|
38
|
+
circuit = @circuits_mutex.synchronize { get_circuit_for_uri(request.uri) }
|
|
28
39
|
|
|
29
40
|
circuit.respond
|
|
30
41
|
end
|
|
@@ -32,9 +43,7 @@ module HTTPX::Plugins::CircuitBreaker
|
|
|
32
43
|
private
|
|
33
44
|
|
|
34
45
|
def get_circuit_for_uri(uri)
|
|
35
|
-
uri
|
|
36
|
-
|
|
37
|
-
if @circuits.key?(uri.origin)
|
|
46
|
+
if uri.respond_to?(:origin) && @circuits.key?(uri.origin)
|
|
38
47
|
@circuits[uri.origin]
|
|
39
48
|
else
|
|
40
49
|
@circuits[uri.to_s]
|
|
@@ -5,7 +5,7 @@ module HTTPX
|
|
|
5
5
|
#
|
|
6
6
|
# This plugin implements a circuit breaker around connection errors.
|
|
7
7
|
#
|
|
8
|
-
# https://gitlab.com/
|
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Circuit-Breaker
|
|
9
9
|
#
|
|
10
10
|
module CircuitBreaker
|
|
11
11
|
using URIExtensions
|
|
@@ -16,11 +16,17 @@ 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
|
|
28
|
+
include HTTPX::Callbacks
|
|
29
|
+
|
|
24
30
|
def initialize(*)
|
|
25
31
|
super
|
|
26
32
|
@circuit_store = CircuitStore.new(@options)
|
|
@@ -31,19 +37,29 @@ module HTTPX
|
|
|
31
37
|
@circuit_store = orig.instance_variable_get(:@circuit_store).dup
|
|
32
38
|
end
|
|
33
39
|
|
|
40
|
+
%i[circuit_open].each do |meth|
|
|
41
|
+
class_eval(<<-MOD, __FILE__, __LINE__ + 1)
|
|
42
|
+
def on_#{meth}(&blk) # def on_circuit_open(&blk)
|
|
43
|
+
on(:#{meth}, &blk) # on(:circuit_open, &blk)
|
|
44
|
+
end # end
|
|
45
|
+
MOD
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
34
50
|
def send_requests(*requests)
|
|
35
51
|
# @type var short_circuit_responses: Array[response]
|
|
36
52
|
short_circuit_responses = []
|
|
37
53
|
|
|
38
54
|
# run all requests through the circuit breaker, see if the circuit is
|
|
39
55
|
# open for any of them.
|
|
40
|
-
real_requests = requests.
|
|
56
|
+
real_requests = requests.each_with_index.with_object([]) do |(req, idx), real_reqs|
|
|
41
57
|
short_circuit_response = @circuit_store.try_respond(req)
|
|
42
58
|
if short_circuit_response.nil?
|
|
43
59
|
real_reqs << req
|
|
44
60
|
next
|
|
45
61
|
end
|
|
46
|
-
short_circuit_responses[
|
|
62
|
+
short_circuit_responses[idx] = short_circuit_response
|
|
47
63
|
end
|
|
48
64
|
|
|
49
65
|
# run requests for the remainder
|
|
@@ -59,6 +75,12 @@ module HTTPX
|
|
|
59
75
|
end
|
|
60
76
|
|
|
61
77
|
def on_response(request, response)
|
|
78
|
+
emit(:circuit_open, request) if try_circuit_open(request, response)
|
|
79
|
+
|
|
80
|
+
super
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def try_circuit_open(request, response)
|
|
62
84
|
if response.is_a?(ErrorResponse)
|
|
63
85
|
case response.error
|
|
64
86
|
when RequestTimeoutError
|
|
@@ -68,9 +90,10 @@ module HTTPX
|
|
|
68
90
|
end
|
|
69
91
|
elsif (break_on = request.options.circuit_breaker_break_on) && break_on.call(response)
|
|
70
92
|
@circuit_store.try_open(request.uri, response)
|
|
93
|
+
else
|
|
94
|
+
@circuit_store.try_close(request.uri)
|
|
95
|
+
nil
|
|
71
96
|
end
|
|
72
|
-
|
|
73
|
-
super
|
|
74
97
|
end
|
|
75
98
|
end
|
|
76
99
|
|
|
@@ -9,7 +9,7 @@ module HTTPX
|
|
|
9
9
|
#
|
|
10
10
|
# It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
|
|
11
11
|
#
|
|
12
|
-
# https://gitlab.com/
|
|
12
|
+
# https://gitlab.com/os85/httpx/wikis/Cookies
|
|
13
13
|
#
|
|
14
14
|
module Cookies
|
|
15
15
|
def self.load_dependencies(*)
|
|
@@ -71,22 +71,32 @@ module HTTPX
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
module OptionsMethods
|
|
74
|
-
def
|
|
75
|
-
super
|
|
74
|
+
def option_headers(*)
|
|
75
|
+
value = super
|
|
76
|
+
|
|
77
|
+
merge_cookie_in_jar(value.delete("cookie"), @cookies) if defined?(@cookies) && value.key?("cookie")
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
value
|
|
80
|
+
end
|
|
78
81
|
|
|
79
|
-
|
|
82
|
+
def option_cookies(value)
|
|
83
|
+
jar = value.is_a?(Jar) ? value : Jar.new(value)
|
|
84
|
+
|
|
85
|
+
merge_cookie_in_jar(@headers.delete("cookie"), jar) if defined?(@headers) && @headers.key?("cookie")
|
|
86
|
+
|
|
87
|
+
jar
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def merge_cookie_in_jar(cookies, jar)
|
|
93
|
+
cookies.each do |ck|
|
|
80
94
|
ck.split(/ *; */).each do |cookie|
|
|
81
95
|
name, value = cookie.split("=", 2)
|
|
82
|
-
|
|
96
|
+
jar.add(Cookie.new(name, value))
|
|
83
97
|
end
|
|
84
98
|
end
|
|
85
99
|
end
|
|
86
|
-
|
|
87
|
-
def option_cookies(value)
|
|
88
|
-
value.is_a?(Jar) ? value : Jar.new(value)
|
|
89
|
-
end
|
|
90
100
|
end
|
|
91
101
|
end
|
|
92
102
|
register_plugin :cookies, Cookies
|