httpx 0.20.0 → 1.3.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 +5 -5
- 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_19_8.md +1 -1
- data/doc/release_notes/0_20_0.md +2 -2
- data/doc/release_notes/0_20_1.md +5 -0
- data/doc/release_notes/0_20_2.md +7 -0
- data/doc/release_notes/0_20_3.md +6 -0
- data/doc/release_notes/0_20_4.md +17 -0
- data/doc/release_notes/0_20_5.md +3 -0
- data/doc/release_notes/0_21_0.md +96 -0
- 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/doc/release_notes/1_2_2.md +10 -0
- data/doc/release_notes/1_2_3.md +16 -0
- data/doc/release_notes/1_2_4.md +8 -0
- data/doc/release_notes/1_2_5.md +7 -0
- data/doc/release_notes/1_2_6.md +13 -0
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +215 -122
- data/lib/httpx/adapters/faraday.rb +145 -107
- data/lib/httpx/adapters/sentry.rb +26 -7
- data/lib/httpx/adapters/webmock.rb +34 -18
- data/lib/httpx/altsvc.rb +63 -26
- 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 +75 -44
- data/lib/httpx/connection/http2.rb +31 -38
- data/lib/httpx/connection.rb +287 -117
- data/lib/httpx/domain_name.rb +10 -13
- data/lib/httpx/errors.rb +52 -2
- data/lib/httpx/extensions.rb +24 -131
- data/lib/httpx/io/ssl.rb +83 -77
- data/lib/httpx/io/tcp.rb +48 -71
- data/lib/httpx/io/udp.rb +18 -52
- data/lib/httpx/io/unix.rb +10 -15
- data/lib/httpx/io.rb +3 -9
- data/lib/httpx/loggable.rb +4 -19
- data/lib/httpx/options.rb +176 -118
- data/lib/httpx/parser/http1.rb +4 -0
- data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
- data/lib/httpx/plugins/{authentication → auth}/digest.rb +14 -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 +4 -3
- data/lib/httpx/plugins/aws_sigv4.rb +12 -9
- 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 +100 -0
- data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +53 -0
- data/lib/httpx/plugins/circuit_breaker.rb +148 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
- data/lib/httpx/plugins/cookies.rb +30 -17
- data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +14 -12
- data/lib/httpx/plugins/expect.rb +21 -14
- data/lib/httpx/plugins/follow_redirects.rb +140 -41
- 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 +36 -29
- data/lib/httpx/plugins/h2c.rb +26 -19
- 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 +175 -0
- data/lib/httpx/plugins/persistent.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +23 -13
- data/lib/httpx/plugins/proxy/socks4.rb +9 -7
- data/lib/httpx/plugins/proxy/socks5.rb +11 -9
- data/lib/httpx/plugins/proxy.rb +80 -61
- data/lib/httpx/plugins/push_promise.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +5 -1
- data/lib/httpx/plugins/response_cache/file_store.rb +40 -0
- data/lib/httpx/plugins/response_cache/store.rb +62 -25
- data/lib/httpx/plugins/response_cache.rb +105 -12
- data/lib/httpx/plugins/retries.rb +87 -17
- data/lib/httpx/plugins/ssrf_filter.rb +145 -0
- data/lib/httpx/plugins/stream.rb +27 -23
- data/lib/httpx/plugins/upgrade/h2.rb +4 -4
- data/lib/httpx/plugins/upgrade.rb +8 -10
- data/lib/httpx/plugins/webdav.rb +80 -0
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +102 -27
- data/lib/httpx/punycode.rb +9 -291
- data/lib/httpx/request/body.rb +154 -0
- data/lib/httpx/request.rb +130 -146
- data/lib/httpx/resolver/https.rb +62 -27
- data/lib/httpx/resolver/multi.rb +9 -13
- data/lib/httpx/resolver/native.rb +192 -76
- data/lib/httpx/resolver/resolver.rb +34 -9
- data/lib/httpx/resolver/system.rb +16 -11
- 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 +159 -217
- data/lib/httpx/selector.rb +9 -4
- data/lib/httpx/session.rb +137 -89
- data/lib/httpx/session_extensions.rb +4 -1
- data/lib/httpx/timers.rb +34 -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 +21 -8
- data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
- data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +4 -4
- 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 +52 -0
- data/lib/httpx/transcoder.rb +5 -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 +2 -1
- data/sig/callbacks.rbs +3 -3
- data/sig/chainable.rbs +11 -9
- data/sig/connection/http1.rbs +8 -7
- data/sig/connection/http2.rbs +19 -19
- data/sig/connection.rbs +64 -24
- data/sig/errors.rbs +22 -3
- data/sig/httpx.rbs +5 -4
- data/sig/io/ssl.rbs +27 -0
- data/sig/io/tcp.rbs +60 -0
- data/sig/io/udp.rbs +20 -0
- data/sig/io/unix.rbs +27 -0
- data/sig/io.rbs +6 -0
- data/sig/options.rbs +32 -22
- data/sig/parser/http1.rbs +1 -1
- 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 +71 -0
- data/sig/plugins/compression.rbs +7 -5
- 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 +18 -4
- 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 +7 -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/http.rbs +3 -0
- data/sig/plugins/proxy/socks4.rbs +9 -6
- data/sig/plugins/proxy/socks5.rbs +10 -6
- data/sig/plugins/proxy/ssh.rbs +1 -1
- data/sig/plugins/proxy.rbs +13 -5
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache.rbs +36 -7
- data/sig/plugins/retries.rbs +30 -8
- data/sig/plugins/stream.rbs +24 -17
- data/sig/plugins/upgrade.rbs +5 -3
- data/sig/pool.rbs +10 -7
- data/sig/request/body.rbs +38 -0
- data/sig/request.rbs +15 -24
- data/sig/resolver/https.rbs +8 -3
- data/sig/resolver/native.rbs +17 -4
- data/sig/resolver/resolver.rbs +8 -6
- 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 +24 -39
- data/sig/selector.rbs +1 -1
- data/sig/session.rbs +29 -18
- data/sig/timers.rbs +18 -8
- 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 +8 -3
- data/sig/{plugins → transcoder}/multipart.rbs +15 -19
- 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 +22 -0
- data/sig/transcoder.rbs +24 -9
- data/sig/utils.rbs +8 -2
- metadata +163 -41
- 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 -12
- /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
- /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/domain_name.rb
CHANGED
|
@@ -51,8 +51,6 @@ module HTTPX
|
|
|
51
51
|
# non-canonical domain.
|
|
52
52
|
attr_reader :domain
|
|
53
53
|
|
|
54
|
-
DOT = "." # :nodoc:
|
|
55
|
-
|
|
56
54
|
class << self
|
|
57
55
|
def new(domain)
|
|
58
56
|
return domain if domain.is_a?(self)
|
|
@@ -63,8 +61,12 @@ module HTTPX
|
|
|
63
61
|
# Normalizes a _domain_ using the Punycode algorithm as necessary.
|
|
64
62
|
# The result will be a downcased, ASCII-only string.
|
|
65
63
|
def normalize(domain)
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
unless domain.ascii_only?
|
|
65
|
+
domain = domain.chomp(".").unicode_normalize(:nfc)
|
|
66
|
+
domain = Punycode.encode_hostname(domain)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
domain.downcase
|
|
68
70
|
end
|
|
69
71
|
end
|
|
70
72
|
|
|
@@ -73,7 +75,7 @@ module HTTPX
|
|
|
73
75
|
def initialize(hostname)
|
|
74
76
|
hostname = String(hostname)
|
|
75
77
|
|
|
76
|
-
raise ArgumentError, "domain name must not start with a dot: #{hostname}" if hostname.start_with?(
|
|
78
|
+
raise ArgumentError, "domain name must not start with a dot: #{hostname}" if hostname.start_with?(".")
|
|
77
79
|
|
|
78
80
|
begin
|
|
79
81
|
@ipaddr = IPAddr.new(hostname)
|
|
@@ -84,7 +86,7 @@ module HTTPX
|
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
@hostname = DomainName.normalize(hostname)
|
|
87
|
-
tld = if (last_dot = @hostname.rindex(
|
|
89
|
+
tld = if (last_dot = @hostname.rindex("."))
|
|
88
90
|
@hostname[(last_dot + 1)..-1]
|
|
89
91
|
else
|
|
90
92
|
@hostname
|
|
@@ -94,7 +96,7 @@ module HTTPX
|
|
|
94
96
|
@domain = if last_dot
|
|
95
97
|
# fallback - accept cookies down to second level
|
|
96
98
|
# cf. http://www.dkim-reputation.org/regdom-libs/
|
|
97
|
-
if (penultimate_dot = @hostname.rindex(
|
|
99
|
+
if (penultimate_dot = @hostname.rindex(".", last_dot - 1))
|
|
98
100
|
@hostname[(penultimate_dot + 1)..-1]
|
|
99
101
|
else
|
|
100
102
|
@hostname
|
|
@@ -126,17 +128,12 @@ module HTTPX
|
|
|
126
128
|
@domain && self <= domain && domain <= @domain
|
|
127
129
|
end
|
|
128
130
|
|
|
129
|
-
# def ==(other)
|
|
130
|
-
# other = DomainName.new(other)
|
|
131
|
-
# other.hostname == @hostname
|
|
132
|
-
# end
|
|
133
|
-
|
|
134
131
|
def <=>(other)
|
|
135
132
|
other = DomainName.new(other)
|
|
136
133
|
othername = other.hostname
|
|
137
134
|
if othername == @hostname
|
|
138
135
|
0
|
|
139
|
-
elsif @hostname.end_with?(othername) && @hostname[-othername.size - 1, 1] ==
|
|
136
|
+
elsif @hostname.end_with?(othername) && @hostname[-othername.size - 1, 1] == "."
|
|
140
137
|
# The other is higher
|
|
141
138
|
-1
|
|
142
139
|
else
|
data/lib/httpx/errors.rb
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
|
+
# the default exception class for exceptions raised by HTTPX.
|
|
4
5
|
class Error < StandardError; end
|
|
5
6
|
|
|
6
7
|
class UnsupportedSchemeError < Error; end
|
|
7
8
|
|
|
9
|
+
class ConnectionError < Error; end
|
|
10
|
+
|
|
11
|
+
# Error raised when there was a timeout. Its subclasses allow for finer-grained
|
|
12
|
+
# control of which timeout happened.
|
|
8
13
|
class TimeoutError < Error
|
|
14
|
+
# The timeout value which caused this error to be raised.
|
|
9
15
|
attr_reader :timeout
|
|
10
16
|
|
|
17
|
+
# initializes the timeout exception with the +timeout+ causing the error, and the
|
|
18
|
+
# error +message+ for it.
|
|
11
19
|
def initialize(timeout, message)
|
|
12
20
|
@timeout = timeout
|
|
13
21
|
super(message)
|
|
14
22
|
end
|
|
15
23
|
|
|
24
|
+
# clones this error into a HTTPX::ConnectionTimeoutError.
|
|
16
25
|
def to_connection_error
|
|
17
26
|
ex = ConnectTimeoutError.new(@timeout, message)
|
|
18
27
|
ex.set_backtrace(backtrace)
|
|
@@ -20,19 +29,52 @@ module HTTPX
|
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
# Error raised when there was a timeout establishing the connection to a server.
|
|
33
|
+
# This may be raised due to timeouts during TCP and TLS (when applicable) connection
|
|
34
|
+
# establishment.
|
|
25
35
|
class ConnectTimeoutError < TimeoutError; end
|
|
26
36
|
|
|
37
|
+
# Error raised when there was a timeout while sending a request, or receiving a response
|
|
38
|
+
# from the server.
|
|
39
|
+
class RequestTimeoutError < TimeoutError
|
|
40
|
+
# The HTTPX::Request request object this exception refers to.
|
|
41
|
+
attr_reader :request
|
|
42
|
+
|
|
43
|
+
# initializes the exception with the +request+ and +response+ it refers to, and the
|
|
44
|
+
# +timeout+ causing the error, and the
|
|
45
|
+
def initialize(request, response, timeout)
|
|
46
|
+
@request = request
|
|
47
|
+
@response = response
|
|
48
|
+
super(timeout, "Timed out after #{timeout} seconds")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def marshal_dump
|
|
52
|
+
[message]
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Error raised when there was a timeout while receiving a response from the server.
|
|
57
|
+
class ReadTimeoutError < RequestTimeoutError; end
|
|
58
|
+
|
|
59
|
+
# Error raised when there was a timeout while sending a request from the server.
|
|
60
|
+
class WriteTimeoutError < RequestTimeoutError; end
|
|
61
|
+
|
|
62
|
+
# Error raised when there was a timeout while waiting for the HTTP/2 settings frame from the server.
|
|
27
63
|
class SettingsTimeoutError < TimeoutError; end
|
|
28
64
|
|
|
65
|
+
# Error raised when there was a timeout while resolving a domain to an IP.
|
|
29
66
|
class ResolveTimeoutError < TimeoutError; end
|
|
30
67
|
|
|
68
|
+
# Error raised when there was an error while resolving a domain to an IP.
|
|
31
69
|
class ResolveError < Error; end
|
|
32
70
|
|
|
71
|
+
# Error raised when there was an error while resolving a domain to an IP
|
|
72
|
+
# using a HTTPX::Resolver::Native resolver.
|
|
33
73
|
class NativeResolveError < ResolveError
|
|
34
74
|
attr_reader :connection, :host
|
|
35
75
|
|
|
76
|
+
# initializes the exception with the +connection+ it refers to, the +host+ domain
|
|
77
|
+
# which failed to resolve, and the error +message+.
|
|
36
78
|
def initialize(connection, host, message = "Can't resolve #{host}")
|
|
37
79
|
@connection = connection
|
|
38
80
|
@host = host
|
|
@@ -40,18 +82,26 @@ module HTTPX
|
|
|
40
82
|
end
|
|
41
83
|
end
|
|
42
84
|
|
|
85
|
+
# The exception class for HTTP responses with 4xx or 5xx status.
|
|
43
86
|
class HTTPError < Error
|
|
87
|
+
# The HTTPX::Response response object this exception refers to.
|
|
44
88
|
attr_reader :response
|
|
45
89
|
|
|
90
|
+
# Creates the instance and assigns the HTTPX::Response +response+.
|
|
46
91
|
def initialize(response)
|
|
47
92
|
@response = response
|
|
48
93
|
super("HTTP Error: #{@response.status} #{@response.headers}\n#{@response.body}")
|
|
49
94
|
end
|
|
50
95
|
|
|
96
|
+
# The HTTP response status.
|
|
97
|
+
#
|
|
98
|
+
# error.status #=> 404
|
|
51
99
|
def status
|
|
52
100
|
@response.status
|
|
53
101
|
end
|
|
54
102
|
end
|
|
55
103
|
|
|
104
|
+
# error raised when a request was sent a server which can't reproduce a response, and
|
|
105
|
+
# has therefore returned an HTTP response using the 421 status code.
|
|
56
106
|
class MisdirectedRequestError < HTTPError; end
|
|
57
107
|
end
|
data/lib/httpx/extensions.rb
CHANGED
|
@@ -3,132 +3,40 @@
|
|
|
3
3
|
require "uri"
|
|
4
4
|
|
|
5
5
|
module HTTPX
|
|
6
|
-
unless Method.method_defined?(:curry)
|
|
7
|
-
|
|
8
|
-
# Backport
|
|
9
|
-
#
|
|
10
|
-
# Ruby 2.1 and lower implement curry only for Procs.
|
|
11
|
-
#
|
|
12
|
-
# Why not using Refinements? Because they don't work for Method (tested with ruby 2.1.9).
|
|
13
|
-
#
|
|
14
|
-
module CurryMethods
|
|
15
|
-
# Backport for the Method#curry method, which is part of ruby core since 2.2 .
|
|
16
|
-
#
|
|
17
|
-
def curry(*args)
|
|
18
|
-
to_proc.curry(*args)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
Method.__send__(:include, CurryMethods)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
unless String.method_defined?(:+@)
|
|
25
|
-
# Backport for +"", to initialize unfrozen strings from the string literal.
|
|
26
|
-
#
|
|
27
|
-
module LiteralStringExtensions
|
|
28
|
-
def +@
|
|
29
|
-
frozen? ? dup : self
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
String.__send__(:include, LiteralStringExtensions)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
unless Numeric.method_defined?(:positive?)
|
|
36
|
-
# Ruby 2.3 Backport (Numeric#positive?)
|
|
37
|
-
#
|
|
38
|
-
module PosMethods
|
|
39
|
-
def positive?
|
|
40
|
-
self > 0
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
Numeric.__send__(:include, PosMethods)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
unless Numeric.method_defined?(:negative?)
|
|
47
|
-
# Ruby 2.3 Backport (Numeric#negative?)
|
|
48
|
-
#
|
|
49
|
-
module NegMethods
|
|
50
|
-
def negative?
|
|
51
|
-
self < 0
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
Numeric.__send__(:include, NegMethods)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
module StringExtensions
|
|
58
|
-
refine String do
|
|
59
|
-
def delete_suffix!(suffix)
|
|
60
|
-
suffix = Backports.coerce_to_str(suffix)
|
|
61
|
-
chomp! if frozen?
|
|
62
|
-
len = suffix.length
|
|
63
|
-
if len > 0 && index(suffix, -len)
|
|
64
|
-
self[-len..-1] = ''
|
|
65
|
-
self
|
|
66
|
-
else
|
|
67
|
-
nil
|
|
68
|
-
end
|
|
69
|
-
end unless String.method_defined?(:delete_suffix!)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
module HashExtensions
|
|
74
|
-
refine Hash do
|
|
75
|
-
def compact
|
|
76
|
-
h = {}
|
|
77
|
-
each do |key, value|
|
|
78
|
-
h[key] = value unless value == nil
|
|
79
|
-
end
|
|
80
|
-
h
|
|
81
|
-
end unless Hash.method_defined?(:compact)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
6
|
module ArrayExtensions
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
7
|
+
module FilterMap
|
|
8
|
+
refine Array do
|
|
9
|
+
# Ruby 2.7 backport
|
|
10
|
+
def filter_map
|
|
11
|
+
return to_enum(:filter_map) unless block_given?
|
|
12
|
+
|
|
13
|
+
each_with_object([]) do |item, res|
|
|
14
|
+
processed = yield(item)
|
|
15
|
+
res << processed if processed
|
|
16
|
+
end
|
|
94
17
|
end
|
|
95
18
|
end unless Array.method_defined?(:filter_map)
|
|
96
|
-
|
|
97
|
-
def sum(accumulator = 0, &block)
|
|
98
|
-
values = block_given? ? map(&block) : self
|
|
99
|
-
values.inject(accumulator, :+)
|
|
100
|
-
end unless Array.method_defined?(:sum)
|
|
101
19
|
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
module IOExtensions
|
|
105
|
-
refine IO do
|
|
106
|
-
# provides a fallback for rubies where IO#wait isn't implemented,
|
|
107
|
-
# but IO#wait_readable and IO#wait_writable are.
|
|
108
|
-
def wait(timeout = nil, _mode = :read_write)
|
|
109
|
-
r, w = IO.select([self], [self], nil, timeout)
|
|
110
|
-
|
|
111
|
-
return unless r || w
|
|
112
|
-
|
|
113
|
-
self
|
|
114
|
-
end unless IO.method_defined?(:wait) && IO.instance_method(:wait).arity == 2
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
20
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
21
|
+
module Intersect
|
|
22
|
+
refine Array do
|
|
23
|
+
# Ruby 3.1 backport
|
|
24
|
+
def intersect?(arr)
|
|
25
|
+
if size < arr.size
|
|
26
|
+
smaller = self
|
|
27
|
+
else
|
|
28
|
+
smaller, arr = arr, self
|
|
29
|
+
end
|
|
30
|
+
(arr & smaller).size > 0
|
|
31
|
+
end
|
|
32
|
+
end unless Array.method_defined?(:intersect?)
|
|
127
33
|
end
|
|
128
34
|
end
|
|
129
35
|
|
|
130
36
|
module URIExtensions
|
|
37
|
+
# uri 0.11 backport, ships with ruby 3.1
|
|
131
38
|
refine URI::Generic do
|
|
39
|
+
|
|
132
40
|
def non_ascii_hostname
|
|
133
41
|
@non_ascii_hostname
|
|
134
42
|
end
|
|
@@ -146,21 +54,6 @@ module HTTPX
|
|
|
146
54
|
def origin
|
|
147
55
|
"#{scheme}://#{authority}"
|
|
148
56
|
end unless URI::HTTP.method_defined?(:origin)
|
|
149
|
-
|
|
150
|
-
def altsvc_match?(uri)
|
|
151
|
-
uri = URI.parse(uri)
|
|
152
|
-
|
|
153
|
-
origin == uri.origin || begin
|
|
154
|
-
case scheme
|
|
155
|
-
when "h2"
|
|
156
|
-
(uri.scheme == "https" || uri.scheme == "h2") &&
|
|
157
|
-
host == uri.host &&
|
|
158
|
-
(port || default_port) == (uri.port || uri.default_port)
|
|
159
|
-
else
|
|
160
|
-
false
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
57
|
end
|
|
165
58
|
end
|
|
166
59
|
end
|
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -4,26 +4,48 @@ require "openssl"
|
|
|
4
4
|
|
|
5
5
|
module HTTPX
|
|
6
6
|
TLSError = OpenSSL::SSL::SSLError
|
|
7
|
-
IPRegex = Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex)
|
|
8
7
|
|
|
9
8
|
class SSL < TCP
|
|
10
|
-
|
|
9
|
+
# rubocop:disable Style/MutableConstant
|
|
10
|
+
TLS_OPTIONS = { alpn_protocols: %w[h2 http/1.1].freeze }
|
|
11
|
+
# https://github.com/jruby/jruby-openssl/issues/284
|
|
12
|
+
TLS_OPTIONS[:verify_hostname] = true if RUBY_ENGINE == "jruby"
|
|
13
|
+
# rubocop:enable Style/MutableConstant
|
|
14
|
+
TLS_OPTIONS.freeze
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
{ alpn_protocols: %w[h2 http/1.1].freeze }.freeze
|
|
14
|
-
else
|
|
15
|
-
{}.freeze
|
|
16
|
-
end
|
|
16
|
+
attr_writer :ssl_session
|
|
17
17
|
|
|
18
18
|
def initialize(_, _, options)
|
|
19
19
|
super
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
ctx_options = TLS_OPTIONS.merge(options.ssl)
|
|
22
22
|
@sni_hostname = ctx_options.delete(:hostname) || @hostname
|
|
23
|
-
@ctx.set_params(ctx_options) unless ctx_options.empty?
|
|
24
|
-
@state = :negotiated if @keep_open
|
|
25
23
|
|
|
26
|
-
@
|
|
24
|
+
if @keep_open && @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
25
|
+
# externally initiated ssl socket
|
|
26
|
+
@ctx = @io.context
|
|
27
|
+
@state = :negotiated
|
|
28
|
+
else
|
|
29
|
+
@ctx = OpenSSL::SSL::SSLContext.new
|
|
30
|
+
@ctx.set_params(ctx_options) unless ctx_options.empty?
|
|
31
|
+
unless @ctx.session_cache_mode.nil? # a dummy method on JRuby
|
|
32
|
+
@ctx.session_cache_mode =
|
|
33
|
+
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
yield(self) if block_given?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@verify_hostname = @ctx.verify_hostname
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
if OpenSSL::SSL::SSLContext.method_defined?(:session_new_cb=)
|
|
43
|
+
def session_new_cb(&pr)
|
|
44
|
+
@ctx.session_new_cb = proc { |_, sess| pr.call(sess) }
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
# session_new_cb not implemented under JRuby
|
|
48
|
+
def session_new_cb; end
|
|
27
49
|
end
|
|
28
50
|
|
|
29
51
|
def protocol
|
|
@@ -32,6 +54,20 @@ module HTTPX
|
|
|
32
54
|
super
|
|
33
55
|
end
|
|
34
56
|
|
|
57
|
+
if RUBY_ENGINE == "jruby"
|
|
58
|
+
# in jruby, alpn_protocol may return ""
|
|
59
|
+
# https://github.com/jruby/jruby-openssl/issues/287
|
|
60
|
+
def protocol
|
|
61
|
+
proto = @io.alpn_protocol
|
|
62
|
+
|
|
63
|
+
return super if proto.nil? || proto.empty?
|
|
64
|
+
|
|
65
|
+
proto
|
|
66
|
+
rescue StandardError
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
35
71
|
def can_verify_peer?
|
|
36
72
|
@ctx.verify_mode == OpenSSL::SSL::VERIFY_PEER
|
|
37
73
|
end
|
|
@@ -43,85 +79,54 @@ module HTTPX
|
|
|
43
79
|
OpenSSL::SSL.verify_certificate_identity(@io.peer_cert, host)
|
|
44
80
|
end
|
|
45
81
|
|
|
46
|
-
def close
|
|
47
|
-
super
|
|
48
|
-
# allow reconnections
|
|
49
|
-
# connect only works if initial @io is a socket
|
|
50
|
-
@io = @io.io if @io.respond_to?(:io)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
82
|
def connected?
|
|
54
83
|
@state == :negotiated
|
|
55
84
|
end
|
|
56
85
|
|
|
86
|
+
def expired?
|
|
87
|
+
super || ssl_session_expired?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def ssl_session_expired?
|
|
91
|
+
@ssl_session.nil? || Process.clock_gettime(Process::CLOCK_REALTIME) >= (@ssl_session.time.to_f + @ssl_session.timeout)
|
|
92
|
+
end
|
|
93
|
+
|
|
57
94
|
def connect
|
|
58
95
|
super
|
|
59
96
|
return if @state == :negotiated ||
|
|
60
97
|
@state != :connected
|
|
61
98
|
|
|
62
99
|
unless @io.is_a?(OpenSSL::SSL::SSLSocket)
|
|
100
|
+
if (hostname_is_ip = (@ip == @sni_hostname))
|
|
101
|
+
# IPv6 address would be "[::1]", must turn to "0000:0000:0000:0000:0000:0000:0000:0001" for cert SAN check
|
|
102
|
+
@sni_hostname = @ip.to_string
|
|
103
|
+
# IP addresses in SNI is not valid per RFC 6066, section 3.
|
|
104
|
+
@ctx.verify_hostname = false
|
|
105
|
+
end
|
|
106
|
+
|
|
63
107
|
@io = OpenSSL::SSL::SSLSocket.new(@io, @ctx)
|
|
64
|
-
|
|
108
|
+
|
|
109
|
+
@io.hostname = @sni_hostname unless hostname_is_ip
|
|
110
|
+
@io.session = @ssl_session unless ssl_session_expired?
|
|
65
111
|
@io.sync_close = true
|
|
66
112
|
end
|
|
67
113
|
try_ssl_connect
|
|
68
114
|
end
|
|
69
115
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
transition(:negotiated)
|
|
76
|
-
@interests = :w
|
|
77
|
-
rescue ::IO::WaitReadable
|
|
116
|
+
def try_ssl_connect
|
|
117
|
+
ret = @io.connect_nonblock(exception: false)
|
|
118
|
+
log(level: 3, color: :cyan) { "TLS CONNECT: #{ret}..." }
|
|
119
|
+
case ret
|
|
120
|
+
when :wait_readable
|
|
78
121
|
@interests = :r
|
|
79
|
-
|
|
122
|
+
return
|
|
123
|
+
when :wait_writable
|
|
80
124
|
@interests = :w
|
|
125
|
+
return
|
|
81
126
|
end
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
rescue ::IO::WaitWritable
|
|
86
|
-
buffer.clear
|
|
87
|
-
0
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def write(*)
|
|
91
|
-
super
|
|
92
|
-
rescue ::IO::WaitReadable
|
|
93
|
-
0
|
|
94
|
-
end
|
|
95
|
-
# :nocov:
|
|
96
|
-
else
|
|
97
|
-
def try_ssl_connect
|
|
98
|
-
case @io.connect_nonblock(exception: false)
|
|
99
|
-
when :wait_readable
|
|
100
|
-
@interests = :r
|
|
101
|
-
return
|
|
102
|
-
when :wait_writable
|
|
103
|
-
@interests = :w
|
|
104
|
-
return
|
|
105
|
-
end
|
|
106
|
-
@io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && !@hostname_is_ip
|
|
107
|
-
transition(:negotiated)
|
|
108
|
-
@interests = :w
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# :nocov:
|
|
112
|
-
if OpenSSL::VERSION < "2.0.6"
|
|
113
|
-
def read(size, buffer)
|
|
114
|
-
@io.read_nonblock(size, buffer)
|
|
115
|
-
buffer.bytesize
|
|
116
|
-
rescue ::IO::WaitReadable,
|
|
117
|
-
::IO::WaitWritable
|
|
118
|
-
buffer.clear
|
|
119
|
-
0
|
|
120
|
-
rescue EOFError
|
|
121
|
-
nil
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
# :nocov:
|
|
127
|
+
@io.post_connection_check(@sni_hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE && @verify_hostname
|
|
128
|
+
transition(:negotiated)
|
|
129
|
+
@interests = :w
|
|
125
130
|
end
|
|
126
131
|
|
|
127
132
|
private
|
|
@@ -130,6 +135,7 @@ module HTTPX
|
|
|
130
135
|
case nextstate
|
|
131
136
|
when :negotiated
|
|
132
137
|
return unless @state == :connected
|
|
138
|
+
|
|
133
139
|
when :closed
|
|
134
140
|
return unless @state == :negotiated ||
|
|
135
141
|
@state == :connected
|
|
@@ -145,12 +151,12 @@ module HTTPX
|
|
|
145
151
|
"#{super}\n\n" \
|
|
146
152
|
"SSL connection using #{@io.ssl_version} / #{Array(@io.cipher).first}\n" \
|
|
147
153
|
"ALPN, server accepted to use #{protocol}\n" \
|
|
148
|
-
"Server certificate:\n" \
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
+
"Server certificate:\n " \
|
|
155
|
+
"subject: #{server_cert.subject}\n " \
|
|
156
|
+
"start date: #{server_cert.not_before}\n " \
|
|
157
|
+
"expire date: #{server_cert.not_after}\n " \
|
|
158
|
+
"issuer: #{server_cert.issuer}\n " \
|
|
159
|
+
"SSL certificate verify ok."
|
|
154
160
|
end
|
|
155
161
|
end
|
|
156
162
|
end
|