httpx 1.7.0 → 1.7.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/doc/release_notes/1_7_1.md +21 -0
- data/lib/httpx/adapters/webmock.rb +18 -9
- data/lib/httpx/altsvc.rb +1 -1
- data/lib/httpx/connection/http1.rb +4 -3
- data/lib/httpx/connection.rb +4 -1
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/options.rb +78 -5
- data/lib/httpx/parser/http1.rb +1 -0
- data/lib/httpx/plugins/auth.rb +28 -2
- data/lib/httpx/plugins/oauth.rb +11 -18
- data/lib/httpx/plugins/persistent.rb +3 -5
- data/lib/httpx/plugins/proxy/http.rb +0 -4
- data/lib/httpx/plugins/proxy.rb +3 -1
- data/lib/httpx/plugins/query.rb +1 -1
- data/lib/httpx/plugins/rate_limiter.rb +20 -15
- data/lib/httpx/plugins/retries.rb +8 -11
- data/lib/httpx/plugins/stream.rb +2 -2
- data/lib/httpx/plugins/stream_bidi.rb +13 -1
- data/lib/httpx/pool.rb +0 -1
- data/lib/httpx/request/body.rb +1 -1
- data/lib/httpx/resolver/cache/base.rb +136 -0
- data/lib/httpx/resolver/cache/memory.rb +42 -0
- data/lib/httpx/resolver/cache.rb +18 -0
- data/lib/httpx/resolver/https.rb +7 -3
- data/lib/httpx/resolver/multi.rb +7 -3
- data/lib/httpx/resolver/native.rb +6 -2
- data/lib/httpx/resolver/resolver.rb +1 -1
- data/lib/httpx/resolver.rb +3 -149
- data/lib/httpx/response/body.rb +3 -3
- data/lib/httpx/selector.rb +5 -3
- data/lib/httpx/session.rb +1 -1
- data/lib/httpx/timers.rb +6 -12
- data/lib/httpx/transcoder/gzip.rb +7 -2
- data/lib/httpx/transcoder/multipart/decoder.rb +1 -1
- data/lib/httpx/transcoder/multipart.rb +1 -1
- data/lib/httpx/utils.rb +13 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +7 -4
- data/sig/loggable.rbs +1 -1
- data/sig/options.rbs +11 -3
- data/sig/plugins/auth.rbs +10 -1
- data/sig/plugins/oauth.rbs +0 -2
- data/sig/plugins/rate_limiter.rbs +4 -2
- data/sig/plugins/retries.rbs +4 -2
- data/sig/resolver/cache/base.rbs +28 -0
- data/sig/resolver/cache/memory.rbs +13 -0
- data/sig/resolver/cache.rbs +16 -0
- data/sig/resolver/https.rbs +19 -0
- data/sig/resolver/multi.rbs +6 -0
- data/sig/resolver.rbs +0 -24
- data/sig/timers.rbs +1 -1
- data/sig/utils.rbs +2 -0
- metadata +9 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "resolv"
|
|
4
|
+
|
|
5
|
+
module HTTPX
|
|
6
|
+
module Resolver::Cache
|
|
7
|
+
# Base class of the Resolver Cache adapter implementations.
|
|
8
|
+
#
|
|
9
|
+
# While resolver caches are not required to inherit from this class, it nevertheless provides
|
|
10
|
+
# common useful functions for desired functionality, such as singleton object ractor-safe access,
|
|
11
|
+
# or a default #resolve implementation which deals with IPs and the system hosts file.
|
|
12
|
+
#
|
|
13
|
+
class Base
|
|
14
|
+
MAX_CACHE_SIZE = 512
|
|
15
|
+
CACHE_MUTEX = Thread::Mutex.new
|
|
16
|
+
HOSTS = Resolv::Hosts.new
|
|
17
|
+
@cache = nil
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
attr_reader :hosts_resolver
|
|
21
|
+
|
|
22
|
+
# returns the singleton instance to be used within the current ractor.
|
|
23
|
+
def cache(label)
|
|
24
|
+
return Ractor.store_if_absent(:"httpx_resolver_cache_#{label}") { new } if Utils.in_ractor?
|
|
25
|
+
|
|
26
|
+
@cache ||= CACHE_MUTEX.synchronize do
|
|
27
|
+
@cache || new
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# resolves +hostname+ into an instance of HTTPX::Resolver::Entry if +hostname+ is an IP,
|
|
33
|
+
# or can be found in the cache, or can be found in the system hosts file.
|
|
34
|
+
def resolve(hostname)
|
|
35
|
+
ip_resolve(hostname) || get(hostname) || hosts_resolve(hostname)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# tries to convert +hostname+ into an IPAddr, returns <tt>nil</tt> otherwise.
|
|
41
|
+
def ip_resolve(hostname)
|
|
42
|
+
[Resolver::Entry.new(hostname)]
|
|
43
|
+
rescue ArgumentError
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# matches +hostname+ to entries in the hosts file, returns <tt>nil</nil> if none is
|
|
47
|
+
# found, or there is no hosts file.
|
|
48
|
+
def hosts_resolve(hostname)
|
|
49
|
+
ips = if Utils.in_ractor?
|
|
50
|
+
Ractor.store_if_absent(:httpx_hosts_resolver) { Resolv::Hosts.new }
|
|
51
|
+
else
|
|
52
|
+
HOSTS
|
|
53
|
+
end.getaddresses(hostname)
|
|
54
|
+
|
|
55
|
+
return if ips.empty?
|
|
56
|
+
|
|
57
|
+
ips.map { |ip| Resolver::Entry.new(ip) }
|
|
58
|
+
rescue IOError
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# not to be used directly!
|
|
62
|
+
def _get(hostname, lookups, hostnames, ttl)
|
|
63
|
+
return unless lookups.key?(hostname)
|
|
64
|
+
|
|
65
|
+
entries = lookups[hostname]
|
|
66
|
+
|
|
67
|
+
return unless entries
|
|
68
|
+
|
|
69
|
+
entries.delete_if do |address|
|
|
70
|
+
address["TTL"] < ttl
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if entries.empty?
|
|
74
|
+
lookups.delete(hostname)
|
|
75
|
+
hostnames.delete(hostname)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
ips = entries.flat_map do |address|
|
|
79
|
+
if (als = address["alias"])
|
|
80
|
+
_get(als, lookups, hostnames, ttl)
|
|
81
|
+
else
|
|
82
|
+
Resolver::Entry.new(address["data"], address["TTL"])
|
|
83
|
+
end
|
|
84
|
+
end.compact
|
|
85
|
+
|
|
86
|
+
ips unless ips.empty?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def _set(hostname, family, entries, lookups, hostnames)
|
|
90
|
+
# lru cleanup
|
|
91
|
+
while lookups.size >= MAX_CACHE_SIZE
|
|
92
|
+
hs = hostnames.shift
|
|
93
|
+
lookups.delete(hs)
|
|
94
|
+
end
|
|
95
|
+
hostnames << hostname
|
|
96
|
+
|
|
97
|
+
lookups[hostname] ||= [] # when there's no default proc
|
|
98
|
+
|
|
99
|
+
case family
|
|
100
|
+
when Socket::AF_INET6
|
|
101
|
+
lookups[hostname].concat(entries)
|
|
102
|
+
when Socket::AF_INET
|
|
103
|
+
lookups[hostname].unshift(*entries)
|
|
104
|
+
end
|
|
105
|
+
entries.each do |entry|
|
|
106
|
+
name = entry["name"]
|
|
107
|
+
next unless name != hostname
|
|
108
|
+
|
|
109
|
+
lookups[name] ||= []
|
|
110
|
+
|
|
111
|
+
case family
|
|
112
|
+
when Socket::AF_INET6
|
|
113
|
+
lookups[name] << entry
|
|
114
|
+
when Socket::AF_INET
|
|
115
|
+
lookups[name].unshift(entry)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def _evict(hostname, ip, lookups, hostnames)
|
|
121
|
+
return unless lookups.key?(hostname)
|
|
122
|
+
|
|
123
|
+
entries = lookups[hostname]
|
|
124
|
+
|
|
125
|
+
return unless entries
|
|
126
|
+
|
|
127
|
+
entries.delete_if { |entry| entry["data"] == ip }
|
|
128
|
+
|
|
129
|
+
return unless entries.empty?
|
|
130
|
+
|
|
131
|
+
lookups.delete(hostname)
|
|
132
|
+
hostnames.delete(hostname)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HTTPX
|
|
4
|
+
module Resolver::Cache
|
|
5
|
+
# Implementation of a thread-safe in-memory LRU resolver cache.
|
|
6
|
+
class Memory < Base
|
|
7
|
+
def initialize
|
|
8
|
+
super
|
|
9
|
+
@hostnames = []
|
|
10
|
+
@lookups = Hash.new { |h, k| h[k] = [] }
|
|
11
|
+
@lookup_mutex = Thread::Mutex.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get(hostname)
|
|
15
|
+
now = Utils.now
|
|
16
|
+
synchronize do |lookups, hostnames|
|
|
17
|
+
_get(hostname, lookups, hostnames, now)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def set(hostname, family, entries)
|
|
22
|
+
synchronize do |lookups, hostnames|
|
|
23
|
+
_set(hostname, family, entries, lookups, hostnames)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def evict(hostname, ip)
|
|
28
|
+
ip = ip.to_s
|
|
29
|
+
|
|
30
|
+
synchronize do |lookups, hostnames|
|
|
31
|
+
_evict(hostname, ip, lookups, hostnames)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def synchronize
|
|
38
|
+
@lookup_mutex.synchronize { yield(@lookups, @hostnames) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "httpx/resolver/cache/base"
|
|
4
|
+
require "httpx/resolver/cache/memory"
|
|
5
|
+
|
|
6
|
+
module HTTPX::Resolver
|
|
7
|
+
# The internal resolvers cache adapters are defined under this namespace.
|
|
8
|
+
#
|
|
9
|
+
# Adapters must comply with the Resolver Cache Adapter API and implement the following methods:
|
|
10
|
+
#
|
|
11
|
+
# * #resolve: (String hostname) -> Array[HTTPX::Entry]? => resolves hostname to a list of cached IPs (if found in cache or system)
|
|
12
|
+
# * #get: (String hostname) -> Array[HTTPX::Entry]? => resolves hostname to a list of cached IPs (if found in cache)
|
|
13
|
+
# * #set: (String hostname, Integer ip_family, Array[dns_result]) -> void => stores the set of results in the cache indexes for
|
|
14
|
+
# the hostname and the IP family
|
|
15
|
+
# * #evict: (String hostname, _ToS ip) -> void => evicts the ip for the hostname from the cache (usually done when no longer reachable)
|
|
16
|
+
module Cache
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/httpx/resolver/https.rb
CHANGED
|
@@ -31,7 +31,7 @@ module HTTPX
|
|
|
31
31
|
use_get: false,
|
|
32
32
|
}.freeze
|
|
33
33
|
|
|
34
|
-
def_delegators :@resolver_connection, :
|
|
34
|
+
def_delegators :@resolver_connection, :connecting?, :to_io, :call, :close,
|
|
35
35
|
:closed?, :deactivate, :terminate, :inflight?, :handle_socket_timeout
|
|
36
36
|
|
|
37
37
|
def initialize(_, options)
|
|
@@ -48,10 +48,14 @@ module HTTPX
|
|
|
48
48
|
@resolver.lazy_initialize
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
def state
|
|
52
|
+
@resolver_connection ? @resolver_connection.state : :idle
|
|
53
|
+
end
|
|
54
|
+
|
|
51
55
|
def <<(connection)
|
|
52
56
|
return if @uri.origin == connection.peer.to_s
|
|
53
57
|
|
|
54
|
-
@uri_addresses ||=
|
|
58
|
+
@uri_addresses ||= @options.resolver_cache.resolve(@uri.host) || @resolver.getaddresses(@uri.host)
|
|
55
59
|
|
|
56
60
|
if @uri_addresses.empty?
|
|
57
61
|
ex = ResolveError.new("Can't resolve DNS server #{@uri.host}")
|
|
@@ -247,7 +251,7 @@ module HTTPX
|
|
|
247
251
|
# eliminate other candidates
|
|
248
252
|
@queries.delete_if { |_, conn| connection == conn }
|
|
249
253
|
|
|
250
|
-
|
|
254
|
+
@options.resolver_cache.set(hostname, @family, addresses) if @resolver_options[:cache]
|
|
251
255
|
catch(:coalesced) { emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) }) }
|
|
252
256
|
end
|
|
253
257
|
end
|
data/lib/httpx/resolver/multi.rb
CHANGED
|
@@ -19,8 +19,6 @@ module HTTPX
|
|
|
19
19
|
resolver.multi = self
|
|
20
20
|
resolver
|
|
21
21
|
end
|
|
22
|
-
|
|
23
|
-
@errors = Hash.new { |hs, k| hs[k] = [] }
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
def state
|
|
@@ -47,7 +45,7 @@ module HTTPX
|
|
|
47
45
|
|
|
48
46
|
def early_resolve(connection)
|
|
49
47
|
hostname = connection.peer.host
|
|
50
|
-
addresses = @resolver_options[:cache] && (connection.addresses ||
|
|
48
|
+
addresses = @resolver_options[:cache] && (connection.addresses || nolookup_resolve(hostname, connection.options))
|
|
51
49
|
return false unless addresses
|
|
52
50
|
|
|
53
51
|
ip_families = connection.options.ip_families
|
|
@@ -83,5 +81,11 @@ module HTTPX
|
|
|
83
81
|
@current_session.select_resolver(resolver, @current_selector)
|
|
84
82
|
end
|
|
85
83
|
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def nolookup_resolve(hostname, options)
|
|
88
|
+
options.resolver_cache.resolve(hostname)
|
|
89
|
+
end
|
|
86
90
|
end
|
|
87
91
|
end
|
|
@@ -136,7 +136,11 @@ module HTTPX
|
|
|
136
136
|
private
|
|
137
137
|
|
|
138
138
|
def calculate_interests
|
|
139
|
-
|
|
139
|
+
if @queries.empty?
|
|
140
|
+
return @io.interests if (@socket_type == :tcp) && (@state == :idle)
|
|
141
|
+
|
|
142
|
+
return
|
|
143
|
+
end
|
|
140
144
|
|
|
141
145
|
return :r if @write_buffer.empty?
|
|
142
146
|
|
|
@@ -417,7 +421,7 @@ module HTTPX
|
|
|
417
421
|
reset_hostname(name, connection: connection)
|
|
418
422
|
@timeouts.delete(connection.peer.host)
|
|
419
423
|
@connections.delete(connection)
|
|
420
|
-
|
|
424
|
+
@options.resolver_cache.set(connection.peer.host, @family, addresses) if @resolver_options[:cache]
|
|
421
425
|
catch(:coalesced) do
|
|
422
426
|
emit_addresses(connection, @family, addresses.map { |a| Resolver::Entry.new(a["data"], a["TTL"]) })
|
|
423
427
|
end
|
|
@@ -125,7 +125,7 @@ module HTTPX
|
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
def early_resolve(connection, hostname: connection.peer.host) # rubocop:disable Naming/PredicateMethod
|
|
128
|
-
addresses = @resolver_options[:cache] && (connection.addresses ||
|
|
128
|
+
addresses = @resolver_options[:cache] && (connection.addresses || @options.resolver_cache.resolve(hostname))
|
|
129
129
|
|
|
130
130
|
return false unless addresses
|
|
131
131
|
|
data/lib/httpx/resolver.rb
CHANGED
|
@@ -8,150 +8,27 @@ module HTTPX
|
|
|
8
8
|
extend self
|
|
9
9
|
|
|
10
10
|
RESOLVE_TIMEOUT = [2, 3].freeze
|
|
11
|
-
MAX_CACHE_SIZE = 512
|
|
12
|
-
|
|
13
11
|
require "httpx/resolver/entry"
|
|
12
|
+
require "httpx/resolver/cache"
|
|
14
13
|
require "httpx/resolver/resolver"
|
|
15
14
|
require "httpx/resolver/system"
|
|
16
15
|
require "httpx/resolver/native"
|
|
17
16
|
require "httpx/resolver/https"
|
|
18
17
|
require "httpx/resolver/multi"
|
|
19
18
|
|
|
20
|
-
@lookup_mutex = Thread::Mutex.new
|
|
21
|
-
@hostnames = []
|
|
22
|
-
@lookups = Hash.new { |h, k| h[k] = [] }
|
|
23
|
-
|
|
24
19
|
@identifier_mutex = Thread::Mutex.new
|
|
25
20
|
@identifier = 1
|
|
26
|
-
@hosts_resolver = Resolv::Hosts.new
|
|
27
21
|
|
|
28
22
|
def supported_ip_families
|
|
29
|
-
if in_ractor?
|
|
23
|
+
if Utils.in_ractor?
|
|
30
24
|
Ractor.store_if_absent(:httpx_supported_ip_families) { find_supported_ip_families }
|
|
31
25
|
else
|
|
32
26
|
@supported_ip_families ||= find_supported_ip_families
|
|
33
27
|
end
|
|
34
28
|
end
|
|
35
29
|
|
|
36
|
-
def resolver_for(resolver_type, options)
|
|
37
|
-
case resolver_type
|
|
38
|
-
when Symbol
|
|
39
|
-
meth = :"resolver_#{resolver_type}_class"
|
|
40
|
-
|
|
41
|
-
return options.__send__(meth) if options.respond_to?(meth)
|
|
42
|
-
when Class
|
|
43
|
-
return resolver_type if resolver_type < Resolver
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
raise Error, "unsupported resolver type (#{resolver_type})"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def nolookup_resolve(hostname)
|
|
50
|
-
ip_resolve(hostname) || cached_lookup(hostname) || hosts_resolve(hostname)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# tries to convert +hostname+ into an IPAddr, returns <tt>nil</tt> otherwise.
|
|
54
|
-
def ip_resolve(hostname)
|
|
55
|
-
[Entry.new(hostname)]
|
|
56
|
-
rescue ArgumentError
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# matches +hostname+ to entries in the hosts file, returns <tt>nil</nil> if none is
|
|
60
|
-
# found, or there is no hosts file.
|
|
61
|
-
def hosts_resolve(hostname)
|
|
62
|
-
ips = if in_ractor?
|
|
63
|
-
Ractor.store_if_absent(:httpx_hosts_resolver) { Resolv::Hosts.new }
|
|
64
|
-
else
|
|
65
|
-
@hosts_resolver
|
|
66
|
-
end.getaddresses(hostname)
|
|
67
|
-
|
|
68
|
-
return if ips.empty?
|
|
69
|
-
|
|
70
|
-
ips.map { |ip| Entry.new(ip) }
|
|
71
|
-
rescue IOError
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def cached_lookup(hostname)
|
|
75
|
-
now = Utils.now
|
|
76
|
-
lookup_synchronize do |lookups, hostnames|
|
|
77
|
-
lookup(hostname, lookups, hostnames, now)
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def cached_lookup_set(hostname, family, entries)
|
|
82
|
-
lookup_synchronize do |lookups, hostnames|
|
|
83
|
-
# lru cleanup
|
|
84
|
-
while lookups.size >= MAX_CACHE_SIZE
|
|
85
|
-
hs = hostnames.shift
|
|
86
|
-
lookups.delete(hs)
|
|
87
|
-
end
|
|
88
|
-
hostnames << hostname
|
|
89
|
-
|
|
90
|
-
case family
|
|
91
|
-
when Socket::AF_INET6
|
|
92
|
-
lookups[hostname].concat(entries)
|
|
93
|
-
when Socket::AF_INET
|
|
94
|
-
lookups[hostname].unshift(*entries)
|
|
95
|
-
end
|
|
96
|
-
entries.each do |entry|
|
|
97
|
-
name = entry["name"]
|
|
98
|
-
next unless name != hostname
|
|
99
|
-
|
|
100
|
-
case family
|
|
101
|
-
when Socket::AF_INET6
|
|
102
|
-
lookups[name] << entry
|
|
103
|
-
when Socket::AF_INET
|
|
104
|
-
lookups[name].unshift(entry)
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def cached_lookup_evict(hostname, ip)
|
|
111
|
-
ip = ip.to_s
|
|
112
|
-
|
|
113
|
-
lookup_synchronize do |lookups, hostnames|
|
|
114
|
-
entries = lookups[hostname]
|
|
115
|
-
|
|
116
|
-
return unless entries
|
|
117
|
-
|
|
118
|
-
entries.delete_if { |entry| entry["data"] == ip }
|
|
119
|
-
|
|
120
|
-
if entries.empty?
|
|
121
|
-
lookups.delete(hostname)
|
|
122
|
-
hostnames.delete(hostname)
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# do not use directly!
|
|
128
|
-
def lookup(hostname, lookups, hostnames, ttl)
|
|
129
|
-
return unless lookups.key?(hostname)
|
|
130
|
-
|
|
131
|
-
entries = lookups[hostname]
|
|
132
|
-
|
|
133
|
-
entries.delete_if do |address|
|
|
134
|
-
address["TTL"] < ttl
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
if entries.empty?
|
|
138
|
-
lookups.delete(hostname)
|
|
139
|
-
hostnames.delete(hostname)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
ips = entries.flat_map do |address|
|
|
143
|
-
if (als = address["alias"])
|
|
144
|
-
lookup(als, lookups, hostnames, ttl)
|
|
145
|
-
else
|
|
146
|
-
Entry.new(address["data"], address["TTL"])
|
|
147
|
-
end
|
|
148
|
-
end.compact
|
|
149
|
-
|
|
150
|
-
ips unless ips.empty?
|
|
151
|
-
end
|
|
152
|
-
|
|
153
30
|
def generate_id
|
|
154
|
-
if in_ractor?
|
|
31
|
+
if Utils.in_ractor?
|
|
155
32
|
identifier = Ractor.store_if_absent(:httpx_resolver_identifier) { -1 }
|
|
156
33
|
Ractor.current[:httpx_resolver_identifier] = (identifier + 1) & 0xFFFF
|
|
157
34
|
else
|
|
@@ -213,16 +90,6 @@ module HTTPX
|
|
|
213
90
|
|
|
214
91
|
private
|
|
215
92
|
|
|
216
|
-
def lookup_synchronize
|
|
217
|
-
if in_ractor?
|
|
218
|
-
lookups = Ractor.store_if_absent(:httpx_resolver_lookups) { Hash.new { |h, k| h[k] = [] } }
|
|
219
|
-
hostnames = Ractor.store_if_absent(:httpx_resolver_hostnames) { [] }
|
|
220
|
-
return yield(lookups, hostnames)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
@lookup_mutex.synchronize { yield(@lookups, @hostnames) }
|
|
224
|
-
end
|
|
225
|
-
|
|
226
93
|
def id_synchronize(&block)
|
|
227
94
|
@identifier_mutex.synchronize(&block)
|
|
228
95
|
end
|
|
@@ -240,18 +107,5 @@ module HTTPX
|
|
|
240
107
|
[Socket::AF_INET]
|
|
241
108
|
end.freeze
|
|
242
109
|
end
|
|
243
|
-
|
|
244
|
-
if defined?(Ractor) &&
|
|
245
|
-
# no ractor support for 3.0
|
|
246
|
-
RUBY_VERSION >= "3.1.0"
|
|
247
|
-
|
|
248
|
-
def in_ractor?
|
|
249
|
-
Ractor.main != Ractor.current
|
|
250
|
-
end
|
|
251
|
-
else
|
|
252
|
-
def in_ractor?
|
|
253
|
-
false
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
110
|
end
|
|
257
111
|
end
|
data/lib/httpx/response/body.rb
CHANGED
|
@@ -59,13 +59,11 @@ module HTTPX
|
|
|
59
59
|
|
|
60
60
|
chunk = decode_chunk(chunk)
|
|
61
61
|
|
|
62
|
-
size = chunk.bytesize
|
|
63
|
-
@length += size
|
|
64
62
|
transition(:open)
|
|
65
63
|
@buffer.write(chunk)
|
|
66
64
|
|
|
67
65
|
@response.emit(:chunk_received, chunk)
|
|
68
|
-
|
|
66
|
+
chunk.bytesize
|
|
69
67
|
end
|
|
70
68
|
|
|
71
69
|
# reads a chunk from the payload (implementation of the IO reader protocol).
|
|
@@ -209,6 +207,8 @@ module HTTPX
|
|
|
209
207
|
chunk = inflater.call(chunk)
|
|
210
208
|
end if @inflaters
|
|
211
209
|
|
|
210
|
+
@length += chunk.bytesize
|
|
211
|
+
|
|
212
212
|
chunk
|
|
213
213
|
end
|
|
214
214
|
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -127,13 +127,15 @@ module HTTPX
|
|
|
127
127
|
private
|
|
128
128
|
|
|
129
129
|
def select(interval, &block)
|
|
130
|
-
has_no_selectables = @selectables.empty?
|
|
131
130
|
# do not cause an infinite loop here.
|
|
132
131
|
#
|
|
133
132
|
# this may happen if timeout calculation actually triggered an error which causes
|
|
134
133
|
# the connections to be reaped (such as the total timeout error) before #select
|
|
135
134
|
# gets called.
|
|
136
|
-
|
|
135
|
+
if @selectables.empty?
|
|
136
|
+
sleep(interval) if interval
|
|
137
|
+
return
|
|
138
|
+
end
|
|
137
139
|
|
|
138
140
|
# @type var r: (selectable | Array[selectable])?
|
|
139
141
|
# @type var w: (selectable | Array[selectable])?
|
|
@@ -171,7 +173,7 @@ module HTTPX
|
|
|
171
173
|
when Array
|
|
172
174
|
select_many(r, w, interval, &block)
|
|
173
175
|
when nil
|
|
174
|
-
return unless interval &&
|
|
176
|
+
return unless interval && @selectables.any?
|
|
175
177
|
|
|
176
178
|
# no selectables
|
|
177
179
|
# TODO: replace with sleep?
|
data/lib/httpx/session.rb
CHANGED
|
@@ -177,7 +177,7 @@ module HTTPX
|
|
|
177
177
|
|
|
178
178
|
# returns the HTTPX::Connection through which the +request+ should be sent through.
|
|
179
179
|
def find_connection(request_uri, selector, options)
|
|
180
|
-
log(level: 2) { "finding connection for
|
|
180
|
+
log(level: 2) { "finding connection for #{request_uri}..." }
|
|
181
181
|
if (connection = selector.find_connection(request_uri, options))
|
|
182
182
|
connection.idling if connection.state == :closed
|
|
183
183
|
connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
|
data/lib/httpx/timers.rb
CHANGED
|
@@ -29,13 +29,15 @@ module HTTPX
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def wait_interval
|
|
32
|
-
drop_elapsed!
|
|
33
|
-
|
|
34
32
|
return if @intervals.empty?
|
|
35
33
|
|
|
34
|
+
first_interval = @intervals.first
|
|
35
|
+
|
|
36
|
+
drop_elapsed!(0) if first_interval.elapsed?(0)
|
|
37
|
+
|
|
36
38
|
@next_interval_at = Utils.now
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
first_interval.interval
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def fire(error = nil)
|
|
@@ -46,20 +48,12 @@ module HTTPX
|
|
|
46
48
|
|
|
47
49
|
drop_elapsed!(elapsed_time)
|
|
48
50
|
|
|
49
|
-
@intervals = @intervals.drop_while { |interval| interval.elapse(elapsed_time) <= 0 }
|
|
50
|
-
|
|
51
51
|
@next_interval_at = nil if @intervals.empty?
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
private
|
|
55
55
|
|
|
56
|
-
def drop_elapsed!(elapsed_time
|
|
57
|
-
# check first, if not elapsed, then return
|
|
58
|
-
first_interval = @intervals.first
|
|
59
|
-
|
|
60
|
-
return unless first_interval && first_interval.elapsed?(elapsed_time)
|
|
61
|
-
|
|
62
|
-
# TODO: would be nice to have a drop_while!
|
|
56
|
+
def drop_elapsed!(elapsed_time)
|
|
63
57
|
@intervals = @intervals.drop_while { |interval| interval.elapse(elapsed_time) <= 0 }
|
|
64
58
|
end
|
|
65
59
|
|
|
@@ -8,6 +8,7 @@ module HTTPX
|
|
|
8
8
|
class Deflater < Transcoder::Deflater
|
|
9
9
|
def initialize(body)
|
|
10
10
|
@compressed_chunk = "".b
|
|
11
|
+
@deflater = nil
|
|
11
12
|
super
|
|
12
13
|
end
|
|
13
14
|
|
|
@@ -28,8 +29,12 @@ module HTTPX
|
|
|
28
29
|
|
|
29
30
|
private
|
|
30
31
|
|
|
31
|
-
def write(
|
|
32
|
-
|
|
32
|
+
def write(*chunks)
|
|
33
|
+
chunks.sum do |chunk|
|
|
34
|
+
chunk = chunk.to_s
|
|
35
|
+
@compressed_chunk << chunk
|
|
36
|
+
chunk.bytesize
|
|
37
|
+
end
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
def compressed_chunk
|
|
@@ -12,7 +12,6 @@ module HTTPX
|
|
|
12
12
|
def initialize(filename, content_type)
|
|
13
13
|
@original_filename = filename
|
|
14
14
|
@content_type = content_type
|
|
15
|
-
@current = nil
|
|
16
15
|
@file = Tempfile.new("httpx", encoding: Encoding::BINARY, mode: File::RDWR)
|
|
17
16
|
super(@file)
|
|
18
17
|
end
|
|
@@ -39,6 +38,7 @@ module HTTPX
|
|
|
39
38
|
@parts = {}
|
|
40
39
|
@intermediate_boundary = "--#{@boundary}"
|
|
41
40
|
@state = :idle
|
|
41
|
+
@current = nil
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def call(response, *)
|
data/lib/httpx/utils.rb
CHANGED
|
@@ -71,5 +71,18 @@ module HTTPX
|
|
|
71
71
|
uri.non_ascii_hostname = non_ascii_hostname
|
|
72
72
|
uri
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
if defined?(Ractor) &&
|
|
76
|
+
# no ractor support for 3.0
|
|
77
|
+
RUBY_VERSION >= "3.1.0"
|
|
78
|
+
|
|
79
|
+
def in_ractor?
|
|
80
|
+
Ractor.main != Ractor.current
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
def in_ractor?
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
end
|
|
74
87
|
end
|
|
75
88
|
end
|
data/lib/httpx/version.rb
CHANGED
data/sig/altsvc.rbs
CHANGED
|
@@ -5,19 +5,22 @@ module HTTPX
|
|
|
5
5
|
|
|
6
6
|
def send: (Request request) -> void
|
|
7
7
|
|
|
8
|
-
def match?: (
|
|
8
|
+
def match?: (http_uri uri, Options options) -> bool
|
|
9
9
|
|
|
10
10
|
private
|
|
11
11
|
|
|
12
|
-
def match_altsvcs?: (
|
|
12
|
+
def match_altsvcs?: (http_uri uri) -> bool
|
|
13
13
|
|
|
14
|
-
def match_altsvc_options?: (
|
|
14
|
+
def match_altsvc_options?: (http_uri uri, Options options) -> bool
|
|
15
15
|
|
|
16
|
-
def altsvc_match?: (
|
|
16
|
+
def altsvc_match?: (http_uri uri, uri other_uri) -> bool
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
type altsvc_params = Hash[String, untyped]
|
|
20
20
|
|
|
21
|
+
self.@altsvc_mutex: Thread::Mutex
|
|
22
|
+
self.@altsvcs: Hash[String, Array[altsvc_params]]
|
|
23
|
+
|
|
21
24
|
def self?.cached_altsvc: (String origin) -> Array[altsvc_params]
|
|
22
25
|
|
|
23
26
|
def self?.cached_altsvc_set: (String origin, altsvc_params) -> void
|