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
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,13 +29,19 @@ 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.
|
|
27
39
|
class RequestTimeoutError < TimeoutError
|
|
40
|
+
# The HTTPX::Request request object this exception refers to.
|
|
28
41
|
attr_reader :request
|
|
29
42
|
|
|
43
|
+
# initializes the exception with the +request+ and +response+ it refers to, and the
|
|
44
|
+
# +timeout+ causing the error, and the
|
|
30
45
|
def initialize(request, response, timeout)
|
|
31
46
|
@request = request
|
|
32
47
|
@response = response
|
|
@@ -38,19 +53,28 @@ module HTTPX
|
|
|
38
53
|
end
|
|
39
54
|
end
|
|
40
55
|
|
|
56
|
+
# Error raised when there was a timeout while receiving a response from the server.
|
|
41
57
|
class ReadTimeoutError < RequestTimeoutError; end
|
|
42
58
|
|
|
59
|
+
# Error raised when there was a timeout while sending a request from the server.
|
|
43
60
|
class WriteTimeoutError < RequestTimeoutError; end
|
|
44
61
|
|
|
62
|
+
# Error raised when there was a timeout while waiting for the HTTP/2 settings frame from the server.
|
|
45
63
|
class SettingsTimeoutError < TimeoutError; end
|
|
46
64
|
|
|
65
|
+
# Error raised when there was a timeout while resolving a domain to an IP.
|
|
47
66
|
class ResolveTimeoutError < TimeoutError; end
|
|
48
67
|
|
|
68
|
+
# Error raised when there was an error while resolving a domain to an IP.
|
|
49
69
|
class ResolveError < Error; end
|
|
50
70
|
|
|
71
|
+
# Error raised when there was an error while resolving a domain to an IP
|
|
72
|
+
# using a HTTPX::Resolver::Native resolver.
|
|
51
73
|
class NativeResolveError < ResolveError
|
|
52
74
|
attr_reader :connection, :host
|
|
53
75
|
|
|
76
|
+
# initializes the exception with the +connection+ it refers to, the +host+ domain
|
|
77
|
+
# which failed to resolve, and the error +message+.
|
|
54
78
|
def initialize(connection, host, message = "Can't resolve #{host}")
|
|
55
79
|
@connection = connection
|
|
56
80
|
@host = host
|
|
@@ -58,18 +82,26 @@ module HTTPX
|
|
|
58
82
|
end
|
|
59
83
|
end
|
|
60
84
|
|
|
85
|
+
# The exception class for HTTP responses with 4xx or 5xx status.
|
|
61
86
|
class HTTPError < Error
|
|
87
|
+
# The HTTPX::Response response object this exception refers to.
|
|
62
88
|
attr_reader :response
|
|
63
89
|
|
|
90
|
+
# Creates the instance and assigns the HTTPX::Response +response+.
|
|
64
91
|
def initialize(response)
|
|
65
92
|
@response = response
|
|
66
93
|
super("HTTP Error: #{@response.status} #{@response.headers}\n#{@response.body}")
|
|
67
94
|
end
|
|
68
95
|
|
|
96
|
+
# The HTTP response status.
|
|
97
|
+
#
|
|
98
|
+
# error.status #=> 404
|
|
69
99
|
def status
|
|
70
100
|
@response.status
|
|
71
101
|
end
|
|
72
102
|
end
|
|
73
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.
|
|
74
106
|
class MisdirectedRequestError < HTTPError; end
|
|
75
107
|
end
|
data/lib/httpx/extensions.rb
CHANGED
|
@@ -3,97 +3,10 @@
|
|
|
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 NumericExtensions
|
|
58
|
-
refine Numeric do
|
|
59
|
-
def infinite?
|
|
60
|
-
self == Float::INFINITY
|
|
61
|
-
end unless Numeric.method_defined?(:infinite?)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
module StringExtensions
|
|
66
|
-
refine String do
|
|
67
|
-
def delete_suffix!(suffix)
|
|
68
|
-
suffix = Backports.coerce_to_str(suffix)
|
|
69
|
-
chomp! if frozen?
|
|
70
|
-
len = suffix.length
|
|
71
|
-
if len > 0 && index(suffix, -len)
|
|
72
|
-
self[-len..-1] = ''
|
|
73
|
-
self
|
|
74
|
-
else
|
|
75
|
-
nil
|
|
76
|
-
end
|
|
77
|
-
end unless String.method_defined?(:delete_suffix!)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
module HashExtensions
|
|
82
|
-
refine Hash do
|
|
83
|
-
def compact
|
|
84
|
-
h = {}
|
|
85
|
-
each do |key, value|
|
|
86
|
-
h[key] = value unless value == nil
|
|
87
|
-
end
|
|
88
|
-
h
|
|
89
|
-
end unless Hash.method_defined?(:compact)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
6
|
module ArrayExtensions
|
|
94
7
|
module FilterMap
|
|
95
8
|
refine Array do
|
|
96
|
-
|
|
9
|
+
# Ruby 2.7 backport
|
|
97
10
|
def filter_map
|
|
98
11
|
return to_enum(:filter_map) unless block_given?
|
|
99
12
|
|
|
@@ -105,17 +18,9 @@ module HTTPX
|
|
|
105
18
|
end unless Array.method_defined?(:filter_map)
|
|
106
19
|
end
|
|
107
20
|
|
|
108
|
-
module Sum
|
|
109
|
-
refine Array do
|
|
110
|
-
def sum(accumulator = 0, &block)
|
|
111
|
-
values = block_given? ? map(&block) : self
|
|
112
|
-
values.inject(accumulator, :+)
|
|
113
|
-
end
|
|
114
|
-
end unless Array.method_defined?(:sum)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
21
|
module Intersect
|
|
118
22
|
refine Array do
|
|
23
|
+
# Ruby 3.1 backport
|
|
119
24
|
def intersect?(arr)
|
|
120
25
|
if size < arr.size
|
|
121
26
|
smaller = self
|
|
@@ -128,30 +33,10 @@ module HTTPX
|
|
|
128
33
|
end
|
|
129
34
|
end
|
|
130
35
|
|
|
131
|
-
module IOExtensions
|
|
132
|
-
refine IO do
|
|
133
|
-
# provides a fallback for rubies where IO#wait isn't implemented,
|
|
134
|
-
# but IO#wait_readable and IO#wait_writable are.
|
|
135
|
-
def wait(timeout = nil, _mode = :read_write)
|
|
136
|
-
r, w = IO.select([self], [self], nil, timeout)
|
|
137
|
-
|
|
138
|
-
return unless r || w
|
|
139
|
-
|
|
140
|
-
self
|
|
141
|
-
end unless IO.method_defined?(:wait) && IO.instance_method(:wait).arity == 2
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
module RegexpExtensions
|
|
146
|
-
refine(Regexp) do
|
|
147
|
-
def match?(*args)
|
|
148
|
-
!match(*args).nil?
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
36
|
module URIExtensions
|
|
37
|
+
# uri 0.11 backport, ships with ruby 3.1
|
|
154
38
|
refine URI::Generic do
|
|
39
|
+
|
|
155
40
|
def non_ascii_hostname
|
|
156
41
|
@non_ascii_hostname
|
|
157
42
|
end
|
|
@@ -169,21 +54,6 @@ module HTTPX
|
|
|
169
54
|
def origin
|
|
170
55
|
"#{scheme}://#{authority}"
|
|
171
56
|
end unless URI::HTTP.method_defined?(:origin)
|
|
172
|
-
|
|
173
|
-
def altsvc_match?(uri)
|
|
174
|
-
uri = URI.parse(uri)
|
|
175
|
-
|
|
176
|
-
origin == uri.origin || begin
|
|
177
|
-
case scheme
|
|
178
|
-
when "h2"
|
|
179
|
-
(uri.scheme == "https" || uri.scheme == "h2") &&
|
|
180
|
-
host == uri.host &&
|
|
181
|
-
(port || default_port) == (uri.port || uri.default_port)
|
|
182
|
-
else
|
|
183
|
-
false
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
57
|
end
|
|
188
58
|
end
|
|
189
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
|
data/lib/httpx/io/tcp.rb
CHANGED
|
@@ -38,7 +38,10 @@ module HTTPX
|
|
|
38
38
|
add_addresses(addresses)
|
|
39
39
|
end
|
|
40
40
|
@ip_index = @addresses.size - 1
|
|
41
|
-
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def socket
|
|
44
|
+
@io
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def add_addresses(addrs)
|
|
@@ -74,7 +77,8 @@ module HTTPX
|
|
|
74
77
|
try_connect
|
|
75
78
|
rescue Errno::ECONNREFUSED,
|
|
76
79
|
Errno::EADDRNOTAVAIL,
|
|
77
|
-
Errno::EHOSTUNREACH
|
|
80
|
+
Errno::EHOSTUNREACH,
|
|
81
|
+
SocketError => e
|
|
78
82
|
raise e if @ip_index <= 0
|
|
79
83
|
|
|
80
84
|
log { "failed connecting to #{@ip} (#{e.message}), trying next..." }
|
|
@@ -90,84 +94,45 @@ module HTTPX
|
|
|
90
94
|
retry
|
|
91
95
|
end
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@interests = :w
|
|
99
|
-
rescue ::IO::WaitReadable
|
|
97
|
+
def try_connect
|
|
98
|
+
ret = @io.connect_nonblock(Socket.sockaddr_in(@port, @ip.to_s), exception: false)
|
|
99
|
+
log(level: 3, color: :cyan) { "TCP CONNECT: #{ret}..." }
|
|
100
|
+
case ret
|
|
101
|
+
when :wait_readable
|
|
100
102
|
@interests = :r
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@interests = :w
|
|
104
|
-
else
|
|
105
|
-
transition(:connected)
|
|
103
|
+
return
|
|
104
|
+
when :wait_writable
|
|
106
105
|
@interests = :w
|
|
106
|
+
return
|
|
107
107
|
end
|
|
108
|
-
|
|
108
|
+
transition(:connected)
|
|
109
|
+
@interests = :w
|
|
110
|
+
rescue Errno::EALREADY
|
|
111
|
+
@interests = :w
|
|
112
|
+
end
|
|
113
|
+
private :try_connect
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
buffer.bytesize
|
|
114
|
-
rescue ::IO::WaitReadable
|
|
115
|
+
def read(size, buffer)
|
|
116
|
+
ret = @io.read_nonblock(size, buffer, exception: false)
|
|
117
|
+
if ret == :wait_readable
|
|
115
118
|
buffer.clear
|
|
116
|
-
0
|
|
117
|
-
rescue EOFError
|
|
118
|
-
nil
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def write(buffer)
|
|
122
|
-
siz = @io.write_nonblock(buffer)
|
|
123
|
-
log { "WRITE: #{siz} bytes..." }
|
|
124
|
-
buffer.shift!(siz)
|
|
125
|
-
siz
|
|
126
|
-
rescue ::IO::WaitWritable
|
|
127
|
-
0
|
|
128
|
-
rescue EOFError
|
|
129
|
-
nil
|
|
130
|
-
end
|
|
131
|
-
# :nocov:
|
|
132
|
-
else
|
|
133
|
-
def try_connect
|
|
134
|
-
case @io.connect_nonblock(Socket.sockaddr_in(@port, @ip.to_s), exception: false)
|
|
135
|
-
when :wait_readable
|
|
136
|
-
@interests = :r
|
|
137
|
-
return
|
|
138
|
-
when :wait_writable
|
|
139
|
-
@interests = :w
|
|
140
|
-
return
|
|
141
|
-
end
|
|
142
|
-
transition(:connected)
|
|
143
|
-
@interests = :w
|
|
144
|
-
rescue Errno::EALREADY
|
|
145
|
-
@interests = :w
|
|
119
|
+
return 0
|
|
146
120
|
end
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def read(size, buffer)
|
|
150
|
-
ret = @io.read_nonblock(size, buffer, exception: false)
|
|
151
|
-
if ret == :wait_readable
|
|
152
|
-
buffer.clear
|
|
153
|
-
return 0
|
|
154
|
-
end
|
|
155
|
-
return if ret.nil?
|
|
121
|
+
return if ret.nil?
|
|
156
122
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
123
|
+
log { "READ: #{buffer.bytesize} bytes..." }
|
|
124
|
+
buffer.bytesize
|
|
125
|
+
end
|
|
160
126
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
127
|
+
def write(buffer)
|
|
128
|
+
siz = @io.write_nonblock(buffer, exception: false)
|
|
129
|
+
return 0 if siz == :wait_writable
|
|
130
|
+
return if siz.nil?
|
|
165
131
|
|
|
166
|
-
|
|
132
|
+
log { "WRITE: #{siz} bytes..." }
|
|
167
133
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
end
|
|
134
|
+
buffer.shift!(siz)
|
|
135
|
+
siz
|
|
171
136
|
end
|
|
172
137
|
|
|
173
138
|
def close
|
|
@@ -188,6 +153,17 @@ module HTTPX
|
|
|
188
153
|
@state == :idle || @state == :closed
|
|
189
154
|
end
|
|
190
155
|
|
|
156
|
+
def expired?
|
|
157
|
+
# do not mess with external sockets
|
|
158
|
+
return false if @options.io
|
|
159
|
+
|
|
160
|
+
return true unless @addresses
|
|
161
|
+
|
|
162
|
+
resolver_addresses = Resolver.nolookup_resolve(@hostname)
|
|
163
|
+
|
|
164
|
+
(Array(resolver_addresses) & @addresses).empty?
|
|
165
|
+
end
|
|
166
|
+
|
|
191
167
|
# :nocov:
|
|
192
168
|
def inspect
|
|
193
169
|
"#<#{self.class}: #{@ip}:#{@port} (state: #{@state})>"
|