httpx 1.6.3 → 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/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/doc/release_notes/1_7_1.md +21 -0
- data/lib/httpx/adapters/datadog.rb +1 -1
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/adapters/webmock.rb +18 -9
- data/lib/httpx/altsvc.rb +4 -2
- data/lib/httpx/connection/http1.rb +9 -9
- data/lib/httpx/connection/http2.rb +2 -0
- data/lib/httpx/connection.rb +7 -9
- data/lib/httpx/domain_name.rb +1 -1
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/loggable.rb +2 -0
- data/lib/httpx/options.rb +118 -22
- data/lib/httpx/parser/http1.rb +1 -0
- data/lib/httpx/plugins/auth/digest.rb +44 -4
- data/lib/httpx/plugins/auth.rb +113 -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 +156 -57
- 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/response_cache.rb +3 -7
- data/lib/httpx/plugins/retries.rb +60 -24
- data/lib/httpx/plugins/ssrf_filter.rb +1 -1
- data/lib/httpx/plugins/stream.rb +60 -9
- data/lib/httpx/plugins/stream_bidi.rb +84 -16
- data/lib/httpx/pool.rb +12 -3
- data/lib/httpx/request/body.rb +1 -1
- data/lib/httpx/request.rb +10 -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 +74 -20
- data/lib/httpx/resolver/multi.rb +10 -2
- data/lib/httpx/resolver/native.rb +32 -6
- data/lib/httpx/resolver/resolver.rb +3 -3
- data/lib/httpx/resolver.rb +36 -114
- data/lib/httpx/response/body.rb +5 -3
- data/lib/httpx/response.rb +22 -6
- data/lib/httpx/selector.rb +14 -3
- data/lib/httpx/session.rb +6 -6
- data/lib/httpx/timers.rb +6 -12
- data/lib/httpx/transcoder/body.rb +1 -1
- data/lib/httpx/transcoder/gzip.rb +7 -2
- data/lib/httpx/transcoder/json.rb +1 -1
- data/lib/httpx/transcoder/multipart/decoder.rb +5 -5
- data/lib/httpx/transcoder/multipart/encoder.rb +1 -1
- data/lib/httpx/transcoder/multipart.rb +17 -9
- data/lib/httpx/transcoder.rb +4 -6
- data/lib/httpx/utils.rb +13 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/altsvc.rbs +9 -3
- data/sig/chainable.rbs +3 -3
- data/sig/connection.rbs +1 -3
- data/sig/loggable.rbs +1 -1
- data/sig/options.rbs +12 -4
- data/sig/plugins/auth/digest.rbs +6 -0
- data/sig/plugins/auth.rbs +37 -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 +44 -15
- data/sig/plugins/rate_limiter.rbs +4 -2
- data/sig/plugins/response_cache/file_store.rbs +2 -0
- data/sig/plugins/response_cache.rbs +4 -0
- data/sig/plugins/retries.rbs +12 -4
- data/sig/plugins/stream.rbs +13 -3
- data/sig/plugins/stream_bidi.rbs +2 -2
- data/sig/pool.rbs +1 -1
- 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 +24 -0
- data/sig/resolver/multi.rbs +8 -0
- data/sig/resolver/native.rbs +2 -0
- data/sig/resolver.rbs +5 -20
- data/sig/response.rbs +3 -0
- data/sig/session.rbs +3 -5
- data/sig/timers.rbs +1 -1
- data/sig/transcoder/multipart.rbs +4 -2
- data/sig/transcoder.rbs +5 -1
- data/sig/utils.rbs +2 -0
- metadata +11 -1
data/lib/httpx/options.rb
CHANGED
|
@@ -12,6 +12,7 @@ module HTTPX
|
|
|
12
12
|
CLOSE_HANDSHAKE_TIMEOUT = 10
|
|
13
13
|
CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
|
|
14
14
|
REQUEST_TIMEOUT = OPERATION_TIMEOUT = nil
|
|
15
|
+
RESOLVER_TYPES = %i[memory file].freeze
|
|
15
16
|
|
|
16
17
|
# default value used for "user-agent" header, when not overridden.
|
|
17
18
|
USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
|
|
@@ -44,7 +45,7 @@ module HTTPX
|
|
|
44
45
|
|
|
45
46
|
return unless meth =~ /^option_(.+)$/
|
|
46
47
|
|
|
47
|
-
optname = Regexp.last_match(1)
|
|
48
|
+
optname = Regexp.last_match(1) #: String
|
|
48
49
|
|
|
49
50
|
if optname =~ /^(.+[^_])_+with/
|
|
50
51
|
# ignore alias method chain generated methods.
|
|
@@ -52,14 +53,14 @@ module HTTPX
|
|
|
52
53
|
# it relies on the "_with/_without" separator, which is the most used convention,
|
|
53
54
|
# however it shouldn't be used in practice in httpx given the plugin architecture
|
|
54
55
|
# as the main extension API.
|
|
55
|
-
orig_name = Regexp.last_match(1)
|
|
56
|
+
orig_name = Regexp.last_match(1) #: String
|
|
56
57
|
|
|
57
58
|
return if @options_names.include?(orig_name.to_sym)
|
|
58
59
|
end
|
|
59
60
|
|
|
60
61
|
optname = optname.to_sym
|
|
61
62
|
|
|
62
|
-
attr_reader(optname)
|
|
63
|
+
attr_reader(optname) unless method_defined?(optname)
|
|
63
64
|
|
|
64
65
|
@options_names << optname unless @options_names.include?(optname)
|
|
65
66
|
end
|
|
@@ -103,7 +104,10 @@ module HTTPX
|
|
|
103
104
|
# :io :: open socket, or domain/ip-to-socket hash, which requests should be sent to
|
|
104
105
|
# :persistent :: whether to persist connections in between requests (defaults to <tt>true</tt>)
|
|
105
106
|
# :resolver_class :: which resolver to use (defaults to <tt>:native</tt>, can also be <tt>:system<tt> for
|
|
106
|
-
# using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class
|
|
107
|
+
# using getaddrinfo or <tt>:https</tt> for DoH resolver, or a custom class inheriting
|
|
108
|
+
# from HTTPX::Resolver::Resolver)
|
|
109
|
+
# :resolver_cache :: strategy to cache DNS results, ignored by the <tt>:system</tt> resolver, can be set to <tt>:memory<tt>
|
|
110
|
+
# or an instance of a custom class inheriting from HTTPX::Resolver::Cache::Base
|
|
107
111
|
# :resolver_options :: hash of options passed to the resolver. Accepted keys depend on the resolver type.
|
|
108
112
|
# :pool_options :: hash of options passed to the connection pool (See Pool#initialize).
|
|
109
113
|
# :ip_families :: which socket families are supported (system-dependent)
|
|
@@ -151,6 +155,36 @@ module HTTPX
|
|
|
151
155
|
freeze
|
|
152
156
|
end
|
|
153
157
|
|
|
158
|
+
# returns the class with which to instantiate the DNS resolver.
|
|
159
|
+
def resolver_class
|
|
160
|
+
case @resolver_class
|
|
161
|
+
when Symbol
|
|
162
|
+
public_send(:"resolver_#{@resolver_class}_class")
|
|
163
|
+
else
|
|
164
|
+
@resolver_class
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def resolver_cache
|
|
169
|
+
cache_type = @resolver_cache
|
|
170
|
+
|
|
171
|
+
case cache_type
|
|
172
|
+
when :memory
|
|
173
|
+
Resolver::Cache::Memory.cache(cache_type)
|
|
174
|
+
when :file
|
|
175
|
+
Resolver::Cache::File.cache(cache_type)
|
|
176
|
+
else
|
|
177
|
+
unless cache_type.respond_to?(:resolve) &&
|
|
178
|
+
cache_type.respond_to?(:get) &&
|
|
179
|
+
cache_type.respond_to?(:set) &&
|
|
180
|
+
cache_type.respond_to?(:evict)
|
|
181
|
+
raise TypeError, ":resolver_cache must be a compatible resolver cache and implement #get, #set and #evict"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
cache_type #: Object & Resolver::_Cache
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
154
188
|
def freeze
|
|
155
189
|
self.class.options_names.each do |ivar|
|
|
156
190
|
# avoid freezing debug option, as when it's set, it's usually an
|
|
@@ -172,6 +206,7 @@ module HTTPX
|
|
|
172
206
|
super || options_equals?(other)
|
|
173
207
|
end
|
|
174
208
|
|
|
209
|
+
# checks whether +other+ is equal by comparing the session options
|
|
175
210
|
def options_equals?(other, ignore_ivars = REQUEST_BODY_IVARS)
|
|
176
211
|
# headers and other request options do not play a role, as they are
|
|
177
212
|
# relevant only for the request.
|
|
@@ -187,34 +222,43 @@ module HTTPX
|
|
|
187
222
|
end
|
|
188
223
|
end
|
|
189
224
|
|
|
225
|
+
# returns a HTTPX::Options instance resulting of the merging of +other+ with self.
|
|
226
|
+
# it may return self if +other+ is self or equal to self.
|
|
190
227
|
def merge(other)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
228
|
+
if (is_options = other.is_a?(Options))
|
|
229
|
+
|
|
230
|
+
return self if eql?(other)
|
|
231
|
+
|
|
232
|
+
opts_names = other.class.options_names
|
|
233
|
+
|
|
234
|
+
return self if opts_names.all? { |opt| public_send(opt) == other.public_send(opt) }
|
|
235
|
+
|
|
236
|
+
other_opts = opts_names
|
|
237
|
+
else
|
|
238
|
+
other_opts = other # : Hash[Symbol, untyped]
|
|
239
|
+
other_opts = Hash[other] unless other.is_a?(Hash)
|
|
200
240
|
|
|
201
|
-
|
|
241
|
+
return self if other_opts.empty?
|
|
202
242
|
|
|
203
|
-
|
|
243
|
+
return self if other_opts.all? { |opt, v| !respond_to?(opt) || public_send(opt) == v }
|
|
244
|
+
end
|
|
204
245
|
|
|
205
246
|
opts = dup
|
|
206
247
|
|
|
207
|
-
|
|
208
|
-
|
|
248
|
+
other_opts.each do |opt, v|
|
|
249
|
+
next unless respond_to?(opt)
|
|
250
|
+
|
|
251
|
+
v = other.public_send(opt) if is_options
|
|
252
|
+
ivar = :"@#{opt}"
|
|
209
253
|
|
|
210
254
|
unless v
|
|
211
255
|
opts.instance_variable_set(ivar, v)
|
|
212
256
|
next
|
|
213
257
|
end
|
|
214
258
|
|
|
215
|
-
v = opts.__send__(:"option_#{
|
|
259
|
+
v = opts.__send__(:"option_#{opt}", v)
|
|
216
260
|
|
|
217
|
-
orig_v =
|
|
261
|
+
orig_v = public_send(opt)
|
|
218
262
|
|
|
219
263
|
v = orig_v.merge(v) if orig_v.respond_to?(:merge) && v.respond_to?(:merge)
|
|
220
264
|
|
|
@@ -354,7 +398,7 @@ module HTTPX
|
|
|
354
398
|
request_class response_class headers_class request_body_class
|
|
355
399
|
response_body_class connection_class http1_class http2_class
|
|
356
400
|
resolver_native_class resolver_system_class resolver_https_class options_class pool_class
|
|
357
|
-
io fallback_protocol debug debug_redact
|
|
401
|
+
io fallback_protocol debug debug_redact
|
|
358
402
|
compress_request_body decompress_response_body
|
|
359
403
|
persistent close_on_fork
|
|
360
404
|
].each do |method_name|
|
|
@@ -379,7 +423,20 @@ module HTTPX
|
|
|
379
423
|
end
|
|
380
424
|
|
|
381
425
|
def option_timeout(value)
|
|
382
|
-
Hash[value]
|
|
426
|
+
timeout_hash = Hash[value]
|
|
427
|
+
|
|
428
|
+
default_timeouts = DEFAULT_OPTIONS[:timeout]
|
|
429
|
+
|
|
430
|
+
# Validate keys and values
|
|
431
|
+
timeout_hash.each do |key, val|
|
|
432
|
+
raise TypeError, "invalid timeout: :#{key}" unless default_timeouts.key?(key)
|
|
433
|
+
|
|
434
|
+
next if val.nil?
|
|
435
|
+
|
|
436
|
+
raise TypeError, ":#{key} must be numeric" unless val.is_a?(Numeric)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
timeout_hash
|
|
383
440
|
end
|
|
384
441
|
|
|
385
442
|
def option_supported_compression_formats(value)
|
|
@@ -401,6 +458,41 @@ module HTTPX
|
|
|
401
458
|
Array(value)
|
|
402
459
|
end
|
|
403
460
|
|
|
461
|
+
def option_resolver_class(resolver_type)
|
|
462
|
+
case resolver_type
|
|
463
|
+
when Symbol
|
|
464
|
+
meth = :"resolver_#{resolver_type}_class"
|
|
465
|
+
|
|
466
|
+
raise TypeError, ":resolver_class must be a supported type" unless respond_to?(meth)
|
|
467
|
+
|
|
468
|
+
resolver_type
|
|
469
|
+
when Class
|
|
470
|
+
raise TypeError, ":resolver_class must be a subclass of `#{Resolver::Resolver}`" unless resolver_type < Resolver::Resolver
|
|
471
|
+
|
|
472
|
+
resolver_type
|
|
473
|
+
else
|
|
474
|
+
raise TypeError, ":resolver_class must be a supported type"
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def option_resolver_cache(cache_type)
|
|
479
|
+
if cache_type.is_a?(Symbol)
|
|
480
|
+
raise TypeError, ":resolver_cache: #{cache_type} is invalid" unless RESOLVER_TYPES.include?(cache_type)
|
|
481
|
+
|
|
482
|
+
require "httpx/resolver/cache/file" if cache_type == :file
|
|
483
|
+
|
|
484
|
+
else
|
|
485
|
+
unless cache_type.respond_to?(:resolve) &&
|
|
486
|
+
cache_type.respond_to?(:get) &&
|
|
487
|
+
cache_type.respond_to?(:set) &&
|
|
488
|
+
cache_type.respond_to?(:evict)
|
|
489
|
+
raise TypeError, ":resolver_cache must be a compatible resolver cache and implement #resolve, #get, #set and #evict"
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
cache_type
|
|
494
|
+
end
|
|
495
|
+
|
|
404
496
|
# called after all options are initialized
|
|
405
497
|
def do_initialize
|
|
406
498
|
hs = @headers
|
|
@@ -424,6 +516,8 @@ module HTTPX
|
|
|
424
516
|
end
|
|
425
517
|
end
|
|
426
518
|
|
|
519
|
+
# rubocop:disable Lint/UselessConstantScoping
|
|
520
|
+
# these really need to be defined at the end of the class
|
|
427
521
|
SET_TEMPORARY_NAME = ->(klass, pl = nil) do
|
|
428
522
|
if klass.respond_to?(:set_temporary_name) # ruby 3.4 only
|
|
429
523
|
name = klass.name || "#{klass.superclass.name}(plugin)"
|
|
@@ -474,10 +568,12 @@ module HTTPX
|
|
|
474
568
|
:addresses => nil,
|
|
475
569
|
:persistent => false,
|
|
476
570
|
:resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
|
|
571
|
+
:resolver_cache => (ENV["HTTPX_RESOLVER_CACHE"] || :memory).to_sym,
|
|
477
572
|
:resolver_options => { cache: true }.freeze,
|
|
478
573
|
:pool_options => EMPTY_HASH,
|
|
479
574
|
:ip_families => nil,
|
|
480
575
|
:close_on_fork => false,
|
|
481
|
-
}.freeze
|
|
576
|
+
}.each_value(&:freeze).freeze
|
|
577
|
+
# rubocop:enable Lint/UselessConstantScoping
|
|
482
578
|
end
|
|
483
579
|
end
|
data/lib/httpx/parser/http1.rb
CHANGED
|
@@ -8,6 +8,8 @@ module HTTPX
|
|
|
8
8
|
module Plugins
|
|
9
9
|
module Authentication
|
|
10
10
|
class Digest
|
|
11
|
+
Error = Class.new(Error)
|
|
12
|
+
|
|
11
13
|
def initialize(user, password, hashed: false, **)
|
|
12
14
|
@user = user
|
|
13
15
|
@password = password
|
|
@@ -29,19 +31,53 @@ module HTTPX
|
|
|
29
31
|
# discard first token, it's Digest
|
|
30
32
|
auth_info = authenticate[/^(\w+) (.*)/, 2]
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
raise_format_error unless auth_info
|
|
35
|
+
|
|
36
|
+
s = StringScanner.new(auth_info)
|
|
37
|
+
|
|
38
|
+
params = {}
|
|
39
|
+
until s.eos?
|
|
40
|
+
k = s.scan_until(/=/)
|
|
41
|
+
raise_format_error unless k&.end_with?("=")
|
|
42
|
+
|
|
43
|
+
if s.peek(1) == "\""
|
|
44
|
+
s.skip("\"")
|
|
45
|
+
v = s.scan_until(/"/)
|
|
46
|
+
raise_format_error unless v&.end_with?("\"")
|
|
47
|
+
|
|
48
|
+
v = v[0..-2]
|
|
49
|
+
s.skip_until(/,/)
|
|
50
|
+
else
|
|
51
|
+
v = s.scan_until(/,|$/)
|
|
52
|
+
|
|
53
|
+
if v&.end_with?(",")
|
|
54
|
+
v = v[0..-2]
|
|
55
|
+
else
|
|
56
|
+
raise_format_error unless s.eos?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
v = v[0..-2] if v&.end_with?(",")
|
|
60
|
+
end
|
|
61
|
+
params[k[0..-2]] = v
|
|
62
|
+
s.skip(/\s/)
|
|
63
|
+
end
|
|
64
|
+
|
|
35
65
|
nonce = params["nonce"]
|
|
36
66
|
nc = next_nonce
|
|
37
67
|
|
|
38
68
|
# verify qop
|
|
39
69
|
qop = params["qop"]
|
|
40
70
|
|
|
71
|
+
if qop
|
|
72
|
+
# some servers send multiple values wrapped in parentheses (i.e. "(qauth,)")
|
|
73
|
+
qop = qop[/\(?([^)]+)\)?/, 1]
|
|
74
|
+
qop = qop.split(",").map { |s| s.delete_prefix("'").delete_suffix("'") }.delete_if(&:empty?).map.first
|
|
75
|
+
end
|
|
76
|
+
|
|
41
77
|
if params["algorithm"] =~ /(.*?)(-sess)?$/
|
|
42
78
|
alg = Regexp.last_match(1)
|
|
43
79
|
algorithm = ::Digest.const_get(alg)
|
|
44
|
-
raise
|
|
80
|
+
raise Error, "unknown algorithm \"#{alg}\"" unless algorithm
|
|
45
81
|
|
|
46
82
|
sess = Regexp.last_match(2)
|
|
47
83
|
else
|
|
@@ -96,6 +132,10 @@ module HTTPX
|
|
|
96
132
|
def next_nonce
|
|
97
133
|
@nonce += 1
|
|
98
134
|
end
|
|
135
|
+
|
|
136
|
+
def raise_format_error
|
|
137
|
+
raise Error, "unsupported digest header format"
|
|
138
|
+
end
|
|
99
139
|
end
|
|
100
140
|
end
|
|
101
141
|
end
|
data/lib/httpx/plugins/auth.rb
CHANGED
|
@@ -10,13 +10,122 @@ module HTTPX
|
|
|
10
10
|
# https://gitlab.com/os85/httpx/wikis/Auth#auth
|
|
11
11
|
#
|
|
12
12
|
module Auth
|
|
13
|
+
def self.subplugins
|
|
14
|
+
{
|
|
15
|
+
retries: AuthRetries,
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# adds support for the following options:
|
|
20
|
+
#
|
|
21
|
+
# :auth_header_value :: the token to use as a string, or a callable which returns a string when called.
|
|
22
|
+
# :auth_header_type :: the authentication type to use in the "authorization" header value (i.e. "Bearer", "Digest"...)
|
|
23
|
+
# :generate_auth_value_on_retry :: callable which returns whether the request should regenerate the auth_header_value
|
|
24
|
+
# when the request is retried (this option will only work if the session also loads the
|
|
25
|
+
# <tt>:retries</tt> plugin).
|
|
26
|
+
module OptionsMethods
|
|
27
|
+
def option_auth_header_value(value)
|
|
28
|
+
value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def option_auth_header_type(value)
|
|
32
|
+
value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def option_generate_auth_value_on_retry(value)
|
|
36
|
+
raise TypeError, "`:generate_auth_value_on_retry` must be a callable" unless value.respond_to?(:call)
|
|
37
|
+
|
|
38
|
+
value
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
13
42
|
module InstanceMethods
|
|
14
|
-
def
|
|
15
|
-
|
|
43
|
+
def initialize(*)
|
|
44
|
+
super
|
|
45
|
+
|
|
46
|
+
@auth_header_value = nil
|
|
47
|
+
@skip_auth_header_value = false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def authorization(token = nil, auth_header_type: nil, &blk)
|
|
51
|
+
with(auth_header_type: auth_header_type, auth_header_value: token || blk)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def bearer_auth(token = nil, &blk)
|
|
55
|
+
authorization(token, auth_header_type: "Bearer", &blk)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def skip_auth_header
|
|
59
|
+
@skip_auth_header_value = true
|
|
60
|
+
yield
|
|
61
|
+
ensure
|
|
62
|
+
@skip_auth_header_value = false
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def reset_auth_header_value!
|
|
66
|
+
@auth_header_value = nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def send_request(request, *)
|
|
72
|
+
return super if @skip_auth_header_value
|
|
73
|
+
|
|
74
|
+
@auth_header_value ||= generate_auth_token
|
|
75
|
+
|
|
76
|
+
request.authorize(@auth_header_value) if @auth_header_value
|
|
77
|
+
|
|
78
|
+
super
|
|
16
79
|
end
|
|
17
80
|
|
|
18
|
-
def
|
|
19
|
-
|
|
81
|
+
def generate_auth_token
|
|
82
|
+
return unless (auth_value = @options.auth_header_value)
|
|
83
|
+
|
|
84
|
+
auth_value = auth_value.call(self) if dynamic_auth_token?(auth_value)
|
|
85
|
+
|
|
86
|
+
auth_value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def dynamic_auth_token?(auth_header_value)
|
|
90
|
+
auth_header_value&.respond_to?(:call)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
module RequestMethods
|
|
95
|
+
def authorize(auth_value)
|
|
96
|
+
if (auth_type = @options.auth_header_type)
|
|
97
|
+
auth_value = "#{auth_type} #{auth_value}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
@headers.add("authorization", auth_value)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module AuthRetries
|
|
105
|
+
module InstanceMethods
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def retryable_request?(request, response, options)
|
|
109
|
+
super || auth_error?(response, options)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def retryable_response?(response, options)
|
|
113
|
+
auth_error?(response, options) || super
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def prepare_to_retry(request, response)
|
|
117
|
+
super
|
|
118
|
+
|
|
119
|
+
return unless auth_error?(response, request.options) ||
|
|
120
|
+
(@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response))
|
|
121
|
+
|
|
122
|
+
request.headers.get("authorization").pop
|
|
123
|
+
@auth_header_value = generate_auth_token
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def auth_error?(response, options)
|
|
127
|
+
response.is_a?(Response) && response.status == 401 && dynamic_auth_token?(options.auth_header_value)
|
|
128
|
+
end
|
|
20
129
|
end
|
|
21
130
|
end
|
|
22
131
|
end
|
|
@@ -8,15 +8,14 @@ module HTTPX
|
|
|
8
8
|
# https://gitlab.com/os85/httpx/wikis/Auth#digest-auth
|
|
9
9
|
#
|
|
10
10
|
module DigestAuth
|
|
11
|
-
DigestError = Class.new(Error)
|
|
12
|
-
|
|
13
11
|
class << self
|
|
14
12
|
def extra_options(options)
|
|
15
13
|
options.merge(max_concurrent_requests: 1)
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
def load_dependencies(
|
|
16
|
+
def load_dependencies(klass)
|
|
19
17
|
require_relative "auth/digest"
|
|
18
|
+
klass.plugin(:auth)
|
|
20
19
|
end
|
|
21
20
|
end
|
|
22
21
|
|
|
@@ -48,11 +47,11 @@ module HTTPX
|
|
|
48
47
|
|
|
49
48
|
probe_response = wrap { super(request).first }
|
|
50
49
|
|
|
51
|
-
return
|
|
50
|
+
return [probe_response] * requests.size unless probe_response.is_a?(Response)
|
|
52
51
|
|
|
53
52
|
if probe_response.status == 401 && digest.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
54
53
|
request.transition(:idle)
|
|
55
|
-
request.
|
|
54
|
+
request.authorize(digest.authenticate(request, probe_response.headers["www-authenticate"]))
|
|
56
55
|
super(request)
|
|
57
56
|
else
|
|
58
57
|
probe_response
|
|
@@ -6,12 +6,13 @@ module HTTPX
|
|
|
6
6
|
#
|
|
7
7
|
# This enables integration with fiber scheduler implementations such as [async](https://github.com/async).
|
|
8
8
|
#
|
|
9
|
-
# # https://gitlab.com/os85/httpx/wikis/
|
|
9
|
+
# # https://gitlab.com/os85/httpx/wikis/Fiber-Concurrency
|
|
10
10
|
#
|
|
11
11
|
module FiberConcurrency
|
|
12
12
|
def self.subplugins
|
|
13
13
|
{
|
|
14
14
|
h2c: FiberConcurrencyH2C,
|
|
15
|
+
stream: FiberConcurrencyStream,
|
|
15
16
|
}
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -188,6 +189,20 @@ module HTTPX
|
|
|
188
189
|
end
|
|
189
190
|
end
|
|
190
191
|
end
|
|
192
|
+
|
|
193
|
+
module FiberConcurrencyStream
|
|
194
|
+
module StreamResponseMethods
|
|
195
|
+
def close
|
|
196
|
+
unless @request.current_context?
|
|
197
|
+
@request.close
|
|
198
|
+
|
|
199
|
+
return
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
super
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
191
206
|
end
|
|
192
207
|
|
|
193
208
|
register_plugin :fiber_concurrency, FiberConcurrency
|
|
@@ -48,7 +48,7 @@ module HTTPX
|
|
|
48
48
|
until message.empty?
|
|
49
49
|
compressed, size = message.unpack("CL>")
|
|
50
50
|
|
|
51
|
-
encoded_data = message.byteslice(5..size + 5 - 1)
|
|
51
|
+
encoded_data = message.byteslice(5..(size + 5 - 1))
|
|
52
52
|
|
|
53
53
|
if compressed == 1
|
|
54
54
|
grpc_encodings.reverse_each do |encoding|
|
data/lib/httpx/plugins/grpc.rb
CHANGED
|
@@ -249,7 +249,7 @@ module HTTPX
|
|
|
249
249
|
call
|
|
250
250
|
end
|
|
251
251
|
|
|
252
|
-
def build_grpc_request(rpc_method, input, deadline:, metadata: nil, **)
|
|
252
|
+
def build_grpc_request(rpc_method, input, deadline:, metadata: nil, **opts)
|
|
253
253
|
uri = @options.origin.dup
|
|
254
254
|
rpc_method = "/#{rpc_method}" unless rpc_method.start_with?("/")
|
|
255
255
|
rpc_method = "/#{@options.grpc_service}#{rpc_method}" if @options.grpc_service
|
|
@@ -273,7 +273,7 @@ module HTTPX
|
|
|
273
273
|
|
|
274
274
|
headers.merge!(@options.call_credentials.call.transform_keys(&:to_s)) if @options.call_credentials
|
|
275
275
|
|
|
276
|
-
build_request("POST", uri, headers: headers, body: input)
|
|
276
|
+
build_request("POST", uri, headers: headers, body: input, **opts)
|
|
277
277
|
end
|
|
278
278
|
end
|
|
279
279
|
end
|
|
@@ -7,8 +7,9 @@ module HTTPX
|
|
|
7
7
|
#
|
|
8
8
|
module NTLMAuth
|
|
9
9
|
class << self
|
|
10
|
-
def load_dependencies(
|
|
10
|
+
def load_dependencies(klass)
|
|
11
11
|
require_relative "auth/ntlm"
|
|
12
|
+
klass.plugin(:auth)
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def extra_options(options)
|
|
@@ -38,14 +39,15 @@ module HTTPX
|
|
|
38
39
|
ntlm = request.options.ntlm
|
|
39
40
|
|
|
40
41
|
if ntlm
|
|
41
|
-
request.
|
|
42
|
+
request.authorize(ntlm.negotiate)
|
|
42
43
|
probe_response = wrap { super(request).first }
|
|
43
44
|
|
|
44
45
|
return probe_response unless probe_response.is_a?(Response)
|
|
45
46
|
|
|
46
47
|
if probe_response.status == 401 && ntlm.can_authenticate?(probe_response.headers["www-authenticate"])
|
|
47
48
|
request.transition(:idle)
|
|
48
|
-
request.headers
|
|
49
|
+
request.headers.get("authorization").pop
|
|
50
|
+
request.authorize(ntlm.authenticate(request, probe_response.headers["www-authenticate"]).encode("utf-8"))
|
|
49
51
|
super(request)
|
|
50
52
|
else
|
|
51
53
|
probe_response
|