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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_11_0.md +3 -3
  3. data/doc/release_notes/1_6_3.md +2 -2
  4. data/doc/release_notes/1_7_0.md +149 -0
  5. data/doc/release_notes/1_7_1.md +21 -0
  6. data/lib/httpx/adapters/datadog.rb +1 -1
  7. data/lib/httpx/adapters/faraday.rb +1 -1
  8. data/lib/httpx/adapters/webmock.rb +18 -9
  9. data/lib/httpx/altsvc.rb +4 -2
  10. data/lib/httpx/connection/http1.rb +9 -9
  11. data/lib/httpx/connection/http2.rb +2 -0
  12. data/lib/httpx/connection.rb +7 -9
  13. data/lib/httpx/domain_name.rb +1 -1
  14. data/lib/httpx/headers.rb +2 -2
  15. data/lib/httpx/io/tcp.rb +1 -1
  16. data/lib/httpx/loggable.rb +2 -0
  17. data/lib/httpx/options.rb +118 -22
  18. data/lib/httpx/parser/http1.rb +1 -0
  19. data/lib/httpx/plugins/auth/digest.rb +44 -4
  20. data/lib/httpx/plugins/auth.rb +113 -4
  21. data/lib/httpx/plugins/aws_sdk_authentication.rb +0 -1
  22. data/lib/httpx/plugins/cookies/cookie.rb +1 -0
  23. data/lib/httpx/plugins/digest_auth.rb +4 -5
  24. data/lib/httpx/plugins/fiber_concurrency.rb +16 -1
  25. data/lib/httpx/plugins/grpc/grpc_encoding.rb +1 -1
  26. data/lib/httpx/plugins/grpc.rb +2 -2
  27. data/lib/httpx/plugins/internal_telemetry.rb +1 -1
  28. data/lib/httpx/plugins/ntlm_auth.rb +5 -3
  29. data/lib/httpx/plugins/oauth.rb +156 -57
  30. data/lib/httpx/plugins/persistent.rb +3 -5
  31. data/lib/httpx/plugins/proxy/http.rb +0 -4
  32. data/lib/httpx/plugins/proxy.rb +3 -1
  33. data/lib/httpx/plugins/query.rb +1 -1
  34. data/lib/httpx/plugins/rate_limiter.rb +20 -15
  35. data/lib/httpx/plugins/response_cache.rb +3 -7
  36. data/lib/httpx/plugins/retries.rb +60 -24
  37. data/lib/httpx/plugins/ssrf_filter.rb +1 -1
  38. data/lib/httpx/plugins/stream.rb +60 -9
  39. data/lib/httpx/plugins/stream_bidi.rb +84 -16
  40. data/lib/httpx/pool.rb +12 -3
  41. data/lib/httpx/request/body.rb +1 -1
  42. data/lib/httpx/request.rb +10 -1
  43. data/lib/httpx/resolver/cache/base.rb +136 -0
  44. data/lib/httpx/resolver/cache/memory.rb +42 -0
  45. data/lib/httpx/resolver/cache.rb +18 -0
  46. data/lib/httpx/resolver/https.rb +74 -20
  47. data/lib/httpx/resolver/multi.rb +10 -2
  48. data/lib/httpx/resolver/native.rb +32 -6
  49. data/lib/httpx/resolver/resolver.rb +3 -3
  50. data/lib/httpx/resolver.rb +36 -114
  51. data/lib/httpx/response/body.rb +5 -3
  52. data/lib/httpx/response.rb +22 -6
  53. data/lib/httpx/selector.rb +14 -3
  54. data/lib/httpx/session.rb +6 -6
  55. data/lib/httpx/timers.rb +6 -12
  56. data/lib/httpx/transcoder/body.rb +1 -1
  57. data/lib/httpx/transcoder/gzip.rb +7 -2
  58. data/lib/httpx/transcoder/json.rb +1 -1
  59. data/lib/httpx/transcoder/multipart/decoder.rb +5 -5
  60. data/lib/httpx/transcoder/multipart/encoder.rb +1 -1
  61. data/lib/httpx/transcoder/multipart.rb +17 -9
  62. data/lib/httpx/transcoder.rb +4 -6
  63. data/lib/httpx/utils.rb +13 -0
  64. data/lib/httpx/version.rb +1 -1
  65. data/sig/altsvc.rbs +9 -3
  66. data/sig/chainable.rbs +3 -3
  67. data/sig/connection.rbs +1 -3
  68. data/sig/loggable.rbs +1 -1
  69. data/sig/options.rbs +12 -4
  70. data/sig/plugins/auth/digest.rbs +6 -0
  71. data/sig/plugins/auth.rbs +37 -4
  72. data/sig/plugins/basic_auth.rbs +3 -3
  73. data/sig/plugins/digest_auth.rbs +2 -4
  74. data/sig/plugins/fiber_concurrency.rbs +6 -0
  75. data/sig/plugins/ntlm_auth.rbs +2 -2
  76. data/sig/plugins/oauth.rbs +44 -15
  77. data/sig/plugins/rate_limiter.rbs +4 -2
  78. data/sig/plugins/response_cache/file_store.rbs +2 -0
  79. data/sig/plugins/response_cache.rbs +4 -0
  80. data/sig/plugins/retries.rbs +12 -4
  81. data/sig/plugins/stream.rbs +13 -3
  82. data/sig/plugins/stream_bidi.rbs +2 -2
  83. data/sig/pool.rbs +1 -1
  84. data/sig/resolver/cache/base.rbs +28 -0
  85. data/sig/resolver/cache/memory.rbs +13 -0
  86. data/sig/resolver/cache.rbs +16 -0
  87. data/sig/resolver/https.rbs +24 -0
  88. data/sig/resolver/multi.rbs +8 -0
  89. data/sig/resolver/native.rbs +2 -0
  90. data/sig/resolver.rbs +5 -20
  91. data/sig/response.rbs +3 -0
  92. data/sig/session.rbs +3 -5
  93. data/sig/timers.rbs +1 -1
  94. data/sig/transcoder/multipart.rbs +4 -2
  95. data/sig/transcoder.rbs +5 -1
  96. data/sig/utils.rbs +2 -0
  97. 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
