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
@@ -8,6 +8,8 @@ module HTTPX
|
|
8
8
|
include Callbacks
|
9
9
|
include Loggable
|
10
10
|
|
11
|
+
using ArrayExtensions::Intersect
|
12
|
+
|
11
13
|
RECORD_TYPES = {
|
12
14
|
Socket::AF_INET6 => Resolv::DNS::Resource::IN::AAAA,
|
13
15
|
Socket::AF_INET => Resolv::DNS::Resource::IN::A,
|
@@ -31,11 +33,13 @@ module HTTPX
|
|
31
33
|
def initialize(family, options)
|
32
34
|
@family = family
|
33
35
|
@record_type = RECORD_TYPES[family]
|
34
|
-
@options =
|
36
|
+
@options = options
|
35
37
|
end
|
36
38
|
|
37
39
|
def close; end
|
38
40
|
|
41
|
+
alias_method :terminate, :close
|
42
|
+
|
39
43
|
def closed?
|
40
44
|
true
|
41
45
|
end
|
@@ -44,11 +48,15 @@ module HTTPX
|
|
44
48
|
true
|
45
49
|
end
|
46
50
|
|
47
|
-
def emit_addresses(connection, family, addresses)
|
51
|
+
def emit_addresses(connection, family, addresses, early_resolve = false)
|
48
52
|
addresses.map! do |address|
|
49
53
|
address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
|
50
54
|
end
|
51
|
-
|
55
|
+
|
56
|
+
# double emission check, but allow early resolution to work
|
57
|
+
return if !early_resolve && connection.addresses && !addresses.intersect?(connection.addresses)
|
58
|
+
|
59
|
+
log { "resolver: answer #{FAMILY_TYPES[RECORD_TYPES[family]]} #{connection.origin.host}: #{addresses.inspect}" }
|
52
60
|
if @pool && # if triggered by early resolve, pool may not be here yet
|
53
61
|
!connection.io &&
|
54
62
|
connection.options.ip_families.size > 1 &&
|
@@ -56,17 +64,34 @@ module HTTPX
|
|
56
64
|
addresses.first.to_s != connection.origin.host.to_s
|
57
65
|
log { "resolver: A response, applying resolution delay..." }
|
58
66
|
@pool.after(0.05) do
|
59
|
-
connection.
|
60
|
-
|
67
|
+
unless connection.state == :closed ||
|
68
|
+
# double emission check
|
69
|
+
(connection.addresses && addresses.intersect?(connection.addresses))
|
70
|
+
emit_resolved_connection(connection, addresses, early_resolve)
|
71
|
+
end
|
61
72
|
end
|
62
73
|
else
|
63
|
-
connection
|
64
|
-
emit(:resolve, connection)
|
74
|
+
emit_resolved_connection(connection, addresses, early_resolve)
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
68
78
|
private
|
69
79
|
|
80
|
+
def emit_resolved_connection(connection, addresses, early_resolve)
|
81
|
+
begin
|
82
|
+
connection.addresses = addresses
|
83
|
+
|
84
|
+
emit(:resolve, connection)
|
85
|
+
rescue StandardError => e
|
86
|
+
if early_resolve
|
87
|
+
connection.force_reset
|
88
|
+
throw(:resolve_error, e)
|
89
|
+
else
|
90
|
+
emit(:error, connection, e)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
70
95
|
def early_resolve(connection, hostname: connection.origin.host)
|
71
96
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
72
97
|
|
@@ -76,7 +101,7 @@ module HTTPX
|
|
76
101
|
|
77
102
|
return if addresses.empty?
|
78
103
|
|
79
|
-
emit_addresses(connection, @family, addresses)
|
104
|
+
emit_addresses(connection, @family, addresses, true)
|
80
105
|
end
|
81
106
|
|
82
107
|
def emit_resolve_error(connection, hostname = connection.origin.host, ex = nil)
|
@@ -84,7 +109,7 @@ module HTTPX
|
|
84
109
|
end
|
85
110
|
|
86
111
|
def resolve_error(hostname, ex = nil)
|
87
|
-
return ex if ex.is_a?(ResolveError)
|
112
|
+
return ex if ex.is_a?(ResolveError) || ex.is_a?(ResolveTimeoutError)
|
88
113
|
|
89
114
|
message = ex ? ex.message : "Can't resolve #{hostname}"
|
90
115
|
error = ResolveError.new(message)
|
@@ -92,6 +92,12 @@ module HTTPX
|
|
92
92
|
resolve
|
93
93
|
end
|
94
94
|
|
95
|
+
def handle_socket_timeout(interval)
|
96
|
+
error = HTTPX::ResolveTimeoutError.new(interval, "timed out while waiting on select")
|
97
|
+
error.set_backtrace(caller)
|
98
|
+
on_error(error)
|
99
|
+
end
|
100
|
+
|
95
101
|
private
|
96
102
|
|
97
103
|
def transition(nextstate)
|
@@ -121,7 +127,7 @@ module HTTPX
|
|
121
127
|
@queries.delete(pair)
|
122
128
|
|
123
129
|
family, connection = pair
|
124
|
-
emit_addresses(connection, family, addrs)
|
130
|
+
catch(:coalesced) { emit_addresses(connection, family, addrs) }
|
125
131
|
when ERROR
|
126
132
|
*pair, error = @pipe_mutex.synchronize { @ips.pop }
|
127
133
|
@queries.delete(pair)
|
@@ -158,12 +164,14 @@ module HTTPX
|
|
158
164
|
def async_resolve(connection, hostname, scheme)
|
159
165
|
families = connection.options.ip_families
|
160
166
|
log { "resolver: query for #{hostname}" }
|
161
|
-
|
167
|
+
timeouts = @timeouts[connection.origin.host]
|
168
|
+
resolve_timeout = timeouts.first
|
162
169
|
|
163
170
|
Thread.start do
|
164
171
|
Thread.current.report_on_exception = false
|
165
172
|
begin
|
166
173
|
addrs = if resolve_timeout
|
174
|
+
|
167
175
|
Timeout.timeout(resolve_timeout) do
|
168
176
|
__addrinfo_resolve(hostname, scheme)
|
169
177
|
end
|
@@ -182,16 +190,13 @@ module HTTPX
|
|
182
190
|
@pipe_write.putc(DONE) unless @pipe_write.closed?
|
183
191
|
end
|
184
192
|
end
|
185
|
-
rescue Timeout::Error => e
|
186
|
-
ex = ResolveTimeoutError.new(resolve_timeout, e.message)
|
187
|
-
ex.set_backtrace(ex.backtrace)
|
188
|
-
@pipe_mutex.synchronize do
|
189
|
-
families.each do |family|
|
190
|
-
@ips.unshift([family, connection, ex])
|
191
|
-
@pipe_write.putc(ERROR) unless @pipe_write.closed?
|
192
|
-
end
|
193
|
-
end
|
194
193
|
rescue StandardError => e
|
194
|
+
if e.is_a?(Timeout::Error)
|
195
|
+
timeouts.shift
|
196
|
+
retry unless timeouts.empty?
|
197
|
+
e = ResolveTimeoutError.new(resolve_timeout, e.message)
|
198
|
+
e.set_backtrace(e.backtrace)
|
199
|
+
end
|
195
200
|
@pipe_mutex.synchronize do
|
196
201
|
families.each do |family|
|
197
202
|
@ips.unshift([family, connection, e])
|
data/lib/httpx/resolver.rb
CHANGED
@@ -5,9 +5,7 @@ require "ipaddr"
|
|
5
5
|
|
6
6
|
module HTTPX
|
7
7
|
module Resolver
|
8
|
-
|
9
|
-
|
10
|
-
RESOLVE_TIMEOUT = 5
|
8
|
+
RESOLVE_TIMEOUT = [2, 3].freeze
|
11
9
|
|
12
10
|
require "httpx/resolver/resolver"
|
13
11
|
require "httpx/resolver/system"
|
@@ -15,19 +13,27 @@ module HTTPX
|
|
15
13
|
require "httpx/resolver/https"
|
16
14
|
require "httpx/resolver/multi"
|
17
15
|
|
18
|
-
|
19
|
-
register :native, Native
|
20
|
-
register :https, HTTPS
|
21
|
-
|
22
|
-
@lookup_mutex = Mutex.new
|
16
|
+
@lookup_mutex = Thread::Mutex.new
|
23
17
|
@lookups = Hash.new { |h, k| h[k] = [] }
|
24
18
|
|
25
|
-
@identifier_mutex = Mutex.new
|
19
|
+
@identifier_mutex = Thread::Mutex.new
|
26
20
|
@identifier = 1
|
27
21
|
@system_resolver = Resolv::Hosts.new
|
28
22
|
|
29
23
|
module_function
|
30
24
|
|
25
|
+
def resolver_for(resolver_type)
|
26
|
+
case resolver_type
|
27
|
+
when :native then Native
|
28
|
+
when :system then System
|
29
|
+
when :https then HTTPS
|
30
|
+
else
|
31
|
+
return resolver_type if resolver_type.is_a?(Class) && resolver_type < Resolver
|
32
|
+
|
33
|
+
raise Error, "unsupported resolver type (#{resolver_type})"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
31
37
|
def nolookup_resolve(hostname)
|
32
38
|
ip_resolve(hostname) || cached_lookup(hostname) || system_resolve(hostname)
|
33
39
|
end
|
@@ -81,16 +87,18 @@ module HTTPX
|
|
81
87
|
def lookup(hostname, ttl)
|
82
88
|
return unless @lookups.key?(hostname)
|
83
89
|
|
84
|
-
@lookups[hostname] = @lookups[hostname].select do |address|
|
90
|
+
entries = @lookups[hostname] = @lookups[hostname].select do |address|
|
85
91
|
address["TTL"] > ttl
|
86
92
|
end
|
87
|
-
|
93
|
+
|
94
|
+
ips = entries.flat_map do |address|
|
88
95
|
if address.key?("alias")
|
89
96
|
lookup(address["alias"], ttl)
|
90
97
|
else
|
91
98
|
IPAddr.new(address["data"])
|
92
99
|
end
|
93
|
-
end
|
100
|
+
end.compact
|
101
|
+
|
94
102
|
ips unless ips.empty?
|
95
103
|
end
|
96
104
|
|
@@ -98,17 +106,30 @@ module HTTPX
|
|
98
106
|
@identifier_mutex.synchronize { @identifier = (@identifier + 1) & 0xFFFF }
|
99
107
|
end
|
100
108
|
|
101
|
-
def encode_dns_query(hostname, type: Resolv::DNS::Resource::IN::A)
|
109
|
+
def encode_dns_query(hostname, type: Resolv::DNS::Resource::IN::A, message_id: generate_id)
|
102
110
|
Resolv::DNS::Message.new.tap do |query|
|
103
|
-
query.id =
|
111
|
+
query.id = message_id
|
104
112
|
query.rd = 1
|
105
113
|
query.add_question(hostname, type)
|
106
114
|
end.encode
|
107
115
|
end
|
108
116
|
|
109
117
|
def decode_dns_answer(payload)
|
110
|
-
|
118
|
+
begin
|
119
|
+
message = Resolv::DNS::Message.decode(payload)
|
120
|
+
rescue Resolv::DNS::DecodeError => e
|
121
|
+
return :decode_error, e
|
122
|
+
end
|
123
|
+
|
124
|
+
# no domain was found
|
125
|
+
return :no_domain_found if message.rcode == Resolv::DNS::RCode::NXDomain
|
126
|
+
|
127
|
+
return :message_truncated if message.tc == 1
|
128
|
+
|
129
|
+
return :dns_error, message.rcode if message.rcode != Resolv::DNS::RCode::NoError
|
130
|
+
|
111
131
|
addresses = []
|
132
|
+
|
112
133
|
message.each_answer do |question, _, value|
|
113
134
|
case value
|
114
135
|
when Resolv::DNS::Resource::IN::CNAME
|
@@ -126,7 +147,8 @@ module HTTPX
|
|
126
147
|
}
|
127
148
|
end
|
128
149
|
end
|
129
|
-
|
150
|
+
|
151
|
+
[:ok, addresses]
|
130
152
|
end
|
131
153
|
end
|
132
154
|
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
# Implementation of the HTTP Response body as a buffer which implements the IO writer protocol
|
5
|
+
# (for buffering the response payload), the IO reader protocol (for consuming the response payload),
|
6
|
+
# and can be iterated over (via #each, which yields the payload in chunks).
|
7
|
+
class Response::Body
|
8
|
+
# the payload encoding (i.e. "utf-8", "ASCII-8BIT")
|
9
|
+
attr_reader :encoding
|
10
|
+
|
11
|
+
# Array of encodings contained in the response "content-encoding" header.
|
12
|
+
attr_reader :encodings
|
13
|
+
|
14
|
+
# initialized with the corresponding HTTPX::Response +response+ and HTTPX::Options +options+.
|
15
|
+
def initialize(response, options)
|
16
|
+
@response = response
|
17
|
+
@headers = response.headers
|
18
|
+
@options = options
|
19
|
+
@window_size = options.window_size
|
20
|
+
@encoding = response.content_type.charset || Encoding::BINARY
|
21
|
+
@encodings = []
|
22
|
+
@length = 0
|
23
|
+
@buffer = nil
|
24
|
+
@reader = nil
|
25
|
+
@state = :idle
|
26
|
+
initialize_inflaters
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize_dup(other)
|
30
|
+
super
|
31
|
+
|
32
|
+
@buffer = other.instance_variable_get(:@buffer).dup
|
33
|
+
end
|
34
|
+
|
35
|
+
def closed?
|
36
|
+
@state == :closed
|
37
|
+
end
|
38
|
+
|
39
|
+
# write the response payload +chunk+ into the buffer. Inflates the chunk when required
|
40
|
+
# and supported.
|
41
|
+
def write(chunk)
|
42
|
+
return if @state == :closed
|
43
|
+
|
44
|
+
return 0 if chunk.empty?
|
45
|
+
|
46
|
+
chunk = decode_chunk(chunk)
|
47
|
+
|
48
|
+
size = chunk.bytesize
|
49
|
+
@length += size
|
50
|
+
transition(:open)
|
51
|
+
@buffer.write(chunk)
|
52
|
+
|
53
|
+
@response.emit(:chunk_received, chunk)
|
54
|
+
size
|
55
|
+
end
|
56
|
+
|
57
|
+
# reads a chunk from the payload (implementation of the IO reader protocol).
|
58
|
+
def read(*args)
|
59
|
+
return unless @buffer
|
60
|
+
|
61
|
+
unless @reader
|
62
|
+
rewind
|
63
|
+
@reader = @buffer
|
64
|
+
end
|
65
|
+
|
66
|
+
@reader.read(*args)
|
67
|
+
end
|
68
|
+
|
69
|
+
# size of the decoded response payload. May differ from "content-length" header if
|
70
|
+
# response was encoded over-the-wire.
|
71
|
+
def bytesize
|
72
|
+
@length
|
73
|
+
end
|
74
|
+
|
75
|
+
# yields the payload in chunks.
|
76
|
+
def each
|
77
|
+
return enum_for(__method__) unless block_given?
|
78
|
+
|
79
|
+
begin
|
80
|
+
if @buffer
|
81
|
+
rewind
|
82
|
+
while (chunk = @buffer.read(@window_size))
|
83
|
+
yield(chunk.force_encoding(@encoding))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
ensure
|
87
|
+
close
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# returns the declared filename in the "contennt-disposition" header, when present.
|
92
|
+
def filename
|
93
|
+
return unless @headers.key?("content-disposition")
|
94
|
+
|
95
|
+
Utils.get_filename(@headers["content-disposition"])
|
96
|
+
end
|
97
|
+
|
98
|
+
# returns the full response payload as a string.
|
99
|
+
def to_s
|
100
|
+
return "".b unless @buffer
|
101
|
+
|
102
|
+
@buffer.to_s
|
103
|
+
end
|
104
|
+
|
105
|
+
alias_method :to_str, :to_s
|
106
|
+
|
107
|
+
# whether the payload is empty.
|
108
|
+
def empty?
|
109
|
+
@length.zero?
|
110
|
+
end
|
111
|
+
|
112
|
+
# copies the payload to +dest+.
|
113
|
+
#
|
114
|
+
# body.copy_to("path/to/file")
|
115
|
+
# body.copy_to(Pathname.new("path/to/file"))
|
116
|
+
# body.copy_to(File.new("path/to/file"))
|
117
|
+
def copy_to(dest)
|
118
|
+
return unless @buffer
|
119
|
+
|
120
|
+
rewind
|
121
|
+
|
122
|
+
if dest.respond_to?(:path) && @buffer.respond_to?(:path)
|
123
|
+
FileUtils.mv(@buffer.path, dest.path)
|
124
|
+
else
|
125
|
+
::IO.copy_stream(@buffer, dest)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# closes/cleans the buffer, resets everything
|
130
|
+
def close
|
131
|
+
if @buffer
|
132
|
+
@buffer.close
|
133
|
+
@buffer = nil
|
134
|
+
end
|
135
|
+
@length = 0
|
136
|
+
transition(:closed)
|
137
|
+
end
|
138
|
+
|
139
|
+
def ==(other)
|
140
|
+
object_id == other.object_id || begin
|
141
|
+
if other.respond_to?(:read)
|
142
|
+
_with_same_buffer_pos { FileUtils.compare_stream(@buffer, other) }
|
143
|
+
else
|
144
|
+
to_s == other.to_s
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# :nocov:
|
150
|
+
def inspect
|
151
|
+
"#<HTTPX::Response::Body:#{object_id} " \
|
152
|
+
"@state=#{@state} " \
|
153
|
+
"@length=#{@length}>"
|
154
|
+
end
|
155
|
+
# :nocov:
|
156
|
+
|
157
|
+
# rewinds the response payload buffer.
|
158
|
+
def rewind
|
159
|
+
return unless @buffer
|
160
|
+
|
161
|
+
# in case there's some reading going on
|
162
|
+
@reader = nil
|
163
|
+
|
164
|
+
@buffer.rewind
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
# prepares inflaters for the advertised encodings in "content-encoding" header.
|
170
|
+
def initialize_inflaters
|
171
|
+
@inflaters = nil
|
172
|
+
|
173
|
+
return unless @headers.key?("content-encoding")
|
174
|
+
|
175
|
+
return unless @options.decompress_response_body
|
176
|
+
|
177
|
+
@inflaters = @headers.get("content-encoding").filter_map do |encoding|
|
178
|
+
next if encoding == "identity"
|
179
|
+
|
180
|
+
inflater = self.class.initialize_inflater_by_encoding(encoding, @response)
|
181
|
+
|
182
|
+
# do not uncompress if there is no decoder available. In fact, we can't reliably
|
183
|
+
# continue decompressing beyond that, so ignore.
|
184
|
+
break unless inflater
|
185
|
+
|
186
|
+
@encodings << encoding
|
187
|
+
inflater
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# passes the +chunk+ through all inflaters to decode it.
|
192
|
+
def decode_chunk(chunk)
|
193
|
+
@inflaters.reverse_each do |inflater|
|
194
|
+
chunk = inflater.call(chunk)
|
195
|
+
end if @inflaters
|
196
|
+
|
197
|
+
chunk
|
198
|
+
end
|
199
|
+
|
200
|
+
# tries transitioning the body STM to the +nextstate+.
|
201
|
+
def transition(nextstate)
|
202
|
+
case nextstate
|
203
|
+
when :open
|
204
|
+
return unless @state == :idle
|
205
|
+
|
206
|
+
@buffer = Response::Buffer.new(
|
207
|
+
threshold_size: @options.body_threshold_size,
|
208
|
+
bytesize: @length,
|
209
|
+
encoding: @encoding
|
210
|
+
)
|
211
|
+
when :closed
|
212
|
+
return if @state == :closed
|
213
|
+
end
|
214
|
+
|
215
|
+
@state = nextstate
|
216
|
+
end
|
217
|
+
|
218
|
+
def _with_same_buffer_pos # :nodoc:
|
219
|
+
return yield unless @buffer && @buffer.respond_to?(:pos)
|
220
|
+
|
221
|
+
# @type ivar @buffer: StringIO | Tempfile
|
222
|
+
current_pos = @buffer.pos
|
223
|
+
@buffer.rewind
|
224
|
+
begin
|
225
|
+
yield
|
226
|
+
ensure
|
227
|
+
@buffer.pos = current_pos
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
class << self
|
232
|
+
def initialize_inflater_by_encoding(encoding, response, **kwargs) # :nodoc:
|
233
|
+
case encoding
|
234
|
+
when "gzip"
|
235
|
+
Transcoder::GZIP.decode(response, **kwargs)
|
236
|
+
when "deflate"
|
237
|
+
Transcoder::Deflate.decode(response, **kwargs)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
require "stringio"
|
5
|
+
require "tempfile"
|
6
|
+
|
7
|
+
module HTTPX
|
8
|
+
# wraps and delegates to an internal buffer, which can be a StringIO or a Tempfile.
|
9
|
+
class Response::Buffer < SimpleDelegator
|
10
|
+
# initializes buffer with the +threshold_size+ over which the payload gets buffer to a tempfile,
|
11
|
+
# the initial +bytesize+, and the +encoding+.
|
12
|
+
def initialize(threshold_size:, bytesize: 0, encoding: Encoding::BINARY)
|
13
|
+
@threshold_size = threshold_size
|
14
|
+
@bytesize = bytesize
|
15
|
+
@encoding = encoding
|
16
|
+
@buffer = StringIO.new("".b)
|
17
|
+
super(@buffer)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize_dup(other)
|
21
|
+
super
|
22
|
+
|
23
|
+
@buffer = other.instance_variable_get(:@buffer).dup
|
24
|
+
end
|
25
|
+
|
26
|
+
# size in bytes of the buffered content.
|
27
|
+
def size
|
28
|
+
@bytesize
|
29
|
+
end
|
30
|
+
|
31
|
+
# writes the +chunk+ into the buffer.
|
32
|
+
def write(chunk)
|
33
|
+
@bytesize += chunk.bytesize
|
34
|
+
try_upgrade_buffer
|
35
|
+
@buffer.write(chunk)
|
36
|
+
end
|
37
|
+
|
38
|
+
# returns the buffered content as a string.
|
39
|
+
def to_s
|
40
|
+
case @buffer
|
41
|
+
when StringIO
|
42
|
+
begin
|
43
|
+
@buffer.string.force_encoding(@encoding)
|
44
|
+
rescue ArgumentError
|
45
|
+
@buffer.string
|
46
|
+
end
|
47
|
+
when Tempfile
|
48
|
+
rewind
|
49
|
+
content = _with_same_buffer_pos { @buffer.read }
|
50
|
+
begin
|
51
|
+
content.force_encoding(@encoding)
|
52
|
+
rescue ArgumentError # ex: unknown encoding name - utf
|
53
|
+
content
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# closes the buffer.
|
59
|
+
def close
|
60
|
+
@buffer.close
|
61
|
+
@buffer.unlink if @buffer.respond_to?(:unlink)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# initializes the buffer into a StringIO, or turns it into a Tempfile when the threshold
|
67
|
+
# has been reached.
|
68
|
+
def try_upgrade_buffer
|
69
|
+
return unless @bytesize > @threshold_size
|
70
|
+
|
71
|
+
return if @buffer.is_a?(Tempfile)
|
72
|
+
|
73
|
+
aux = @buffer
|
74
|
+
|
75
|
+
@buffer = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
76
|
+
|
77
|
+
if aux
|
78
|
+
aux.rewind
|
79
|
+
::IO.copy_stream(aux, @buffer)
|
80
|
+
aux.close
|
81
|
+
end
|
82
|
+
|
83
|
+
__setobj__(@buffer)
|
84
|
+
end
|
85
|
+
|
86
|
+
def _with_same_buffer_pos # :nodoc:
|
87
|
+
current_pos = @buffer.pos
|
88
|
+
@buffer.rewind
|
89
|
+
begin
|
90
|
+
yield
|
91
|
+
ensure
|
92
|
+
@buffer.pos = current_pos
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|