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
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module HTTPX
|
|
4
4
|
module Plugins
|
|
5
5
|
#
|
|
6
|
-
# This plugin adds helper methods to implement HTTP Digest Auth (https://
|
|
6
|
+
# This plugin adds helper methods to implement HTTP Digest Auth (https://datatracker.ietf.org/doc/html/rfc7616)
|
|
7
7
|
#
|
|
8
|
-
# https://gitlab.com/
|
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Auth#digest-auth
|
|
9
9
|
#
|
|
10
10
|
module DigestAuth
|
|
11
11
|
DigestError = Class.new(Error)
|
|
@@ -16,36 +16,35 @@ 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
|
|
|
23
23
|
module OptionsMethods
|
|
24
24
|
def option_digest(value)
|
|
25
|
-
raise TypeError, ":digest must be a Digest" unless value.is_a?(Authentication::Digest)
|
|
25
|
+
raise TypeError, ":digest must be a #{Authentication::Digest}" unless value.is_a?(Authentication::Digest)
|
|
26
26
|
|
|
27
27
|
value
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
module InstanceMethods
|
|
32
|
-
def
|
|
33
|
-
with(digest: Authentication::Digest.new(user, password))
|
|
32
|
+
def digest_auth(user, password, hashed: false)
|
|
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|
|
|
40
40
|
digest = request.options.digest
|
|
41
41
|
|
|
42
|
-
unless digest
|
|
43
|
-
super(request)
|
|
44
|
-
next
|
|
45
|
-
end
|
|
42
|
+
next super(request) unless digest
|
|
46
43
|
|
|
47
44
|
probe_response = wrap { super(request).first }
|
|
48
45
|
|
|
46
|
+
return probe_response unless probe_response.is_a?(Response)
|
|
47
|
+
|
|
49
48
|
if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
50
49
|
request.transition(:idle)
|
|
51
50
|
request.headers["authorization"] = digest.authenticate(request, probe_response.headers["www-authenticate"])
|
|
@@ -58,6 +57,6 @@ module HTTPX
|
|
|
58
57
|
end
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
register_plugin :
|
|
60
|
+
register_plugin :digest_auth, DigestAuth
|
|
62
61
|
end
|
|
63
62
|
end
|
data/lib/httpx/plugins/expect.rb
CHANGED
|
@@ -5,7 +5,7 @@ module HTTPX
|
|
|
5
5
|
#
|
|
6
6
|
# This plugin makes all HTTP/1.1 requests with a body send the "Expect: 100-continue".
|
|
7
7
|
#
|
|
8
|
-
# https://gitlab.com/
|
|
8
|
+
# https://gitlab.com/os85/httpx/wikis/Expect#expect
|
|
9
9
|
#
|
|
10
10
|
module Expect
|
|
11
11
|
EXPECT_TIMEOUT = 2
|
|
@@ -50,7 +50,8 @@ module HTTPX
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def response=(response)
|
|
53
|
-
if response
|
|
53
|
+
if response.is_a?(Response) &&
|
|
54
|
+
response.status == 100 &&
|
|
54
55
|
!@headers.key?("expect") &&
|
|
55
56
|
(@state == :body || @state == :done)
|
|
56
57
|
|
|
@@ -74,14 +75,16 @@ module HTTPX
|
|
|
74
75
|
|
|
75
76
|
return unless request.headers["expect"] == "100-continue"
|
|
76
77
|
|
|
77
|
-
request.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
expect_timeout = request.options.expect_timeout
|
|
79
|
+
|
|
80
|
+
return if expect_timeout.nil? || expect_timeout.infinite?
|
|
81
|
+
|
|
82
|
+
set_request_timeout(request, expect_timeout, :expect, %i[body response]) do
|
|
83
|
+
# expect timeout expired
|
|
84
|
+
if request.state == :expect && !request.expects?
|
|
85
|
+
Expect.no_expect_store << request.origin
|
|
86
|
+
request.headers.delete("expect")
|
|
87
|
+
consume
|
|
85
88
|
end
|
|
86
89
|
end
|
|
87
90
|
end
|
|
@@ -92,12 +95,11 @@ module HTTPX
|
|
|
92
95
|
response = @responses.delete(request)
|
|
93
96
|
return unless response
|
|
94
97
|
|
|
95
|
-
if response.status == 417 && request.headers.key?("expect")
|
|
98
|
+
if response.is_a?(Response) && response.status == 417 && request.headers.key?("expect")
|
|
96
99
|
response.close
|
|
97
100
|
request.headers.delete("expect")
|
|
98
101
|
request.transition(:idle)
|
|
99
|
-
|
|
100
|
-
connection.send(request)
|
|
102
|
+
send_request(request, connections, options)
|
|
101
103
|
return
|
|
102
104
|
end
|
|
103
105
|
|
|
@@ -11,12 +11,14 @@ module HTTPX
|
|
|
11
11
|
#
|
|
12
12
|
# It also doesn't follow insecure redirects (https -> http) by default (see *follow_insecure_redirects*).
|
|
13
13
|
#
|
|
14
|
-
# https://gitlab.com/
|
|
14
|
+
# https://gitlab.com/os85/httpx/wikis/Follow-Redirects
|
|
15
15
|
#
|
|
16
16
|
module FollowRedirects
|
|
17
17
|
MAX_REDIRECTS = 3
|
|
18
18
|
REDIRECT_STATUS = (300..399).freeze
|
|
19
19
|
|
|
20
|
+
using URIExtensions
|
|
21
|
+
|
|
20
22
|
module OptionsMethods
|
|
21
23
|
def option_max_redirects(value)
|
|
22
24
|
num = Integer(value)
|
|
@@ -28,6 +30,16 @@ module HTTPX
|
|
|
28
30
|
def option_follow_insecure_redirects(value)
|
|
29
31
|
value
|
|
30
32
|
end
|
|
33
|
+
|
|
34
|
+
def option_allow_auth_to_other_origins(value)
|
|
35
|
+
value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def option_redirect_on(value)
|
|
39
|
+
raise TypeError, ":redirect_on must be callable" unless value.respond_to?(:call)
|
|
40
|
+
|
|
41
|
+
value
|
|
42
|
+
end
|
|
31
43
|
end
|
|
32
44
|
|
|
33
45
|
module InstanceMethods
|
|
@@ -44,21 +56,53 @@ module HTTPX
|
|
|
44
56
|
|
|
45
57
|
max_redirects = redirect_request.max_redirects
|
|
46
58
|
|
|
59
|
+
return response unless response.is_a?(Response)
|
|
47
60
|
return response unless REDIRECT_STATUS.include?(response.status) && response.headers.key?("location")
|
|
48
61
|
return response unless max_redirects.positive?
|
|
49
62
|
|
|
50
|
-
|
|
63
|
+
# build redirect request
|
|
64
|
+
redirect_uri = __get_location_from_response(response)
|
|
51
65
|
|
|
52
|
-
|
|
66
|
+
if options.redirect_on
|
|
67
|
+
redirect_allowed = options.redirect_on.call(redirect_uri)
|
|
68
|
+
return response unless redirect_allowed
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if response.status == 305 && options.respond_to?(:proxy)
|
|
72
|
+
# The requested resource MUST be accessed through the proxy given by
|
|
73
|
+
# the Location field. The Location field gives the URI of the proxy.
|
|
74
|
+
retry_options = options.merge(headers: redirect_request.headers,
|
|
75
|
+
proxy: { uri: redirect_uri },
|
|
76
|
+
body: redirect_request.body,
|
|
77
|
+
max_redirects: max_redirects - 1)
|
|
78
|
+
redirect_uri = redirect_request.uri
|
|
79
|
+
options = retry_options
|
|
80
|
+
else
|
|
81
|
+
redirect_headers = redirect_request_headers(redirect_request.uri, redirect_uri, request.headers, options)
|
|
82
|
+
|
|
83
|
+
# redirects are **ALWAYS** GET
|
|
84
|
+
retry_opts = Hash[options].merge(
|
|
85
|
+
headers: redirect_headers.to_h,
|
|
86
|
+
body: redirect_request.body,
|
|
87
|
+
max_redirects: max_redirects - 1
|
|
88
|
+
)
|
|
89
|
+
retry_options = options.class.new(retry_opts)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
redirect_uri = Utils.to_uri(redirect_uri)
|
|
53
93
|
|
|
54
94
|
if !options.follow_insecure_redirects &&
|
|
55
95
|
response.uri.scheme == "https" &&
|
|
56
|
-
|
|
57
|
-
error = InsecureRedirectError.new(
|
|
96
|
+
redirect_uri.scheme == "http"
|
|
97
|
+
error = InsecureRedirectError.new(redirect_uri.to_s)
|
|
58
98
|
error.set_backtrace(caller)
|
|
59
99
|
return ErrorResponse.new(request, error, options)
|
|
60
100
|
end
|
|
61
101
|
|
|
102
|
+
retry_request = build_request("GET", redirect_uri, retry_options)
|
|
103
|
+
|
|
104
|
+
request.redirect_request = retry_request
|
|
105
|
+
|
|
62
106
|
retry_after = response.headers["retry-after"]
|
|
63
107
|
|
|
64
108
|
if retry_after
|
|
@@ -72,37 +116,25 @@ module HTTPX
|
|
|
72
116
|
|
|
73
117
|
log { "redirecting after #{retry_after} secs..." }
|
|
74
118
|
pool.after(retry_after) do
|
|
75
|
-
|
|
76
|
-
connection.send(retry_request)
|
|
119
|
+
send_request(retry_request, connections, options)
|
|
77
120
|
end
|
|
78
121
|
else
|
|
79
|
-
|
|
80
|
-
connection.send(retry_request)
|
|
122
|
+
send_request(retry_request, connections, options)
|
|
81
123
|
end
|
|
82
124
|
nil
|
|
83
125
|
end
|
|
84
126
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
max_redirects = request.max_redirects
|
|
127
|
+
def redirect_request_headers(original_uri, redirect_uri, headers, options)
|
|
128
|
+
return headers if options.allow_auth_to_other_origins
|
|
88
129
|
|
|
89
|
-
|
|
90
|
-
# The requested resource MUST be accessed through the proxy given by
|
|
91
|
-
# the Location field. The Location field gives the URI of the proxy.
|
|
92
|
-
retry_options = options.merge(headers: request.headers,
|
|
93
|
-
proxy: { uri: redirect_uri },
|
|
94
|
-
body: request.body,
|
|
95
|
-
max_redirects: max_redirects - 1)
|
|
96
|
-
redirect_uri = request.url
|
|
97
|
-
else
|
|
130
|
+
return headers unless headers.key?("authorization")
|
|
98
131
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
max_redirects: max_redirects - 1)
|
|
132
|
+
unless original_uri.origin == redirect_uri.origin
|
|
133
|
+
headers = headers.dup
|
|
134
|
+
headers.delete("authorization")
|
|
103
135
|
end
|
|
104
136
|
|
|
105
|
-
|
|
137
|
+
headers
|
|
106
138
|
end
|
|
107
139
|
|
|
108
140
|
def __get_location_from_response(response)
|
|
@@ -113,14 +145,24 @@ module HTTPX
|
|
|
113
145
|
end
|
|
114
146
|
|
|
115
147
|
module RequestMethods
|
|
116
|
-
|
|
117
|
-
klass.__send__(:attr_writer, :redirect_request)
|
|
118
|
-
end
|
|
148
|
+
attr_accessor :root_request
|
|
119
149
|
|
|
120
150
|
def redirect_request
|
|
121
151
|
@redirect_request || self
|
|
122
152
|
end
|
|
123
153
|
|
|
154
|
+
def redirect_request=(req)
|
|
155
|
+
@redirect_request = req
|
|
156
|
+
req.root_request = @root_request || self
|
|
157
|
+
@response = nil
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def response
|
|
161
|
+
return super unless @redirect_request
|
|
162
|
+
|
|
163
|
+
@redirect_request.response
|
|
164
|
+
end
|
|
165
|
+
|
|
124
166
|
def max_redirects
|
|
125
167
|
@options.max_redirects || MAX_REDIRECTS
|
|
126
168
|
end
|
|
@@ -11,6 +11,7 @@ module HTTPX
|
|
|
11
11
|
@response = response
|
|
12
12
|
@decoder = ->(z) { z }
|
|
13
13
|
@consumed = false
|
|
14
|
+
@grpc_response = nil
|
|
14
15
|
end
|
|
15
16
|
|
|
16
17
|
def inspect
|
|
@@ -34,9 +35,7 @@ module HTTPX
|
|
|
34
35
|
private
|
|
35
36
|
|
|
36
37
|
def grpc_response
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@grpc_response = if @response.respond_to?(:each)
|
|
38
|
+
@grpc_response ||= if @response.respond_to?(:each)
|
|
40
39
|
Enumerator.new do |y|
|
|
41
40
|
Message.stream(@response).each do |message|
|
|
42
41
|
y << @decoder.call(message)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Transcoder
|
|
5
|
+
module GRPCEncoding
|
|
6
|
+
class Deflater
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
attr_reader :content_type
|
|
10
|
+
|
|
11
|
+
def initialize(body, compressed:)
|
|
12
|
+
@content_type = body.content_type
|
|
13
|
+
@body = BodyReader.new(body)
|
|
14
|
+
@compressed = compressed
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def bytesize
|
|
18
|
+
return @body.bytesize if @body.respond_to?(:bytesize)
|
|
19
|
+
|
|
20
|
+
Float::INFINITY
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def read(length = nil, outbuf = nil)
|
|
24
|
+
buf = @body.read(length, outbuf)
|
|
25
|
+
|
|
26
|
+
return unless buf
|
|
27
|
+
|
|
28
|
+
compressed_flag = @compressed ? 1 : 0
|
|
29
|
+
|
|
30
|
+
buf = outbuf if outbuf
|
|
31
|
+
|
|
32
|
+
buf.prepend([compressed_flag, buf.bytesize].pack("CL>"))
|
|
33
|
+
buf
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Inflater
|
|
38
|
+
def initialize(response)
|
|
39
|
+
@response = response
|
|
40
|
+
@grpc_encodings = nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def call(message, &blk)
|
|
44
|
+
data = "".b
|
|
45
|
+
|
|
46
|
+
until message.empty?
|
|
47
|
+
compressed, size = message.unpack("CL>")
|
|
48
|
+
|
|
49
|
+
encoded_data = message.byteslice(5..size + 5 - 1)
|
|
50
|
+
|
|
51
|
+
if compressed == 1
|
|
52
|
+
grpc_encodings.reverse_each do |encoding|
|
|
53
|
+
decoder = @response.body.class.initialize_inflater_by_encoding(encoding, @response, bytesize: encoded_data.bytesize)
|
|
54
|
+
encoded_data = decoder.call(encoded_data)
|
|
55
|
+
|
|
56
|
+
blk.call(encoded_data) if blk
|
|
57
|
+
|
|
58
|
+
data << encoded_data
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
blk.call(encoded_data) if blk
|
|
62
|
+
|
|
63
|
+
data << encoded_data
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
message = message.byteslice((size + 5)..-1)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
data
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def grpc_encodings
|
|
75
|
+
@grpc_encodings ||= @response.headers.get("grpc-encoding")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.encode(*args, **kwargs)
|
|
80
|
+
Deflater.new(*args, **kwargs)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.decode(response)
|
|
84
|
+
Inflater.new(response)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -12,55 +12,25 @@ module HTTPX
|
|
|
12
12
|
# decodes a unary grpc response
|
|
13
13
|
def unary(response)
|
|
14
14
|
verify_status(response)
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
decoder = Transcoder::GRPCEncoding.decode(response)
|
|
17
|
+
|
|
18
|
+
decoder.call(response.to_s)
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
# lazy decodes a grpc stream response
|
|
19
22
|
def stream(response, &block)
|
|
20
23
|
return enum_for(__method__, response) unless block
|
|
21
24
|
|
|
25
|
+
decoder = Transcoder::GRPCEncoding.decode(response)
|
|
26
|
+
|
|
22
27
|
response.each do |frame|
|
|
23
|
-
|
|
28
|
+
decoder.call(frame, &block)
|
|
24
29
|
end
|
|
25
30
|
|
|
26
31
|
verify_status(response)
|
|
27
32
|
end
|
|
28
33
|
|
|
29
|
-
# encodes a single grpc message
|
|
30
|
-
def encode(bytes, deflater:)
|
|
31
|
-
if deflater
|
|
32
|
-
compressed_flag = 1
|
|
33
|
-
bytes = deflater.deflate(StringIO.new(bytes))
|
|
34
|
-
else
|
|
35
|
-
compressed_flag = 0
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
"".b << [compressed_flag, bytes.bytesize].pack("CL>") << bytes.to_s
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# decodes a single grpc message
|
|
42
|
-
def decode(message, encodings:, encoders:)
|
|
43
|
-
until message.empty?
|
|
44
|
-
|
|
45
|
-
compressed, size = message.unpack("CL>")
|
|
46
|
-
|
|
47
|
-
data = message.byteslice(5..size + 5 - 1)
|
|
48
|
-
if compressed == 1
|
|
49
|
-
encodings.reverse_each do |algo|
|
|
50
|
-
inflater = encoders.registry(algo).inflater(size)
|
|
51
|
-
data = inflater.inflate(data)
|
|
52
|
-
size = data.bytesize
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
return data unless block_given?
|
|
57
|
-
|
|
58
|
-
yield data
|
|
59
|
-
|
|
60
|
-
message = message.byteslice((5 + size)..-1)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
34
|
def cancel(request)
|
|
65
35
|
request.emit(:refuse, :client_cancellation)
|
|
66
36
|
end
|
data/lib/httpx/plugins/grpc.rb
CHANGED
|
@@ -16,7 +16,7 @@ module HTTPX
|
|
|
16
16
|
#
|
|
17
17
|
# This plugin adds DSL to build GRPC interfaces.
|
|
18
18
|
#
|
|
19
|
-
# https://gitlab.com/
|
|
19
|
+
# https://gitlab.com/os85/httpx/wikis/GRPC
|
|
20
20
|
#
|
|
21
21
|
module GRPC
|
|
22
22
|
unless String.method_defined?(:underscore)
|
|
@@ -49,20 +49,19 @@ module HTTPX
|
|
|
49
49
|
class << self
|
|
50
50
|
def load_dependencies(*)
|
|
51
51
|
require "stringio"
|
|
52
|
+
require "httpx/plugins/grpc/grpc_encoding"
|
|
52
53
|
require "httpx/plugins/grpc/message"
|
|
53
54
|
require "httpx/plugins/grpc/call"
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
def configure(klass)
|
|
57
58
|
klass.plugin(:persistent)
|
|
58
|
-
klass.plugin(:compression)
|
|
59
59
|
klass.plugin(:stream)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
def extra_options(options)
|
|
63
63
|
options.merge(
|
|
64
64
|
fallback_protocol: "h2",
|
|
65
|
-
http2_settings: { wait_for_handshake: false },
|
|
66
65
|
grpc_rpcs: {}.freeze,
|
|
67
66
|
grpc_compression: false,
|
|
68
67
|
grpc_deadline: DEADLINE
|
|
@@ -108,9 +107,18 @@ module HTTPX
|
|
|
108
107
|
@trailing_metadata = Hash[trailers]
|
|
109
108
|
super
|
|
110
109
|
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
module RequestBodyMethods
|
|
113
|
+
def initialize(headers, _)
|
|
114
|
+
super
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
if (compression = headers["grpc-encoding"])
|
|
117
|
+
deflater_body = self.class.initialize_deflater_body(@body, compression)
|
|
118
|
+
@body = Transcoder::GRPCEncoding.encode(deflater_body || @body, compressed: !deflater_body.nil?)
|
|
119
|
+
else
|
|
120
|
+
@body = Transcoder::GRPCEncoding.encode(@body, compressed: false)
|
|
121
|
+
end
|
|
114
122
|
end
|
|
115
123
|
end
|
|
116
124
|
|
|
@@ -141,17 +149,29 @@ module HTTPX
|
|
|
141
149
|
deadline: @options.grpc_deadline,
|
|
142
150
|
}.merge(opts)
|
|
143
151
|
|
|
152
|
+
local_rpc_name = rpc_name.underscore
|
|
153
|
+
|
|
144
154
|
session_class = Class.new(self.class) do
|
|
155
|
+
# define rpc method with ruby style name
|
|
145
156
|
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
146
|
-
def #{
|
|
147
|
-
rpc_execute("#{
|
|
148
|
-
end
|
|
157
|
+
def #{local_rpc_name}(input, **opts) # def grpc_action(input, **opts)
|
|
158
|
+
rpc_execute("#{local_rpc_name}", input, **opts) # rpc_execute("grpc_action", input, **opts)
|
|
159
|
+
end # end
|
|
149
160
|
OUT
|
|
161
|
+
|
|
162
|
+
# define rpc method with original name
|
|
163
|
+
unless local_rpc_name == rpc_name
|
|
164
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
165
|
+
def #{rpc_name}(input, **opts) # def grpcAction(input, **opts)
|
|
166
|
+
rpc_execute("#{local_rpc_name}", input, **opts) # rpc_execute("grpc_action", input, **opts)
|
|
167
|
+
end # end
|
|
168
|
+
OUT
|
|
169
|
+
end
|
|
150
170
|
end
|
|
151
171
|
|
|
152
172
|
session_class.new(@options.merge(
|
|
153
173
|
grpc_rpcs: @options.grpc_rpcs.merge(
|
|
154
|
-
|
|
174
|
+
local_rpc_name => [rpc_name, input, output, rpc_opts]
|
|
155
175
|
).freeze
|
|
156
176
|
))
|
|
157
177
|
end
|
|
@@ -195,7 +215,7 @@ module HTTPX
|
|
|
195
215
|
**opts)
|
|
196
216
|
grpc_request = build_grpc_request(rpc_method, input, deadline: deadline, metadata: metadata, **opts)
|
|
197
217
|
response = request(grpc_request, **opts)
|
|
198
|
-
response.raise_for_status
|
|
218
|
+
response.raise_for_status unless opts[:stream]
|
|
199
219
|
GRPC::Call.new(response)
|
|
200
220
|
end
|
|
201
221
|
|
|
@@ -233,7 +253,7 @@ module HTTPX
|
|
|
233
253
|
uri.path = rpc_method
|
|
234
254
|
|
|
235
255
|
headers = HEADERS.merge(
|
|
236
|
-
"grpc-accept-encoding" => ["identity", *@options.
|
|
256
|
+
"grpc-accept-encoding" => ["identity", *@options.supported_compression_formats]
|
|
237
257
|
)
|
|
238
258
|
unless deadline == Float::INFINITY
|
|
239
259
|
# convert to milliseconds
|
|
@@ -241,30 +261,16 @@ module HTTPX
|
|
|
241
261
|
headers["grpc-timeout"] = "#{deadline}m"
|
|
242
262
|
end
|
|
243
263
|
|
|
244
|
-
headers = headers.merge(metadata) if metadata
|
|
264
|
+
headers = headers.merge(metadata.transform_keys(&:to_s)) if metadata
|
|
245
265
|
|
|
246
266
|
# prepare compressor
|
|
247
|
-
deflater = nil
|
|
248
267
|
compression = @options.grpc_compression == true ? "gzip" : @options.grpc_compression
|
|
249
268
|
|
|
250
|
-
if compression
|
|
251
|
-
headers["grpc-encoding"] = compression
|
|
252
|
-
deflater = @options.encodings.registry(compression).deflater
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
headers.merge!(@options.call_credentials.call) if @options.call_credentials
|
|
269
|
+
headers["grpc-encoding"] = compression if compression
|
|
256
270
|
|
|
257
|
-
|
|
258
|
-
Enumerator.new do |y|
|
|
259
|
-
input.each do |message|
|
|
260
|
-
y << Message.encode(message, deflater: deflater)
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
else
|
|
264
|
-
Message.encode(input, deflater: deflater)
|
|
265
|
-
end
|
|
271
|
+
headers.merge!(@options.call_credentials.call.transform_keys(&:to_s)) if @options.call_credentials
|
|
266
272
|
|
|
267
|
-
build_request(
|
|
273
|
+
build_request("POST", uri, headers: headers, body: input)
|
|
268
274
|
end
|
|
269
275
|
end
|
|
270
276
|
end
|
data/lib/httpx/plugins/h2c.rb
CHANGED
|
@@ -4,21 +4,16 @@ module HTTPX
|
|
|
4
4
|
module Plugins
|
|
5
5
|
#
|
|
6
6
|
# This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
|
|
7
|
-
# (https://
|
|
7
|
+
# (https://datatracker.ietf.org/doc/html/rfc7540#section-3.2)
|
|
8
8
|
#
|
|
9
|
-
# https://gitlab.com/
|
|
9
|
+
# https://gitlab.com/os85/httpx/wikis/Connection-Upgrade#h2c
|
|
10
10
|
#
|
|
11
11
|
module H2C
|
|
12
|
-
VALID_H2C_VERBS = %
|
|
12
|
+
VALID_H2C_VERBS = %w[GET OPTIONS HEAD].freeze
|
|
13
13
|
|
|
14
14
|
class << self
|
|
15
|
-
def load_dependencies(
|
|
16
|
-
require "base64"
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def configure(klass)
|
|
15
|
+
def load_dependencies(klass)
|
|
20
16
|
klass.plugin(:upgrade)
|
|
21
|
-
klass.default_options.upgrade_handlers.register "h2c", self
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
def call(connection, request, response)
|
|
@@ -26,7 +21,7 @@ module HTTPX
|
|
|
26
21
|
end
|
|
27
22
|
|
|
28
23
|
def extra_options(options)
|
|
29
|
-
options.merge(max_concurrent_requests: 1)
|
|
24
|
+
options.merge(max_concurrent_requests: 1, upgrade_handlers: options.upgrade_handlers.merge("h2c" => self))
|
|
30
25
|
end
|
|
31
26
|
end
|
|
32
27
|
|
|
@@ -38,7 +33,7 @@ module HTTPX
|
|
|
38
33
|
|
|
39
34
|
connection = pool.find_connection(upgrade_request.uri, upgrade_request.options)
|
|
40
35
|
|
|
41
|
-
return super if connection && connection.upgrade_protocol ==
|
|
36
|
+
return super if connection && connection.upgrade_protocol == "h2c"
|
|
42
37
|
|
|
43
38
|
# build upgrade request
|
|
44
39
|
upgrade_request.headers.add("connection", "upgrade")
|
|
@@ -78,22 +73,34 @@ module HTTPX
|
|
|
78
73
|
@inflight -= prev_parser.requests.size
|
|
79
74
|
end
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
@parser = H2CParser.new(@write_buffer, parser_options)
|
|
76
|
+
@parser = H2CParser.new(@write_buffer, @options)
|
|
83
77
|
set_parser_callbacks(@parser)
|
|
84
78
|
@inflight += 1
|
|
85
79
|
@parser.upgrade(request, response)
|
|
86
|
-
@upgrade_protocol =
|
|
87
|
-
|
|
88
|
-
if request.options.max_concurrent_requests != @options.max_concurrent_requests
|
|
89
|
-
@options = @options.merge(max_concurrent_requests: nil)
|
|
90
|
-
end
|
|
80
|
+
@upgrade_protocol = "h2c"
|
|
91
81
|
|
|
92
82
|
prev_parser.requests.each do |req|
|
|
93
83
|
req.transition(:idle)
|
|
94
84
|
send(req)
|
|
95
85
|
end
|
|
96
86
|
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def send_request_to_parser(request)
|
|
91
|
+
super
|
|
92
|
+
|
|
93
|
+
return unless request.headers["upgrade"] == "h2c" && parser.is_a?(Connection::HTTP1)
|
|
94
|
+
|
|
95
|
+
max_concurrent_requests = parser.max_concurrent_requests
|
|
96
|
+
|
|
97
|
+
return if max_concurrent_requests == 1
|
|
98
|
+
|
|
99
|
+
parser.max_concurrent_requests = 1
|
|
100
|
+
request.once(:response) do
|
|
101
|
+
parser.max_concurrent_requests = max_concurrent_requests
|
|
102
|
+
end
|
|
103
|
+
end
|
|
97
104
|
end
|
|
98
105
|
end
|
|
99
106
|
register_plugin(:h2c, H2C)
|