- ivar_map = nil
192
- other_ivars = case other
193
- when Options
194
- other.instance_variables
195
- else
196
- other = Hash[other] unless other.is_a?(Hash)
197
- ivar_map = other.keys.to_h { |k| [:"@#{k}", k] }
198
- ivar_map.keys
199
- end
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
- return self if other_ivars.empty?
241
+ return self if other_opts.empty?
202
242
 
203
- return self if other_ivars.all? { |ivar| instance_variable_get(ivar) == access_option(other, ivar, ivar_map) }
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
- other_ivars.each do |ivar|
208
- v = access_option(other, ivar, ivar_map)
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_#{ivar[1..-1]}", v)
259
+ v = opts.__send__(:"option_#{opt}", v)
216
260
 
217
- orig_v = instance_variable_get(ivar)
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 resolver_class
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
@@ -26,6 +26,7 @@ module HTTPX
26
26
  @headers = {}
27
27
  @content_length = nil
28
28
  @_has_trailers = nil
29
+ @buffer.clear
29
30
  end
30
31
 
31
32
  def upgrade?
@@ -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
- params = auth_info.split(/ *, */)
33
- .to_h { |val| val.split("=", 2) }
34
- .transform_values { |v| v.delete("\"") }
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 DigestError, "unknown algorithm \"#{alg}\"" unless algorithm
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
@@ -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 authorization(token)
15
- with(headers: { "authorization" => token })
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 bearer_auth(token)
19
- authorization("Bearer #{token}")
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
@@ -94,7 +94,6 @@ module HTTPX
94
94
  region: AwsSdkAuthentication.region(@options.aws_profile),
95
95
  **options
96
96
  )
97
-
98
97
  aws_sigv4_authentication(
99
98
  credentials: credentials,
100
99
  region: region,
@@ -7,6 +7,7 @@ module HTTPX
7
7
  # Contains the single cookie info: name, value and attributes.
8
8
  class Cookie
9
9
  include Comparable
10
+
10
11
  # Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at
11
12
  # least)
12
13
  MAX_LENGTH = 4096
@@ -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 ([probe_response] * requests.size) unless probe_response.is_a?(Response)
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.headers["authorization"] = digest.authenticate(request, probe_response.headers["www-authenticate"])
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/FiberConcurrency
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|
@@ -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
@@ -45,7 +45,7 @@ module HTTPX
45
45
  debug_level: @options ? @options.debug_level : DEBUG_LEVEL,
46
46
  debug: nil
47
47
  ) do
48
- "[ELAPSED TIME]: #{label}: #{elapsed} (ms)" << "\e[0m"
48
+ "[ELAPSED TIME]: #{label}: #{elapsed} (ms)\e[0m"
49
49
  end
50
50
  end
51
51
  end
@@ -7,8 +7,9 @@ module HTTPX
7
7
  #
8
8
  module NTLMAuth
9
9
  class << self
10
- def load_dependencies(_klass)
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.headers["authorization"] = ntlm.negotiate
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["authorization"] = ntlm.authenticate(request, probe_response.headers["www-authenticate"])
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