httpx 0.20.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|