httpx 1.6.3 → 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 +2 -2
- 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/altsvc.rb +3 -1
- data/lib/httpx/connection/http1.rb +5 -6
- data/lib/httpx/connection/http2.rb +2 -0
- data/lib/httpx/connection.rb +3 -8
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/loggable.rb +2 -0
- data/lib/httpx/options.rb +40 -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/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/rate_limiter.rb +2 -2
- data/lib/httpx/plugins/response_cache.rb +3 -7
- 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 +73 -17
- data/lib/httpx/pool.rb +12 -2
- data/lib/httpx/request.rb +10 -1
- data/lib/httpx/resolver/https.rb +67 -17
- data/lib/httpx/resolver/multi.rb +4 -0
- data/lib/httpx/resolver/native.rb +26 -4
- data/lib/httpx/resolver/resolver.rb +2 -2
- 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 +9 -0
- data/lib/httpx/session.rb +6 -6
- data/lib/httpx/transcoder/body.rb +1 -1
- 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.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 +1 -3
- data/sig/options.rbs +1 -1
- 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/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 +2 -2
- data/sig/pool.rbs +1 -1
- data/sig/resolver/https.rbs +5 -0
- data/sig/resolver/multi.rbs +2 -0
- data/sig/resolver/native.rbs +2 -0
- data/sig/resolver.rbs +12 -3
- data/sig/response.rbs +3 -0
- data/sig/session.rbs +3 -5
- data/sig/transcoder/multipart.rbs +4 -2
- data/sig/transcoder.rbs +5 -1
- metadata +3 -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
|
@@ -199,6 +199,12 @@ module HTTPX
|
|
|
199
199
|
def select_many(r, w, interval, &block)
|
|
200
200
|
begin
|
|
201
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
|
|
202
208
|
rescue StandardError => e
|
|
203
209
|
(Array(r) + Array(w)).each do |sel|
|
|
204
210
|
sel.on_error(e)
|
|
@@ -240,6 +246,9 @@ module HTTPX
|
|
|
240
246
|
when :w then io.to_io.wait_writable(interval)
|
|
241
247
|
when :rw then rw_wait(io, interval)
|
|
242
248
|
end
|
|
249
|
+
rescue IOError => e
|
|
250
|
+
io.on_error(e)
|
|
251
|
+
io.force_close(true)
|
|
243
252
|
rescue StandardError => e
|
|
244
253
|
io.on_error(e)
|
|
245
254
|
|
data/lib/httpx/session.rb
CHANGED
|
@@ -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}" }
|
|
@@ -224,7 +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.each(&:deactivate)
|
|
228
|
+
selector.each_connection.to_a.each(&:deactivate)
|
|
228
229
|
end
|
|
229
230
|
|
|
230
231
|
# callback executed when an HTTP/2 promise frame has been received.
|
|
@@ -415,12 +416,12 @@ module HTTPX
|
|
|
415
416
|
|
|
416
417
|
def find_resolver_for(connection, selector)
|
|
417
418
|
if (resolver = selector.find_resolver(connection.options))
|
|
418
|
-
resolver.log(level: 2) { "found resolver##{
|
|
419
|
+
resolver.log(level: 2) { "found resolver##{resolver.object_id}(#{resolver.state}) in selector##{selector.object_id}" }
|
|
419
420
|
return resolver
|
|
420
421
|
end
|
|
421
422
|
|
|
422
423
|
resolver = @pool.checkout_resolver(connection.options)
|
|
423
|
-
resolver.log(level: 2) { "found resolver##{
|
|
424
|
+
resolver.log(level: 2) { "found resolver##{resolver.object_id}(#{resolver.state}) in pool##{@pool.object_id}" }
|
|
424
425
|
pin(resolver, selector)
|
|
425
426
|
|
|
426
427
|
resolver
|
|
@@ -436,14 +437,13 @@ module HTTPX
|
|
|
436
437
|
conn1.log(level: 2) { "check-in connection##{conn1.object_id}(#{conn1.state}) in pool##{@pool.object_id}" }
|
|
437
438
|
@pool.checkin_connection(conn1)
|
|
438
439
|
end
|
|
439
|
-
return
|
|
440
|
+
return
|
|
440
441
|
end
|
|
441
442
|
|
|
442
443
|
conn2.log(level: 2) { "coalescing with connection##{conn1.object_id}[#{conn1.origin}])" }
|
|
443
444
|
select_connection(conn1, selector) if from_pool
|
|
444
445
|
conn2.coalesce!(conn1)
|
|
445
446
|
conn2.disconnect
|
|
446
|
-
true
|
|
447
447
|
end
|
|
448
448
|
|
|
449
449
|
def get_current_selector
|
|
@@ -472,6 +472,7 @@ module HTTPX
|
|
|
472
472
|
th.thread_variable_get(:httpx_persistent_selector_store)
|
|
473
473
|
end
|
|
474
474
|
|
|
475
|
+
Options.freeze
|
|
475
476
|
@default_options = Options.new
|
|
476
477
|
@default_options.freeze
|
|
477
478
|
@plugins = []
|
|
@@ -493,7 +494,6 @@ module HTTPX
|
|
|
493
494
|
#
|
|
494
495
|
def plugin(pl, options = nil, &block)
|
|
495
496
|
label = pl
|
|
496
|
-
# raise Error, "Cannot add a plugin to a frozen config" if frozen?
|
|
497
497
|
pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
|
|
498
498
|
raise ArgumentError, "Invalid plugin type: #{pl.class.inspect}" unless pl.is_a?(Module)
|
|
499
499
|
|
|
@@ -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
|
|
data/lib/httpx/transcoder.rb
CHANGED
|
@@ -4,20 +4,18 @@ module HTTPX
|
|
|
4
4
|
module Transcoder
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
def normalize_keys(key, value,
|
|
8
|
-
if
|
|
9
|
-
block.call(key.to_s, value)
|
|
10
|
-
elsif value.respond_to?(:to_ary)
|
|
7
|
+
def normalize_keys(key, value, transcoder = self, &block)
|
|
8
|
+
if value.respond_to?(:to_ary)
|
|
11
9
|
if value.empty?
|
|
12
10
|
block.call("#{key}[]")
|
|
13
11
|
else
|
|
14
12
|
value.to_ary.each do |element|
|
|
15
|
-
normalize_keys("#{key}[]", element,
|
|
13
|
+
transcoder.normalize_keys("#{key}[]", element, transcoder, &block)
|
|
16
14
|
end
|
|
17
15
|
end
|
|
18
16
|
elsif value.respond_to?(:to_hash)
|
|
19
17
|
value.to_hash.each do |child_key, child_value|
|
|
20
|
-
normalize_keys("#{key}[#{child_key}]", child_value,
|
|
18
|
+
transcoder.normalize_keys("#{key}[#{child_key}]", child_value, transcoder, &block)
|
|
21
19
|
end
|
|
22
20
|
else
|
|
23
21
|
block.call(key.to_s, value)
|
data/lib/httpx/version.rb
CHANGED
data/sig/altsvc.rbs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module HTTPX
|
|
2
2
|
module AltSvc
|
|
3
3
|
module ConnectionMixin
|
|
4
|
+
H2_ALTSVC_SCHEMES: Array[String]
|
|
4
5
|
|
|
5
6
|
def send: (Request request) -> void
|
|
6
7
|
|
|
@@ -11,6 +12,8 @@ module HTTPX
|
|
|
11
12
|
def match_altsvcs?: (URI::Generic uri) -> bool
|
|
12
13
|
|
|
13
14
|
def match_altsvc_options?: (URI::Generic uri, Options options) -> bool
|
|
15
|
+
|
|
16
|
+
def altsvc_match?: (uri uri, uri other_uri) -> bool
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
type altsvc_params = Hash[String, untyped]
|
data/sig/chainable.rbs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module HTTPX
|
|
2
2
|
module Chainable
|
|
3
|
-
def request: (
|
|
4
|
-
| (Request, **untyped) -> response
|
|
3
|
+
def request: (Request, **untyped) -> response
|
|
4
|
+
| (*Request, **untyped) -> Array[response]
|
|
5
5
|
| (verb, uri | [uri], **untyped) -> response
|
|
6
6
|
| (Array[[verb, uri] | [verb, uri, request_params]], **untyped) -> Array[response]
|
|
7
7
|
| (verb, _Each[uri | [uri, request_params]], **untyped) -> Array[response]
|
|
@@ -12,7 +12,7 @@ module HTTPX
|
|
|
12
12
|
def with: (options) -> Session
|
|
13
13
|
| (options) { (Session) -> void } -> void
|
|
14
14
|
|
|
15
|
-
def plugin: (:auth, ?options) -> Plugins::
|
|
15
|
+
def plugin: (:auth, ?options) -> Plugins::sessionAuth
|
|
16
16
|
| (:basic_auth, ?options) -> Plugins::sessionBasicAuth
|
|
17
17
|
| (:digest_auth, ?options) -> Plugins::sessionDigestAuth
|
|
18
18
|
| (:ntlm_auth, ?options) -> Plugins::sessionNTLMAuth
|
data/sig/connection.rbs
CHANGED
|
@@ -61,7 +61,7 @@ module HTTPX
|
|
|
61
61
|
|
|
62
62
|
def addresses?: () -> boolish
|
|
63
63
|
|
|
64
|
-
def match?: (
|
|
64
|
+
def match?: (http_uri uri, Options options) -> bool
|
|
65
65
|
|
|
66
66
|
def mergeable?: (Connection connection) -> bool
|
|
67
67
|
|
|
@@ -71,8 +71,6 @@ module HTTPX
|
|
|
71
71
|
|
|
72
72
|
def coalescable?: (Connection connection) -> bool
|
|
73
73
|
|
|
74
|
-
def create_idle: (?Hash[Symbol, untyped] options) -> instance
|
|
75
|
-
|
|
76
74
|
def merge: (Connection connection) -> void
|
|
77
75
|
|
|
78
76
|
def purge_pending: () { (Request request) -> void } -> void
|
data/sig/options.rbs
CHANGED
|
@@ -138,7 +138,7 @@ module HTTPX
|
|
|
138
138
|
|
|
139
139
|
def options_equals?: (Options other, ?Array[Symbol] ignore_ivars) -> bool
|
|
140
140
|
|
|
141
|
-
def merge: (_ToHash[Symbol, untyped] other) -> instance
|
|
141
|
+
def merge: (Object & _ToHash[Symbol, untyped] other) -> (instance | self)
|
|
142
142
|
|
|
143
143
|
def to_hash: () -> Hash[Symbol, untyped]
|
|
144
144
|
|
data/sig/plugins/auth/digest.rbs
CHANGED
|
@@ -2,9 +2,13 @@ module HTTPX
|
|
|
2
2
|
module Plugins
|
|
3
3
|
module Authentication
|
|
4
4
|
class Digest
|
|
5
|
+
Error: singleton(HTTPX::Error)
|
|
6
|
+
|
|
7
|
+
|
|
5
8
|
@user: String
|
|
6
9
|
@password: String
|
|
7
10
|
@hashed: bool
|
|
11
|
+
@nonce: Integer
|
|
8
12
|
|
|
9
13
|
def can_authenticate?: (String? authenticate) -> boolish
|
|
10
14
|
|
|
@@ -19,6 +23,8 @@ module HTTPX
|
|
|
19
23
|
def make_cnonce: () -> String
|
|
20
24
|
|
|
21
25
|
def next_nonce: () -> Integer
|
|
26
|
+
|
|
27
|
+
def raise_format_error: () -> void
|
|
22
28
|
end
|
|
23
29
|
end
|
|
24
30
|
end
|
data/sig/plugins/auth.rbs
CHANGED
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
module HTTPX
|
|
2
2
|
module Plugins
|
|
3
|
-
module
|
|
3
|
+
module Auth
|
|
4
|
+
interface _AuthOptions
|
|
5
|
+
def auth_header_value: () -> (String | ^(Request request) -> string)?
|
|
6
|
+
|
|
7
|
+
def auth_header_type: () -> String?
|
|
8
|
+
end
|
|
9
|
+
|
|
4
10
|
module InstanceMethods
|
|
5
|
-
|
|
11
|
+
@auth_header_value: String
|
|
12
|
+
@skip_auth_header_value: bool
|
|
13
|
+
|
|
14
|
+
def authorization: (?string token, ?auth_header_type: string) ?{ (Request) -> string } -> instance
|
|
15
|
+
|
|
16
|
+
def bearer_auth: (?string token) ?{ (Request) -> string } -> instance
|
|
17
|
+
|
|
18
|
+
def skip_auth_header: [T] { () -> T } -> T
|
|
19
|
+
|
|
20
|
+
def reset_auth_header_value!: () -> void
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def generate_auth_token: () -> String?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module RequestMethods
|
|
28
|
+
def authorize: (String auth_value) -> void
|
|
29
|
+
end
|
|
6
30
|
|
|
7
|
-
|
|
31
|
+
module AuthRetries
|
|
8
32
|
end
|
|
9
33
|
end
|
|
10
34
|
|
|
11
|
-
type
|
|
35
|
+
type sessionAuth = Session & Auth::InstanceMethods
|
|
12
36
|
end
|
|
13
37
|
end
|
data/sig/plugins/basic_auth.rbs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
module HTTPX
|
|
2
2
|
module Plugins
|
|
3
3
|
module BasicAuth
|
|
4
|
-
def self.load_dependencies: (singleton(Session)) -> void
|
|
4
|
+
def self.load_dependencies: (singleton(Session) klass) -> void
|
|
5
5
|
|
|
6
|
-
def self.configure: (singleton(Session)) -> void
|
|
6
|
+
def self.configure: (singleton(Session) klass) -> void
|
|
7
7
|
|
|
8
8
|
module InstanceMethods
|
|
9
9
|
def basic_auth: (string user, string password) -> instance
|
|
10
10
|
end
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
type sessionBasicAuth =
|
|
13
|
+
type sessionBasicAuth = sessionAuth & BasicAuth::InstanceMethods
|
|
14
14
|
end
|
|
15
15
|
end
|
data/sig/plugins/digest_auth.rbs
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
module HTTPX
|
|
2
2
|
module Plugins
|
|
3
3
|
module DigestAuth
|
|
4
|
-
DigestError: singleton(Error)
|
|
5
|
-
|
|
6
4
|
interface _DigestOptions
|
|
7
5
|
def digest: () -> Authentication::Digest?
|
|
8
6
|
end
|
|
9
7
|
|
|
10
8
|
def self.extra_options: (Options) -> (Options & _DigestOptions)
|
|
11
9
|
|
|
12
|
-
def self.load_dependencies: (
|
|
10
|
+
def self.load_dependencies: (singleton(Session) klass) -> void
|
|
13
11
|
|
|
14
12
|
module InstanceMethods
|
|
15
13
|
def digest_auth: (string user, string password, ?hashed: bool) -> instance
|
|
16
14
|
end
|
|
17
15
|
end
|
|
18
16
|
|
|
19
|
-
type sessionDigestAuth =
|
|
17
|
+
type sessionDigestAuth = sessionAuth & DigestAuth::InstanceMethods
|
|
20
18
|
end
|
|
21
19
|
end
|