httpx 1.6.2 → 1.7.0
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/0_11_0.md +3 -3
- data/doc/release_notes/1_6_3.md +47 -0
- data/doc/release_notes/1_7_0.md +149 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/sentry.rb +1 -1
- data/lib/httpx/altsvc.rb +3 -1
- data/lib/httpx/connection/http1.rb +14 -15
- data/lib/httpx/connection/http2.rb +16 -15
- data/lib/httpx/connection.rb +118 -110
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/extensions.rb +0 -14
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +1 -1
- data/lib/httpx/loggable.rb +14 -2
- data/lib/httpx/options.rb +60 -17
- data/lib/httpx/plugins/auth/digest.rb +44 -4
- data/lib/httpx/plugins/auth.rb +87 -4
- data/lib/httpx/plugins/aws_sdk_authentication.rb +0 -1
- data/lib/httpx/plugins/callbacks.rb +15 -1
- data/lib/httpx/plugins/cookies/cookie.rb +1 -0
- data/lib/httpx/plugins/digest_auth.rb +4 -5
- data/lib/httpx/plugins/fiber_concurrency.rb +16 -1
- data/lib/httpx/plugins/grpc/grpc_encoding.rb +1 -1
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/internal_telemetry.rb +1 -1
- data/lib/httpx/plugins/ntlm_auth.rb +5 -3
- data/lib/httpx/plugins/oauth.rb +162 -56
- data/lib/httpx/plugins/proxy/http.rb +37 -9
- data/lib/httpx/plugins/rate_limiter.rb +2 -2
- data/lib/httpx/plugins/response_cache/file_store.rb +1 -0
- data/lib/httpx/plugins/response_cache.rb +16 -9
- data/lib/httpx/plugins/retries.rb +55 -16
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +59 -8
- data/lib/httpx/plugins/stream_bidi.rb +87 -22
- data/lib/httpx/pool.rb +65 -21
- data/lib/httpx/request.rb +13 -14
- data/lib/httpx/resolver/https.rb +100 -34
- data/lib/httpx/resolver/multi.rb +12 -27
- data/lib/httpx/resolver/native.rb +68 -38
- data/lib/httpx/resolver/resolver.rb +46 -29
- data/lib/httpx/resolver/system.rb +63 -39
- data/lib/httpx/resolver.rb +97 -29
- data/lib/httpx/response/body.rb +2 -0
- data/lib/httpx/response.rb +22 -6
- data/lib/httpx/selector.rb +44 -20
- data/lib/httpx/session.rb +23 -33
- data/lib/httpx/transcoder/body.rb +1 -1
- data/lib/httpx/transcoder/deflate.rb +13 -8
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/transcoder/multipart/decoder.rb +4 -4
- data/lib/httpx/transcoder/multipart/encoder.rb +1 -1
- data/lib/httpx/transcoder/multipart.rb +16 -8
- data/lib/httpx/transcoder/utils/body_reader.rb +1 -2
- data/lib/httpx/transcoder/utils/deflater.rb +1 -2
- data/lib/httpx/transcoder.rb +4 -6
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +3 -0
- data/sig/chainable.rbs +3 -3
- data/sig/connection.rbs +13 -6
- data/sig/loggable.rbs +5 -1
- data/sig/options.rbs +6 -2
- data/sig/plugins/auth/digest.rbs +6 -0
- data/sig/plugins/auth.rbs +28 -4
- data/sig/plugins/basic_auth.rbs +3 -3
- data/sig/plugins/callbacks.rbs +3 -0
- data/sig/plugins/digest_auth.rbs +2 -4
- data/sig/plugins/fiber_concurrency.rbs +6 -0
- data/sig/plugins/ntlm_auth.rbs +2 -2
- data/sig/plugins/oauth.rbs +46 -15
- data/sig/plugins/rate_limiter.rbs +1 -1
- data/sig/plugins/response_cache/file_store.rbs +2 -0
- data/sig/plugins/response_cache.rbs +4 -0
- data/sig/plugins/retries.rbs +8 -2
- data/sig/plugins/stream.rbs +13 -3
- data/sig/plugins/stream_bidi.rbs +5 -7
- data/sig/pool.rbs +1 -1
- data/sig/resolver/https.rbs +7 -0
- data/sig/resolver/multi.rbs +2 -9
- data/sig/resolver/native.rbs +1 -1
- data/sig/resolver/resolver.rbs +9 -8
- data/sig/resolver/system.rbs +4 -2
- data/sig/resolver.rbs +12 -3
- data/sig/response.rbs +3 -0
- data/sig/selector.rbs +2 -0
- data/sig/session.rbs +8 -8
- data/sig/transcoder/multipart.rbs +4 -2
- data/sig/transcoder.rbs +5 -1
- metadata +5 -1
data/lib/httpx/resolver.rb
CHANGED
|
@@ -5,7 +5,10 @@ require "resolv"
|
|
|
5
5
|
|
|
6
6
|
module HTTPX
|
|
7
7
|
module Resolver
|
|
8
|
+
extend self
|
|
9
|
+
|
|
8
10
|
RESOLVE_TIMEOUT = [2, 3].freeze
|
|
11
|
+
MAX_CACHE_SIZE = 512
|
|
9
12
|
|
|
10
13
|
require "httpx/resolver/entry"
|
|
11
14
|
require "httpx/resolver/resolver"
|
|
@@ -15,26 +18,19 @@ module HTTPX
|
|
|
15
18
|
require "httpx/resolver/multi"
|
|
16
19
|
|
|
17
20
|
@lookup_mutex = Thread::Mutex.new
|
|
21
|
+
@hostnames = []
|
|
18
22
|
@lookups = Hash.new { |h, k| h[k] = [] }
|
|
19
23
|
|
|
20
24
|
@identifier_mutex = Thread::Mutex.new
|
|
21
25
|
@identifier = 1
|
|
22
26
|
@hosts_resolver = Resolv::Hosts.new
|
|
23
27
|
|
|
24
|
-
module_function
|
|
25
|
-
|
|
26
28
|
def supported_ip_families
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
else
|
|
33
|
-
[Socket::AF_INET]
|
|
34
|
-
end
|
|
35
|
-
rescue NotImplementedError
|
|
36
|
-
[Socket::AF_INET]
|
|
37
|
-
end.freeze
|
|
29
|
+
if in_ractor?
|
|
30
|
+
Ractor.store_if_absent(:httpx_supported_ip_families) { find_supported_ip_families }
|
|
31
|
+
else
|
|
32
|
+
@supported_ip_families ||= find_supported_ip_families
|
|
33
|
+
end
|
|
38
34
|
end
|
|
39
35
|
|
|
40
36
|
def resolver_for(resolver_type, options)
|
|
@@ -63,7 +59,12 @@ module HTTPX
|
|
|
63
59
|
# matches +hostname+ to entries in the hosts file, returns <tt>nil</nil> if none is
|
|
64
60
|
# found, or there is no hosts file.
|
|
65
61
|
def hosts_resolve(hostname)
|
|
66
|
-
ips =
|
|
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
|
+
|
|
67
68
|
return if ips.empty?
|
|
68
69
|
|
|
69
70
|
ips.map { |ip| Entry.new(ip) }
|
|
@@ -72,13 +73,20 @@ module HTTPX
|
|
|
72
73
|
|
|
73
74
|
def cached_lookup(hostname)
|
|
74
75
|
now = Utils.now
|
|
75
|
-
lookup_synchronize do |lookups|
|
|
76
|
-
lookup(hostname, lookups, now)
|
|
76
|
+
lookup_synchronize do |lookups, hostnames|
|
|
77
|
+
lookup(hostname, lookups, hostnames, now)
|
|
77
78
|
end
|
|
78
79
|
end
|
|
79
80
|
|
|
80
81
|
def cached_lookup_set(hostname, family, entries)
|
|
81
|
-
lookup_synchronize do |lookups|
|
|
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
|
+
|
|
82
90
|
case family
|
|
83
91
|
when Socket::AF_INET6
|
|
84
92
|
lookups[hostname].concat(entries)
|
|
@@ -86,13 +94,14 @@ module HTTPX
|
|
|
86
94
|
lookups[hostname].unshift(*entries)
|
|
87
95
|
end
|
|
88
96
|
entries.each do |entry|
|
|
89
|
-
|
|
97
|
+
name = entry["name"]
|
|
98
|
+
next unless name != hostname
|
|
90
99
|
|
|
91
100
|
case family
|
|
92
101
|
when Socket::AF_INET6
|
|
93
|
-
lookups[
|
|
102
|
+
lookups[name] << entry
|
|
94
103
|
when Socket::AF_INET
|
|
95
|
-
lookups[
|
|
104
|
+
lookups[name].unshift(entry)
|
|
96
105
|
end
|
|
97
106
|
end
|
|
98
107
|
end
|
|
@@ -101,26 +110,38 @@ module HTTPX
|
|
|
101
110
|
def cached_lookup_evict(hostname, ip)
|
|
102
111
|
ip = ip.to_s
|
|
103
112
|
|
|
104
|
-
lookup_synchronize do |lookups|
|
|
113
|
+
lookup_synchronize do |lookups, hostnames|
|
|
105
114
|
entries = lookups[hostname]
|
|
106
115
|
|
|
107
116
|
return unless entries
|
|
108
117
|
|
|
109
|
-
|
|
118
|
+
entries.delete_if { |entry| entry["data"] == ip }
|
|
119
|
+
|
|
120
|
+
if entries.empty?
|
|
121
|
+
lookups.delete(hostname)
|
|
122
|
+
hostnames.delete(hostname)
|
|
123
|
+
end
|
|
110
124
|
end
|
|
111
125
|
end
|
|
112
126
|
|
|
113
127
|
# do not use directly!
|
|
114
|
-
def lookup(hostname, lookups, ttl)
|
|
128
|
+
def lookup(hostname, lookups, hostnames, ttl)
|
|
115
129
|
return unless lookups.key?(hostname)
|
|
116
130
|
|
|
117
|
-
entries = lookups[hostname]
|
|
118
|
-
|
|
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)
|
|
119
140
|
end
|
|
120
141
|
|
|
121
142
|
ips = entries.flat_map do |address|
|
|
122
143
|
if (als = address["alias"])
|
|
123
|
-
lookup(als, lookups, ttl)
|
|
144
|
+
lookup(als, lookups, hostnames, ttl)
|
|
124
145
|
else
|
|
125
146
|
Entry.new(address["data"], address["TTL"])
|
|
126
147
|
end
|
|
@@ -130,7 +151,12 @@ module HTTPX
|
|
|
130
151
|
end
|
|
131
152
|
|
|
132
153
|
def generate_id
|
|
133
|
-
|
|
154
|
+
if in_ractor?
|
|
155
|
+
identifier = Ractor.store_if_absent(:httpx_resolver_identifier) { -1 }
|
|
156
|
+
Ractor.current[:httpx_resolver_identifier] = (identifier + 1) & 0xFFFF
|
|
157
|
+
else
|
|
158
|
+
id_synchronize { @identifier = (@identifier + 1) & 0xFFFF }
|
|
159
|
+
end
|
|
134
160
|
end
|
|
135
161
|
|
|
136
162
|
def encode_dns_query(hostname, type: Resolv::DNS::Resource::IN::A, message_id: generate_id)
|
|
@@ -152,7 +178,14 @@ module HTTPX
|
|
|
152
178
|
|
|
153
179
|
return :message_truncated if message.tc == 1
|
|
154
180
|
|
|
155
|
-
|
|
181
|
+
if message.rcode != Resolv::DNS::RCode::NoError
|
|
182
|
+
case message.rcode
|
|
183
|
+
when Resolv::DNS::RCode::ServFail
|
|
184
|
+
return :retriable_error, message.rcode
|
|
185
|
+
else
|
|
186
|
+
return :dns_error, message.rcode
|
|
187
|
+
end
|
|
188
|
+
end
|
|
156
189
|
|
|
157
190
|
addresses = []
|
|
158
191
|
|
|
@@ -178,12 +211,47 @@ module HTTPX
|
|
|
178
211
|
[:ok, addresses]
|
|
179
212
|
end
|
|
180
213
|
|
|
214
|
+
private
|
|
215
|
+
|
|
181
216
|
def lookup_synchronize
|
|
182
|
-
|
|
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) }
|
|
183
224
|
end
|
|
184
225
|
|
|
185
226
|
def id_synchronize(&block)
|
|
186
227
|
@identifier_mutex.synchronize(&block)
|
|
187
228
|
end
|
|
229
|
+
|
|
230
|
+
def find_supported_ip_families
|
|
231
|
+
list = Socket.ip_address_list
|
|
232
|
+
|
|
233
|
+
begin
|
|
234
|
+
if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
|
|
235
|
+
[Socket::AF_INET6, Socket::AF_INET]
|
|
236
|
+
else
|
|
237
|
+
[Socket::AF_INET]
|
|
238
|
+
end
|
|
239
|
+
rescue NotImplementedError
|
|
240
|
+
[Socket::AF_INET]
|
|
241
|
+
end.freeze
|
|
242
|
+
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
|
|
188
256
|
end
|
|
189
257
|
end
|
data/lib/httpx/response/body.rb
CHANGED
data/lib/httpx/response.rb
CHANGED
|
@@ -211,16 +211,19 @@ module HTTPX
|
|
|
211
211
|
|
|
212
212
|
def initialize(header_value)
|
|
213
213
|
@header_value = header_value
|
|
214
|
+
@mime_type = @charset = nil
|
|
215
|
+
@initialized = false
|
|
214
216
|
end
|
|
215
217
|
|
|
216
218
|
# returns the mime type declared in the header.
|
|
217
219
|
#
|
|
218
220
|
# ContentType.new("application/json; charset=utf-8").mime_type #=> "application/json"
|
|
219
221
|
def mime_type
|
|
220
|
-
return @mime_type if
|
|
222
|
+
return @mime_type if @initialized
|
|
221
223
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
+
load
|
|
225
|
+
|
|
226
|
+
@mime_type
|
|
224
227
|
end
|
|
225
228
|
|
|
226
229
|
# returns the charset declared in the header.
|
|
@@ -228,10 +231,23 @@ module HTTPX
|
|
|
228
231
|
# ContentType.new("application/json; charset=utf-8").charset #=> "utf-8"
|
|
229
232
|
# ContentType.new("text/plain").charset #=> nil
|
|
230
233
|
def charset
|
|
231
|
-
return @charset if
|
|
234
|
+
return @charset if @initialized
|
|
235
|
+
|
|
236
|
+
load
|
|
237
|
+
|
|
238
|
+
@charset
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
private
|
|
242
|
+
|
|
243
|
+
def load
|
|
244
|
+
m = @header_value.to_s[MIME_TYPE_RE, 1]
|
|
245
|
+
m && @mime_type = m.strip.downcase
|
|
246
|
+
|
|
247
|
+
c = @header_value.to_s[CHARSET_RE, 1]
|
|
248
|
+
c && @charset = c.strip.delete('"')
|
|
232
249
|
|
|
233
|
-
|
|
234
|
-
m && @charset = m.strip.delete('"')
|
|
250
|
+
@initialized = true
|
|
235
251
|
end
|
|
236
252
|
end
|
|
237
253
|
|
data/lib/httpx/selector.rb
CHANGED
|
@@ -51,7 +51,7 @@ module HTTPX
|
|
|
51
51
|
|
|
52
52
|
begin
|
|
53
53
|
select(timeout) do |c|
|
|
54
|
-
c.log(level: 2) { "[#{c.state}] selected#{" after #{timeout} secs" unless timeout.nil?}..." }
|
|
54
|
+
c.log(level: 2) { "[#{c.state}] selected from selector##{object_id} #{" after #{timeout} secs" unless timeout.nil?}..." }
|
|
55
55
|
|
|
56
56
|
c.call
|
|
57
57
|
end
|
|
@@ -61,17 +61,6 @@ module HTTPX
|
|
|
61
61
|
@timers.fire(e)
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
|
-
rescue StandardError => e
|
|
65
|
-
each_connection do |c|
|
|
66
|
-
c.emit(:error, e)
|
|
67
|
-
end
|
|
68
|
-
rescue Exception # rubocop:disable Lint/RescueException
|
|
69
|
-
each_connection do |conn|
|
|
70
|
-
conn.force_reset
|
|
71
|
-
conn.disconnect
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
raise
|
|
75
64
|
end
|
|
76
65
|
|
|
77
66
|
def terminate
|
|
@@ -157,7 +146,9 @@ module HTTPX
|
|
|
157
146
|
|
|
158
147
|
next(is_closed) if is_closed
|
|
159
148
|
|
|
160
|
-
io.log(level: 2)
|
|
149
|
+
io.log(level: 2) do
|
|
150
|
+
"[#{io.state}] registering in selector##{object_id} for select (#{interests})#{" for #{interval} seconds" unless interval.nil?}"
|
|
151
|
+
end
|
|
161
152
|
|
|
162
153
|
if READABLE.include?(interests)
|
|
163
154
|
r = r.nil? ? io : (Array(r) << io)
|
|
@@ -206,7 +197,27 @@ module HTTPX
|
|
|
206
197
|
end
|
|
207
198
|
|
|
208
199
|
def select_many(r, w, interval, &block)
|
|
209
|
-
|
|
200
|
+
begin
|
|
201
|
+
readers, writers = ::IO.select(r, w, nil, interval)
|
|
202
|
+
rescue IOError => e
|
|
203
|
+
(Array(r) + Array(w)).each do |sel|
|
|
204
|
+
# TODO: is there a way to cheaply find the IO associated with the error?
|
|
205
|
+
sel.on_error(e)
|
|
206
|
+
sel.force_close(true)
|
|
207
|
+
end
|
|
208
|
+
rescue StandardError => e
|
|
209
|
+
(Array(r) + Array(w)).each do |sel|
|
|
210
|
+
sel.on_error(e)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
return
|
|
214
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
215
|
+
(Array(r) + Array(w)).each do |sel|
|
|
216
|
+
sel.force_close(true)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
raise e
|
|
220
|
+
end
|
|
210
221
|
|
|
211
222
|
if readers.nil? && writers.nil? && interval
|
|
212
223
|
[*r, *w].each { |io| io.handle_socket_timeout(interval) }
|
|
@@ -228,12 +239,25 @@ module HTTPX
|
|
|
228
239
|
end
|
|
229
240
|
|
|
230
241
|
def select_one(io, interests, interval)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
242
|
+
begin
|
|
243
|
+
result =
|
|
244
|
+
case interests
|
|
245
|
+
when :r then io.to_io.wait_readable(interval)
|
|
246
|
+
when :w then io.to_io.wait_writable(interval)
|
|
247
|
+
when :rw then rw_wait(io, interval)
|
|
248
|
+
end
|
|
249
|
+
rescue IOError => e
|
|
250
|
+
io.on_error(e)
|
|
251
|
+
io.force_close(true)
|
|
252
|
+
rescue StandardError => e
|
|
253
|
+
io.on_error(e)
|
|
254
|
+
|
|
255
|
+
return
|
|
256
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
257
|
+
io.force_close(true)
|
|
258
|
+
|
|
259
|
+
raise e
|
|
260
|
+
end
|
|
237
261
|
|
|
238
262
|
unless result || interval.nil?
|
|
239
263
|
io.handle_socket_timeout(interval) unless @is_timer_interval
|
data/lib/httpx/session.rb
CHANGED
|
@@ -120,13 +120,16 @@ module HTTPX
|
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
def select_connection(connection, selector)
|
|
123
|
-
|
|
123
|
+
pin(connection, selector)
|
|
124
|
+
connection.log(level: 2) do
|
|
125
|
+
"registering into selector##{selector.object_id}"
|
|
126
|
+
end
|
|
124
127
|
selector.register(connection)
|
|
125
128
|
end
|
|
126
129
|
|
|
127
|
-
def
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
def pin(conn_or_resolver, selector)
|
|
131
|
+
conn_or_resolver.current_session = self
|
|
132
|
+
conn_or_resolver.current_selector = selector
|
|
130
133
|
end
|
|
131
134
|
|
|
132
135
|
alias_method :select_resolver, :select_connection
|
|
@@ -137,9 +140,6 @@ module HTTPX
|
|
|
137
140
|
end
|
|
138
141
|
selector.deregister(connection)
|
|
139
142
|
|
|
140
|
-
# when connections coalesce
|
|
141
|
-
return if connection.state == :idle
|
|
142
|
-
|
|
143
143
|
return if cloned
|
|
144
144
|
|
|
145
145
|
return if @closing && connection.state == :closed
|
|
@@ -177,6 +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 ##{request_uri}..." }
|
|
180
181
|
if (connection = selector.find_connection(request_uri, options))
|
|
181
182
|
connection.idling if connection.state == :closed
|
|
182
183
|
connection.log(level: 2) { "found connection##{connection.object_id}(#{connection.state}) in selector##{selector.object_id}" }
|
|
@@ -194,7 +195,7 @@ module HTTPX
|
|
|
194
195
|
if options.io
|
|
195
196
|
select_connection(connection, selector)
|
|
196
197
|
else
|
|
197
|
-
|
|
198
|
+
pin(connection, selector)
|
|
198
199
|
end
|
|
199
200
|
when :closing, :closed
|
|
200
201
|
connection.idling
|
|
@@ -205,7 +206,7 @@ module HTTPX
|
|
|
205
206
|
resolve_connection(connection, selector)
|
|
206
207
|
end
|
|
207
208
|
else
|
|
208
|
-
|
|
209
|
+
pin(connection, selector)
|
|
209
210
|
end
|
|
210
211
|
|
|
211
212
|
connection
|
|
@@ -224,13 +225,7 @@ module HTTPX
|
|
|
224
225
|
|
|
225
226
|
# tries deactivating connections in the +selector+, deregistering the ones that have been deactivated.
|
|
226
227
|
def deactivate(selector)
|
|
227
|
-
selector.each_connection.
|
|
228
|
-
c.deactivate
|
|
229
|
-
|
|
230
|
-
c.state == :inactive
|
|
231
|
-
end.each do |c| # rubocop:disable Style/MultilineBlockChain
|
|
232
|
-
deselect_connection(c, selector)
|
|
233
|
-
end
|
|
228
|
+
selector.each_connection.to_a.each(&:deactivate)
|
|
234
229
|
end
|
|
235
230
|
|
|
236
231
|
# callback executed when an HTTP/2 promise frame has been received.
|
|
@@ -395,6 +390,7 @@ module HTTPX
|
|
|
395
390
|
|
|
396
391
|
resolver = find_resolver_for(connection, selector)
|
|
397
392
|
|
|
393
|
+
pin(connection, selector)
|
|
398
394
|
resolver.early_resolve(connection) || resolver.lazy_resolve(connection)
|
|
399
395
|
end
|
|
400
396
|
|
|
@@ -402,6 +398,9 @@ module HTTPX
|
|
|
402
398
|
from_pool = false
|
|
403
399
|
found_connection = selector.find_mergeable_connection(connection) || begin
|
|
404
400
|
from_pool = true
|
|
401
|
+
connection.log(level: 2) do
|
|
402
|
+
"try finding a mergeable connection in pool##{@pool.object_id}"
|
|
403
|
+
end
|
|
405
404
|
@pool.checkout_mergeable_connection(connection)
|
|
406
405
|
end
|
|
407
406
|
|
|
@@ -409,29 +408,21 @@ module HTTPX
|
|
|
409
408
|
|
|
410
409
|
connection.log(level: 2) do
|
|
411
410
|
"try coalescing from #{from_pool ? "pool##{@pool.object_id}" : "selector##{selector.object_id}"} " \
|
|
412
|
-
"(
|
|
411
|
+
"(connection##{found_connection.object_id}[#{found_connection.origin}])"
|
|
413
412
|
end
|
|
414
413
|
|
|
415
414
|
coalesce_connections(found_connection, connection, selector, from_pool)
|
|
416
415
|
end
|
|
417
416
|
|
|
418
|
-
def on_resolver_close(resolver, selector)
|
|
419
|
-
return if resolver.closed?
|
|
420
|
-
|
|
421
|
-
deselect_resolver(resolver, selector)
|
|
422
|
-
resolver.close unless resolver.closed?
|
|
423
|
-
end
|
|
424
|
-
|
|
425
417
|
def find_resolver_for(connection, selector)
|
|
426
418
|
if (resolver = selector.find_resolver(connection.options))
|
|
427
|
-
resolver.log(level: 2) { "found resolver##{
|
|
419
|
+
resolver.log(level: 2) { "found resolver##{resolver.object_id}(#{resolver.state}) in selector##{selector.object_id}" }
|
|
428
420
|
return resolver
|
|
429
421
|
end
|
|
430
422
|
|
|
431
423
|
resolver = @pool.checkout_resolver(connection.options)
|
|
432
|
-
resolver.log(level: 2) { "found resolver##{
|
|
433
|
-
resolver
|
|
434
|
-
resolver.current_selector = selector
|
|
424
|
+
resolver.log(level: 2) { "found resolver##{resolver.object_id}(#{resolver.state}) in pool##{@pool.object_id}" }
|
|
425
|
+
pin(resolver, selector)
|
|
435
426
|
|
|
436
427
|
resolver
|
|
437
428
|
end
|
|
@@ -446,14 +437,13 @@ module HTTPX
|
|
|
446
437
|
conn1.log(level: 2) { "check-in connection##{conn1.object_id}(#{conn1.state}) in pool##{@pool.object_id}" }
|
|
447
438
|
@pool.checkin_connection(conn1)
|
|
448
439
|
end
|
|
449
|
-
return
|
|
440
|
+
return
|
|
450
441
|
end
|
|
451
442
|
|
|
452
|
-
conn2.log(level: 2) { "coalescing with
|
|
453
|
-
conn2.coalesce!(conn1)
|
|
443
|
+
conn2.log(level: 2) { "coalescing with connection##{conn1.object_id}[#{conn1.origin}])" }
|
|
454
444
|
select_connection(conn1, selector) if from_pool
|
|
445
|
+
conn2.coalesce!(conn1)
|
|
455
446
|
conn2.disconnect
|
|
456
|
-
true
|
|
457
447
|
end
|
|
458
448
|
|
|
459
449
|
def get_current_selector
|
|
@@ -482,6 +472,7 @@ module HTTPX
|
|
|
482
472
|
th.thread_variable_get(:httpx_persistent_selector_store)
|
|
483
473
|
end
|
|
484
474
|
|
|
475
|
+
Options.freeze
|
|
485
476
|
@default_options = Options.new
|
|
486
477
|
@default_options.freeze
|
|
487
478
|
@plugins = []
|
|
@@ -503,7 +494,6 @@ module HTTPX
|
|
|
503
494
|
#
|
|
504
495
|
def plugin(pl, options = nil, &block)
|
|
505
496
|
label = pl
|
|
506
|
-
# raise Error, "Cannot add a plugin to a frozen config" if frozen?
|
|
507
497
|
pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
|
|
508
498
|
raise ArgumentError, "Invalid plugin type: #{pl.class.inspect}" unless pl.is_a?(Module)
|
|
509
499
|
|
|
@@ -10,15 +10,20 @@ module HTTPX
|
|
|
10
10
|
def deflate(chunk)
|
|
11
11
|
@deflater ||= Zlib::Deflate.new
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
else
|
|
20
|
-
@deflater.deflate(chunk)
|
|
13
|
+
unless chunk.nil?
|
|
14
|
+
chunk = @deflater.deflate(chunk)
|
|
15
|
+
|
|
16
|
+
# deflate call may return nil, while still
|
|
17
|
+
# retaining the last chunk in the deflater.
|
|
18
|
+
return chunk unless chunk.empty?
|
|
21
19
|
end
|
|
20
|
+
|
|
21
|
+
return if @deflater.closed?
|
|
22
|
+
|
|
23
|
+
last = @deflater.finish
|
|
24
|
+
@deflater.close
|
|
25
|
+
|
|
26
|
+
last unless last.empty?
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
|
|
@@ -64,7 +64,7 @@ module HTTPX::Transcoder
|
|
|
64
64
|
else
|
|
65
65
|
require "json"
|
|
66
66
|
def json_load(*args); ::JSON.parse(*args); end
|
|
67
|
-
def json_dump(*args); ::JSON.
|
|
67
|
+
def json_dump(*args); ::JSON.generate(*args); end
|
|
68
68
|
end
|
|
69
69
|
# rubocop:enable Style/SingleLineMethods
|
|
70
70
|
end
|
|
@@ -60,7 +60,7 @@ module HTTPX
|
|
|
60
60
|
when :idle
|
|
61
61
|
raise Error, "payload does not start with boundary" unless @buffer.start_with?("#{@intermediate_boundary}#{CRLF}")
|
|
62
62
|
|
|
63
|
-
@buffer = @buffer.byteslice(@intermediate_boundary.bytesize + 2..-1)
|
|
63
|
+
@buffer = @buffer.byteslice((@intermediate_boundary.bytesize + 2)..-1)
|
|
64
64
|
|
|
65
65
|
@state = :part_header
|
|
66
66
|
when :part_header
|
|
@@ -70,7 +70,7 @@ module HTTPX
|
|
|
70
70
|
return unless idx
|
|
71
71
|
|
|
72
72
|
# @type var head: String
|
|
73
|
-
head = @buffer.byteslice(0..idx + 4 - 1)
|
|
73
|
+
head = @buffer.byteslice(0..(idx + 4 - 1))
|
|
74
74
|
|
|
75
75
|
@buffer = @buffer.byteslice(head.bytesize..-1)
|
|
76
76
|
|
|
@@ -107,8 +107,8 @@ module HTTPX
|
|
|
107
107
|
idx = @buffer.index(body_separator)
|
|
108
108
|
|
|
109
109
|
if idx
|
|
110
|
-
payload = @buffer.byteslice(0..idx - 1)
|
|
111
|
-
@buffer = @buffer.byteslice(idx + body_separator.bytesize..-1)
|
|
110
|
+
payload = @buffer.byteslice(0..(idx - 1))
|
|
111
|
+
@buffer = @buffer.byteslice((idx + body_separator.bytesize)..-1)
|
|
112
112
|
part << payload
|
|
113
113
|
part.rewind if part.respond_to?(:rewind)
|
|
114
114
|
@state = :parse_boundary
|
|
@@ -53,7 +53,7 @@ module HTTPX
|
|
|
53
53
|
|
|
54
54
|
def to_parts(form)
|
|
55
55
|
params = form.each_with_object([]) do |(key, val), aux|
|
|
56
|
-
Transcoder.normalize_keys(key, val
|
|
56
|
+
Transcoder::Multipart.normalize_keys(key, val) do |k, v|
|
|
57
57
|
next if v.nil?
|
|
58
58
|
|
|
59
59
|
value, content_type, filename = Part.call(v)
|
|
@@ -7,20 +7,28 @@ require_relative "multipart/mime_type_detector"
|
|
|
7
7
|
|
|
8
8
|
module HTTPX::Transcoder
|
|
9
9
|
module Multipart
|
|
10
|
-
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def multipart?(form_data)
|
|
13
|
+
form_data.any? do |_, v|
|
|
14
|
+
multipart_value?(v) ||
|
|
15
|
+
(v.respond_to?(:to_ary) && v.to_ary.any? { |av| multipart_value?(av) }) ||
|
|
16
|
+
(v.respond_to?(:to_hash) && v.to_hash.any? { |_, e| multipart_value?(e) })
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def multipart_value?(value)
|
|
11
21
|
value.respond_to?(:read) ||
|
|
12
22
|
(value.respond_to?(:to_hash) &&
|
|
13
23
|
value.key?(:body) &&
|
|
14
24
|
(value.key?(:filename) || value.key?(:content_type)))
|
|
15
25
|
end
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
(v.respond_to?(:to_ary) && v.to_ary.any?(&Multipart::MULTIPART_VALUE_COND)) ||
|
|
23
|
-
(v.respond_to?(:to_hash) && v.to_hash.any? { |_, e| Multipart::MULTIPART_VALUE_COND.call(e) })
|
|
27
|
+
def normalize_keys(key, value, transcoder = self, &block)
|
|
28
|
+
if multipart_value?(value)
|
|
29
|
+
block.call(key.to_s, value)
|
|
30
|
+
else
|
|
31
|
+
HTTPX::Transcoder.normalize_keys(key, value, transcoder, &block)
|
|
24
32
|
end
|
|
25
33
|
end
|
|
26
34
|
|