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
|
@@ -8,32 +8,12 @@ module HTTPX
|
|
|
8
8
|
extend Forwardable
|
|
9
9
|
using URIExtensions
|
|
10
10
|
|
|
11
|
-
DEFAULTS =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
else
|
|
18
|
-
{
|
|
19
|
-
nameserver: nil,
|
|
20
|
-
**Resolv::DNS::Config.default_config_hash,
|
|
21
|
-
packet_size: 512,
|
|
22
|
-
timeouts: Resolver::RESOLVE_TIMEOUT,
|
|
23
|
-
}.freeze
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# nameservers for ipv6 are misconfigured in certain systems;
|
|
27
|
-
# this can use an unexpected endless loop
|
|
28
|
-
# https://gitlab.com/honeyryderchuck/httpx/issues/56
|
|
29
|
-
DEFAULTS[:nameserver].select! do |nameserver|
|
|
30
|
-
begin
|
|
31
|
-
IPAddr.new(nameserver)
|
|
32
|
-
true
|
|
33
|
-
rescue IPAddr::InvalidAddressError
|
|
34
|
-
false
|
|
35
|
-
end
|
|
36
|
-
end if DEFAULTS[:nameserver]
|
|
11
|
+
DEFAULTS = {
|
|
12
|
+
nameserver: nil,
|
|
13
|
+
**Resolv::DNS::Config.default_config_hash,
|
|
14
|
+
packet_size: 512,
|
|
15
|
+
timeouts: Resolver::RESOLVE_TIMEOUT,
|
|
16
|
+
}.freeze
|
|
37
17
|
|
|
38
18
|
DNS_PORT = 53
|
|
39
19
|
|
|
@@ -41,12 +21,16 @@ module HTTPX
|
|
|
41
21
|
|
|
42
22
|
attr_reader :state
|
|
43
23
|
|
|
44
|
-
def initialize(
|
|
24
|
+
def initialize(family, options)
|
|
45
25
|
super
|
|
46
26
|
@ns_index = 0
|
|
47
27
|
@resolver_options = DEFAULTS.merge(@options.resolver_options)
|
|
48
|
-
@
|
|
49
|
-
@
|
|
28
|
+
@socket_type = @resolver_options.fetch(:socket_type, :udp)
|
|
29
|
+
@nameserver = if (nameserver = @resolver_options[:nameserver])
|
|
30
|
+
nameserver = nameserver[family] if nameserver.is_a?(Hash)
|
|
31
|
+
Array(nameserver)
|
|
32
|
+
end
|
|
33
|
+
@ndots = @resolver_options.fetch(:ndots, 1)
|
|
50
34
|
@search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) }
|
|
51
35
|
@_timeouts = Array(@resolver_options[:timeouts])
|
|
52
36
|
@timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
|
|
@@ -104,6 +88,7 @@ module HTTPX
|
|
|
104
88
|
if @nameserver.nil?
|
|
105
89
|
ex = ResolveError.new("No available nameserver")
|
|
106
90
|
ex.set_backtrace(caller)
|
|
91
|
+
connection.force_reset
|
|
107
92
|
throw(:resolve_error, ex)
|
|
108
93
|
else
|
|
109
94
|
@connections << connection
|
|
@@ -119,7 +104,7 @@ module HTTPX
|
|
|
119
104
|
@timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
|
|
120
105
|
end
|
|
121
106
|
|
|
122
|
-
def
|
|
107
|
+
def handle_socket_timeout(interval)
|
|
123
108
|
do_retry(interval)
|
|
124
109
|
end
|
|
125
110
|
|
|
@@ -152,12 +137,23 @@ module HTTPX
|
|
|
152
137
|
host = connection.origin.host
|
|
153
138
|
timeout = (@timeouts[host][0] -= loop_time)
|
|
154
139
|
|
|
155
|
-
return unless timeout
|
|
140
|
+
return unless timeout <= 0
|
|
156
141
|
|
|
157
142
|
@timeouts[host].shift
|
|
158
|
-
|
|
143
|
+
|
|
144
|
+
if !@timeouts[host].empty?
|
|
145
|
+
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
|
146
|
+
resolve(connection)
|
|
147
|
+
elsif @ns_index + 1 < @nameserver.size
|
|
148
|
+
# try on the next nameserver
|
|
149
|
+
@ns_index += 1
|
|
150
|
+
log { "resolver: failed resolving #{host} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)" }
|
|
151
|
+
transition(:idle)
|
|
152
|
+
resolve(connection)
|
|
153
|
+
else
|
|
154
|
+
|
|
159
155
|
@timeouts.delete(host)
|
|
160
|
-
|
|
156
|
+
reset_hostname(h, reset_candidates: false)
|
|
161
157
|
|
|
162
158
|
return unless @queries.empty?
|
|
163
159
|
|
|
@@ -165,18 +161,55 @@ module HTTPX
|
|
|
165
161
|
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
|
166
162
|
# resolve timeout, including from the previous retries.
|
|
167
163
|
raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{connection.origin.host}")
|
|
168
|
-
else
|
|
169
|
-
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
|
170
|
-
resolve(connection)
|
|
171
164
|
end
|
|
172
165
|
end
|
|
173
166
|
|
|
174
167
|
def dread(wsize = @resolver_options[:packet_size])
|
|
175
168
|
loop do
|
|
169
|
+
wsize = @large_packet.capacity if @large_packet
|
|
170
|
+
|
|
176
171
|
siz = @io.read(wsize, @read_buffer)
|
|
177
|
-
return unless siz && siz.positive?
|
|
178
172
|
|
|
179
|
-
|
|
173
|
+
unless siz
|
|
174
|
+
ex = EOFError.new("descriptor closed")
|
|
175
|
+
ex.set_backtrace(caller)
|
|
176
|
+
raise ex
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
return unless siz.positive?
|
|
180
|
+
|
|
181
|
+
if @socket_type == :tcp
|
|
182
|
+
# packet may be incomplete, need to keep draining from the socket
|
|
183
|
+
if @large_packet
|
|
184
|
+
# large packet buffer already exists, continue pumping
|
|
185
|
+
@large_packet << @read_buffer
|
|
186
|
+
|
|
187
|
+
next unless @large_packet.full?
|
|
188
|
+
|
|
189
|
+
parse(@large_packet.to_s)
|
|
190
|
+
|
|
191
|
+
@socket_type = @resolver_options.fetch(:socket_type, :udp)
|
|
192
|
+
@large_packet = nil
|
|
193
|
+
transition(:closed)
|
|
194
|
+
return
|
|
195
|
+
else
|
|
196
|
+
size = @read_buffer[0, 2].unpack1("n")
|
|
197
|
+
buffer = @read_buffer.byteslice(2..-1)
|
|
198
|
+
|
|
199
|
+
if size > @read_buffer.bytesize
|
|
200
|
+
# only do buffer logic if it's worth it, and the whole packet isn't here already
|
|
201
|
+
@large_packet = Buffer.new(size)
|
|
202
|
+
@large_packet << buffer
|
|
203
|
+
|
|
204
|
+
next
|
|
205
|
+
else
|
|
206
|
+
parse(buffer)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
else # udp
|
|
210
|
+
parse(@read_buffer)
|
|
211
|
+
end
|
|
212
|
+
|
|
180
213
|
return if @state == :closed
|
|
181
214
|
end
|
|
182
215
|
end
|
|
@@ -186,34 +219,68 @@ module HTTPX
|
|
|
186
219
|
return if @write_buffer.empty?
|
|
187
220
|
|
|
188
221
|
siz = @io.write(@write_buffer)
|
|
189
|
-
|
|
222
|
+
|
|
223
|
+
unless siz
|
|
224
|
+
ex = EOFError.new("descriptor closed")
|
|
225
|
+
ex.set_backtrace(caller)
|
|
226
|
+
raise ex
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
return unless siz.positive?
|
|
190
230
|
|
|
191
231
|
return if @state == :closed
|
|
192
232
|
end
|
|
193
233
|
end
|
|
194
234
|
|
|
195
235
|
def parse(buffer)
|
|
196
|
-
|
|
197
|
-
addresses = Resolver.decode_dns_answer(buffer)
|
|
198
|
-
rescue Resolv::DNS::DecodeError => e
|
|
199
|
-
hostname, connection = @queries.first
|
|
200
|
-
@queries.delete(hostname)
|
|
201
|
-
@timeouts.delete(hostname)
|
|
202
|
-
@connections.delete(connection)
|
|
203
|
-
ex = NativeResolveError.new(connection, connection.origin.host, e.message)
|
|
204
|
-
ex.set_backtrace(e.backtrace)
|
|
205
|
-
raise ex
|
|
206
|
-
end
|
|
236
|
+
code, result = Resolver.decode_dns_answer(buffer)
|
|
207
237
|
|
|
208
|
-
|
|
238
|
+
case code
|
|
239
|
+
when :ok
|
|
240
|
+
parse_addresses(result)
|
|
241
|
+
when :no_domain_found
|
|
242
|
+
# Indicates no such domain was found.
|
|
209
243
|
hostname, connection = @queries.first
|
|
210
|
-
|
|
211
|
-
@timeouts.delete(hostname)
|
|
244
|
+
reset_hostname(hostname, reset_candidates: false)
|
|
212
245
|
|
|
213
246
|
unless @queries.value?(connection)
|
|
214
247
|
@connections.delete(connection)
|
|
215
|
-
raise NativeResolveError.new(connection, connection.origin.host)
|
|
248
|
+
raise NativeResolveError.new(connection, connection.origin.host, "name or service not known")
|
|
216
249
|
end
|
|
250
|
+
|
|
251
|
+
resolve
|
|
252
|
+
when :message_truncated
|
|
253
|
+
# TODO: what to do if it's already tcp??
|
|
254
|
+
return if @socket_type == :tcp
|
|
255
|
+
|
|
256
|
+
@socket_type = :tcp
|
|
257
|
+
|
|
258
|
+
hostname, _ = @queries.first
|
|
259
|
+
reset_hostname(hostname)
|
|
260
|
+
transition(:closed)
|
|
261
|
+
when :dns_error
|
|
262
|
+
hostname, connection = @queries.first
|
|
263
|
+
reset_hostname(hostname)
|
|
264
|
+
@connections.delete(connection)
|
|
265
|
+
ex = NativeResolveError.new(connection, connection.origin.host, "unknown DNS error (error code #{result})")
|
|
266
|
+
raise ex
|
|
267
|
+
when :decode_error
|
|
268
|
+
hostname, connection = @queries.first
|
|
269
|
+
reset_hostname(hostname)
|
|
270
|
+
@connections.delete(connection)
|
|
271
|
+
ex = NativeResolveError.new(connection, connection.origin.host, result.message)
|
|
272
|
+
ex.set_backtrace(result.backtrace)
|
|
273
|
+
raise ex
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def parse_addresses(addresses)
|
|
278
|
+
if addresses.empty?
|
|
279
|
+
# no address found, eliminate candidates
|
|
280
|
+
hostname, connection = @queries.first
|
|
281
|
+
reset_hostname(hostname)
|
|
282
|
+
@connections.delete(connection)
|
|
283
|
+
raise NativeResolveError.new(connection, connection.origin.host)
|
|
217
284
|
else
|
|
218
285
|
address = addresses.first
|
|
219
286
|
name = address["name"]
|
|
@@ -221,20 +288,21 @@ module HTTPX
|
|
|
221
288
|
connection = @queries.delete(name)
|
|
222
289
|
|
|
223
290
|
unless connection
|
|
291
|
+
orig_name = name
|
|
224
292
|
# absolute name
|
|
225
293
|
name_labels = Resolv::DNS::Name.create(name).to_a
|
|
226
|
-
name = @queries.
|
|
294
|
+
name = @queries.each_key.first { |hname| name_labels == Resolv::DNS::Name.create(hname).to_a }
|
|
227
295
|
|
|
228
296
|
# probably a retried query for which there's an answer
|
|
229
|
-
|
|
297
|
+
unless name
|
|
298
|
+
@timeouts.delete(orig_name)
|
|
299
|
+
return
|
|
300
|
+
end
|
|
230
301
|
|
|
231
302
|
address["name"] = name
|
|
232
303
|
connection = @queries.delete(name)
|
|
233
304
|
end
|
|
234
305
|
|
|
235
|
-
# eliminate other candidates
|
|
236
|
-
@queries.delete_if { |_, conn| connection == conn }
|
|
237
|
-
|
|
238
306
|
if address.key?("alias") # CNAME
|
|
239
307
|
# clean up intermediate queries
|
|
240
308
|
@timeouts.delete(name) unless connection.origin.host == name
|
|
@@ -246,7 +314,7 @@ module HTTPX
|
|
|
246
314
|
return
|
|
247
315
|
end
|
|
248
316
|
else
|
|
249
|
-
|
|
317
|
+
reset_hostname(name, connection: connection)
|
|
250
318
|
@timeouts.delete(connection.origin.host)
|
|
251
319
|
@connections.delete(connection)
|
|
252
320
|
Resolver.cached_lookup_set(connection.origin.host, @family, addresses) if @resolver_options[:cache]
|
|
@@ -260,6 +328,7 @@ module HTTPX
|
|
|
260
328
|
|
|
261
329
|
def resolve(connection = @connections.first, hostname = nil)
|
|
262
330
|
raise Error, "no URI to resolve" unless connection
|
|
331
|
+
|
|
263
332
|
return unless @write_buffer.empty?
|
|
264
333
|
|
|
265
334
|
hostname ||= @queries.key(connection)
|
|
@@ -276,12 +345,19 @@ module HTTPX
|
|
|
276
345
|
end
|
|
277
346
|
log { "resolver: query #{@record_type.name.split("::").last} for #{hostname}" }
|
|
278
347
|
begin
|
|
279
|
-
@write_buffer <<
|
|
348
|
+
@write_buffer << encode_dns_query(hostname)
|
|
280
349
|
rescue Resolv::DNS::EncodeError => e
|
|
281
350
|
emit_resolve_error(connection, hostname, e)
|
|
282
351
|
end
|
|
283
352
|
end
|
|
284
353
|
|
|
354
|
+
def encode_dns_query(hostname)
|
|
355
|
+
message_id = Resolver.generate_id
|
|
356
|
+
msg = Resolver.encode_dns_query(hostname, type: @record_type, message_id: message_id)
|
|
357
|
+
msg[0, 2] = [msg.size, message_id].pack("nn") if @socket_type == :tcp
|
|
358
|
+
msg
|
|
359
|
+
end
|
|
360
|
+
|
|
285
361
|
def generate_candidates(name)
|
|
286
362
|
return [name] if name.end_with?(".")
|
|
287
363
|
|
|
@@ -289,21 +365,25 @@ module HTTPX
|
|
|
289
365
|
name_parts = name.scan(/[^.]+/)
|
|
290
366
|
candidates = [name] if @ndots <= name_parts.size - 1
|
|
291
367
|
candidates.concat(@search.map { |domain| [*name_parts, *domain].join(".") })
|
|
292
|
-
|
|
368
|
+
fname = "#{name}."
|
|
369
|
+
candidates << fname unless candidates.include?(fname)
|
|
293
370
|
|
|
294
371
|
candidates
|
|
295
372
|
end
|
|
296
373
|
|
|
297
374
|
def build_socket
|
|
298
|
-
return if @io
|
|
299
|
-
|
|
300
375
|
ip, port = @nameserver[@ns_index]
|
|
301
376
|
port ||= DNS_PORT
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
377
|
+
|
|
378
|
+
case @socket_type
|
|
379
|
+
when :udp
|
|
380
|
+
log { "resolver: server: udp://#{ip}:#{port}..." }
|
|
381
|
+
UDP.new(ip, port, @options)
|
|
382
|
+
when :tcp
|
|
383
|
+
log { "resolver: server: tcp://#{ip}:#{port}..." }
|
|
384
|
+
origin = URI("tcp://#{ip}:#{port}")
|
|
385
|
+
TCP.new(origin, [ip], @options)
|
|
386
|
+
end
|
|
307
387
|
end
|
|
308
388
|
|
|
309
389
|
def transition(nextstate)
|
|
@@ -317,7 +397,7 @@ module HTTPX
|
|
|
317
397
|
when :open
|
|
318
398
|
return unless @state == :idle
|
|
319
399
|
|
|
320
|
-
build_socket
|
|
400
|
+
@io ||= build_socket
|
|
321
401
|
|
|
322
402
|
@io.connect
|
|
323
403
|
return unless @io.connected?
|
|
@@ -344,5 +424,18 @@ module HTTPX
|
|
|
344
424
|
end
|
|
345
425
|
end
|
|
346
426
|
end
|
|
427
|
+
|
|
428
|
+
def reset_hostname(hostname, connection: @queries.delete(hostname), reset_candidates: true)
|
|
429
|
+
@timeouts.delete(hostname)
|
|
430
|
+
@timeouts.delete(hostname)
|
|
431
|
+
|
|
432
|
+
return unless connection && reset_candidates
|
|
433
|
+
|
|
434
|
+
# eliminate other candidates
|
|
435
|
+
candidates = @queries.select { |_, conn| connection == conn }.keys
|
|
436
|
+
@queries.delete_if { |h, _| candidates.include?(h) }
|
|
437
|
+
# reset timeouts
|
|
438
|
+
@timeouts.delete_if { |h, _| candidates.include?(h) }
|
|
439
|
+
end
|
|
347
440
|
end
|
|
348
441
|
end
|
|
@@ -38,6 +38,8 @@ module HTTPX
|
|
|
38
38
|
|
|
39
39
|
def close; end
|
|
40
40
|
|
|
41
|
+
alias_method :terminate, :close
|
|
42
|
+
|
|
41
43
|
def closed?
|
|
42
44
|
true
|
|
43
45
|
end
|
|
@@ -46,15 +48,15 @@ module HTTPX
|
|
|
46
48
|
true
|
|
47
49
|
end
|
|
48
50
|
|
|
49
|
-
def emit_addresses(connection, family, addresses)
|
|
51
|
+
def emit_addresses(connection, family, addresses, early_resolve = false)
|
|
50
52
|
addresses.map! do |address|
|
|
51
53
|
address.is_a?(IPAddr) ? address : IPAddr.new(address.to_s)
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
# double emission check
|
|
55
|
-
return if connection.addresses && !addresses.intersect?(connection.addresses)
|
|
56
|
+
# double emission check, but allow early resolution to work
|
|
57
|
+
return if !early_resolve && connection.addresses && !addresses.intersect?(connection.addresses)
|
|
56
58
|
|
|
57
|
-
log { "resolver: answer #{connection.origin.host}: #{addresses.inspect}" }
|
|
59
|
+
log { "resolver: answer #{FAMILY_TYPES[RECORD_TYPES[family]]} #{connection.origin.host}: #{addresses.inspect}" }
|
|
58
60
|
if @pool && # if triggered by early resolve, pool may not be here yet
|
|
59
61
|
!connection.io &&
|
|
60
62
|
connection.options.ip_families.size > 1 &&
|
|
@@ -62,21 +64,34 @@ module HTTPX
|
|
|
62
64
|
addresses.first.to_s != connection.origin.host.to_s
|
|
63
65
|
log { "resolver: A response, applying resolution delay..." }
|
|
64
66
|
@pool.after(0.05) do
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
connection
|
|
69
|
-
emit(:resolve, connection)
|
|
67
|
+
unless connection.state == :closed ||
|
|
68
|
+
# double emission check
|
|
69
|
+
(connection.addresses && addresses.intersect?(connection.addresses))
|
|
70
|
+
emit_resolved_connection(connection, addresses, early_resolve)
|
|
70
71
|
end
|
|
71
72
|
end
|
|
72
73
|
else
|
|
73
|
-
connection
|
|
74
|
-
emit(:resolve, connection)
|
|
74
|
+
emit_resolved_connection(connection, addresses, early_resolve)
|
|
75
75
|
end
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
private
|
|
79
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
|
+
|
|
80
95
|
def early_resolve(connection, hostname: connection.origin.host)
|
|
81
96
|
addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
|
|
82
97
|
|
|
@@ -86,7 +101,7 @@ module HTTPX
|
|
|
86
101
|
|
|
87
102
|
return if addresses.empty?
|
|
88
103
|
|
|
89
|
-
emit_addresses(connection, @family, addresses)
|
|
104
|
+
emit_addresses(connection, @family, addresses, true)
|
|
90
105
|
end
|
|
91
106
|
|
|
92
107
|
def emit_resolve_error(connection, hostname = connection.origin.host, ex = nil)
|
|
@@ -94,7 +109,7 @@ module HTTPX
|
|
|
94
109
|
end
|
|
95
110
|
|
|
96
111
|
def resolve_error(hostname, ex = nil)
|
|
97
|
-
return ex if ex.is_a?(ResolveError)
|
|
112
|
+
return ex if ex.is_a?(ResolveError) || ex.is_a?(ResolveTimeoutError)
|
|
98
113
|
|
|
99
114
|
message = ex ? ex.message : "Can't resolve #{hostname}"
|
|
100
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)
|
|
@@ -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
|