httpx 1.8.4 → 1.9.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/1_8_1.md +1 -1
  3. data/doc/release_notes/1_8_2.md +4 -4
  4. data/doc/release_notes/1_8_3.md +3 -5
  5. data/doc/release_notes/1_8_4.md +1 -1
  6. data/doc/release_notes/1_9_0.md +75 -0
  7. data/doc/release_notes/1_9_1.md +32 -0
  8. data/lib/httpx/adapters/faraday.rb +1 -1
  9. data/lib/httpx/adapters/webmock.rb +15 -2
  10. data/lib/httpx/altsvc.rb +29 -15
  11. data/lib/httpx/chainable.rb +4 -1
  12. data/lib/httpx/connection/http1.rb +34 -35
  13. data/lib/httpx/connection/http2.rb +146 -33
  14. data/lib/httpx/connection.rb +282 -162
  15. data/lib/httpx/headers.rb +2 -2
  16. data/lib/httpx/io/tcp.rb +42 -21
  17. data/lib/httpx/options.rb +86 -35
  18. data/lib/httpx/parser/http1.rb +3 -6
  19. data/lib/httpx/plugins/auth/digest.rb +1 -2
  20. data/lib/httpx/plugins/auth.rb +11 -2
  21. data/lib/httpx/plugins/aws_sdk_authentication.rb +7 -3
  22. data/lib/httpx/plugins/aws_sigv4.rb +1 -1
  23. data/lib/httpx/plugins/basic_auth.rb +2 -0
  24. data/lib/httpx/plugins/cache.rb +1 -1
  25. data/lib/httpx/plugins/callbacks.rb +8 -6
  26. data/lib/httpx/plugins/circuit_breaker.rb +1 -0
  27. data/lib/httpx/plugins/content_digest.rb +3 -5
  28. data/lib/httpx/plugins/digest_auth.rb +2 -0
  29. data/lib/httpx/plugins/expect.rb +10 -1
  30. data/lib/httpx/plugins/fiber_concurrency.rb +59 -16
  31. data/lib/httpx/plugins/follow_redirects.rb +1 -1
  32. data/lib/httpx/plugins/grpc.rb +21 -0
  33. data/lib/httpx/plugins/h2c.rb +12 -11
  34. data/lib/httpx/plugins/hedging.rb +284 -0
  35. data/lib/httpx/plugins/ntlm_auth.rb +5 -3
  36. data/lib/httpx/plugins/ntlm_v2_auth.rb +5 -3
  37. data/lib/httpx/plugins/oauth.rb +2 -2
  38. data/lib/httpx/plugins/proxy/http.rb +67 -26
  39. data/lib/httpx/plugins/proxy/socks5.rb +4 -4
  40. data/lib/httpx/plugins/proxy/ssh.rb +30 -11
  41. data/lib/httpx/plugins/proxy.rb +68 -54
  42. data/lib/httpx/plugins/push_promise.rb +4 -2
  43. data/lib/httpx/plugins/query.rb +1 -0
  44. data/lib/httpx/plugins/retries.rb +2 -2
  45. data/lib/httpx/plugins/server_sent_events.rb +1 -0
  46. data/lib/httpx/plugins/ssrf_filter.rb +1 -1
  47. data/lib/httpx/plugins/stream.rb +12 -10
  48. data/lib/httpx/plugins/stream_bidi.rb +4 -0
  49. data/lib/httpx/plugins/tracing.rb +99 -11
  50. data/lib/httpx/plugins/upgrade/h2.rb +3 -3
  51. data/lib/httpx/plugins/upgrade.rb +2 -0
  52. data/lib/httpx/plugins/webdav.rb +8 -0
  53. data/lib/httpx/request/body.rb +2 -1
  54. data/lib/httpx/request.rb +6 -6
  55. data/lib/httpx/resolver/https.rb +3 -2
  56. data/lib/httpx/resolver/native.rb +19 -9
  57. data/lib/httpx/resolver/resolver.rb +2 -2
  58. data/lib/httpx/resolver/system.rb +4 -0
  59. data/lib/httpx/resolver.rb +10 -6
  60. data/lib/httpx/response.rb +19 -7
  61. data/lib/httpx/selector.rb +111 -10
  62. data/lib/httpx/session.rb +36 -10
  63. data/lib/httpx/transcoder/json.rb +1 -1
  64. data/lib/httpx/version.rb +1 -1
  65. data/lib/httpx.rb +1 -0
  66. data/sig/altsvc.rbs +3 -3
  67. data/sig/callbacks.rbs +1 -0
  68. data/sig/chainable.rbs +3 -1
  69. data/sig/connection/http1.rbs +4 -2
  70. data/sig/connection/http2.rbs +23 -17
  71. data/sig/connection.rbs +29 -7
  72. data/sig/headers.rbs +1 -1
  73. data/sig/httpx.rbs +2 -0
  74. data/sig/io/tcp.rbs +5 -0
  75. data/sig/options.rbs +43 -12
  76. data/sig/parser/http1.rbs +1 -1
  77. data/sig/plugins/auth/digest.rbs +0 -1
  78. data/sig/plugins/auth.rbs +12 -5
  79. data/sig/plugins/expect.rbs +6 -0
  80. data/sig/plugins/fiber_concurrency.rbs +10 -1
  81. data/sig/plugins/hedging.rbs +44 -0
  82. data/sig/plugins/proxy/http.rbs +9 -2
  83. data/sig/plugins/proxy.rbs +3 -2
  84. data/sig/plugins/retries.rbs +2 -2
  85. data/sig/plugins/stream_bidi.rbs +0 -2
  86. data/sig/plugins/tracing.rbs +22 -5
  87. data/sig/request/body.rbs +1 -1
  88. data/sig/resolver/entry.rbs +2 -2
  89. data/sig/resolver/https.rbs +2 -0
  90. data/sig/resolver/native.rbs +3 -0
  91. data/sig/resolver/resolver.rbs +1 -1
  92. data/sig/resolver/system.rbs +2 -0
  93. data/sig/resolver.rbs +1 -1
  94. data/sig/response.rbs +1 -2
  95. data/sig/selector.rbs +17 -3
  96. data/sig/session.rbs +8 -6
  97. metadata +7 -1
data/lib/httpx/headers.rb CHANGED
@@ -159,8 +159,8 @@ module HTTPX
159
159
  # This method is more internal, and for this reason doesn't try
160
160
  # to "correct" the user input, i.e. it doesn't downcase the key.
161
161
  #
162
- def get(field)
163
- @headers[field] || EMPTY
162
+ def get(downcased_key)
163
+ @headers[downcased_key] || EMPTY
164
164
  end
165
165
 
166
166
  private
data/lib/httpx/io/tcp.rb CHANGED
@@ -17,7 +17,7 @@ module HTTPX
17
17
  @keep_open = false
18
18
  @addresses = []
19
19
  @ip_index = -1
20
- @ip = nil
20
+ @ip = @attempt_started_at = nil
21
21
  @hostname = origin.host
22
22
  @options = options
23
23
  @fallback_protocol = @options.fallback_protocol
@@ -80,6 +80,27 @@ module HTTPX
80
80
  @addresses.any?
81
81
  end
82
82
 
83
+ # time left until the attempt to connect to the current address should be abandoned.
84
+ def connect_attempt_time_left
85
+ return unless @state == :idle && @ip_index.positive? && (attempt_started_at = @attempt_started_at)
86
+
87
+ timeouts = @options.timeout
88
+ timeout = timeouts.connect_timeout || timeouts.request_timeout || timeouts.total_request_timeout
89
+
90
+ return unless timeout && timeout.finite?
91
+
92
+ timeout.fdiv(@addresses.size) - Utils.elapsed_time(attempt_started_at)
93
+ end
94
+
95
+ # abandons the connection attempt to the current address.
96
+ def next_address!(e)
97
+ @io&.close
98
+ @ip_index -= 1
99
+ raise e if @ip_index.negative?
100
+
101
+ log { "failed connecting to #{@ip} (#{e.message}), trying next..." }
102
+ end
103
+
83
104
  def to_io
84
105
  @io.to_io
85
106
  end
@@ -109,13 +130,8 @@ module HTTPX
109
130
  try_connect
110
131
  rescue Errno::EHOSTUNREACH,
111
132
  Errno::ENETUNREACH => e
112
- @ip_index -= 1
113
-
114
- raise e if @ip_index.negative?
115
-
116
- log { "failed connecting to #{@ip} (#{e.message}), evict from cache and trying next..." }
133
+ next_address!(e)
117
134
  @options.resolver_cache.evict(@hostname, @ip)
118
-
119
135
  @io = build_socket
120
136
  retry
121
137
  rescue Errno::ECONNREFUSED,
@@ -132,7 +148,7 @@ module HTTPX
132
148
  rescue Errno::ETIMEDOUT => e
133
149
  @ip_index -= 1
134
150
 
135
- raise ConnectTimeoutError.new(@options.timeout[:connect_timeout], e.message) if @ip_index.negative?
151
+ raise ConnectTimeoutError.new(@options.timeout.connect_timeout, e.message) if @ip_index.negative?
136
152
 
137
153
  log { "failed connecting to #{@ip} (#{e.message}), trying next..." }
138
154
 
@@ -159,26 +175,29 @@ module HTTPX
159
175
  private :try_connect
160
176
 
161
177
  def read(size, buffer)
162
- ret = @io.read_nonblock(size, buffer, exception: false)
163
- if ret == :wait_readable
178
+ siz = @io.read_nonblock(size, buffer, exception: false)
179
+
180
+ case siz
181
+ when String
182
+ log { "READ: #{buffer.bytesize} bytes..." }
183
+ buffer.bytesize
184
+ when :wait_readable, :wait_writable
164
185
  buffer.clear
165
- return 0
186
+ 0
166
187
  end
167
- return if ret.nil?
168
-
169
- log { "READ: #{buffer.bytesize} bytes..." }
170
- buffer.bytesize
171
188
  end
172
189
 
173
190
  def write(buffer)
174
191
  siz = @io.write_nonblock(buffer, exception: false)
175
- return 0 if siz == :wait_writable
176
- return if siz.nil?
177
192
 
178
- log { "WRITE: #{siz} bytes..." }
179
-
180
- buffer.shift!(siz)
181
- siz
193
+ case siz
194
+ when Integer
195
+ log { "WRITE: #{siz} bytes..." }
196
+ buffer.shift!(siz)
197
+ siz
198
+ when :wait_readable, :wait_writable
199
+ 0
200
+ end
182
201
  end
183
202
 
184
203
  def close
@@ -234,6 +253,7 @@ module HTTPX
234
253
 
235
254
  def build_socket
236
255
  @ip = @addresses[@ip_index]
256
+ @attempt_started_at = Utils.now
237
257
  Socket.new(@ip.family, :STREAM, 0)
238
258
  end
239
259
 
@@ -255,6 +275,7 @@ module HTTPX
255
275
 
256
276
  def log_transition_state(nextstate)
257
277
  label = host
278
+ label = "#{label}:#{@port}" if label
258
279
  label = "#{label}(##{@io.fileno})" if nextstate == :connected
259
280
  "#{label} #{@state} -> #{nextstate}"
260
281
  end
data/lib/httpx/options.rb CHANGED
@@ -12,12 +12,11 @@ module HTTPX
12
12
  SETTINGS_TIMEOUT = 10
13
13
  CLOSE_HANDSHAKE_TIMEOUT = 10
14
14
  CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
15
- REQUEST_TIMEOUT = OPERATION_TIMEOUT = TOTAL_REQUEST_TIMEOUT = nil
16
15
  RESOLVER_TYPES = %i[memory file].freeze
17
16
  # default value used for "user-agent" header, when not overridden.
18
17
  USER_AGENT = "httpx.rb/#{VERSION}".freeze # rubocop:disable Style/RedundantFreeze
19
18
 
20
- @options_names = []
19
+ @options_names = Set[]
21
20
 
22
21
  class << self
23
22
  attr_reader :options_names
@@ -199,14 +198,14 @@ module HTTPX
199
198
  # here, which is to make option objects shareable across ractors,
200
199
  # and in most cases debug should be nil, or one of the objects
201
200
  # which will eventually be shareable, like STDOUT or STDERR.
202
- next if ivar == :debug
201
+ next if %i[debug io].include?(ivar)
203
202
 
204
- instance_variable_get(:"@#{ivar}").freeze
203
+ send(ivar).freeze
205
204
  end
206
205
  super
207
206
  end
208
207
 
209
- REQUEST_BODY_IVARS = %i[@headers].freeze
208
+ REQUEST_BODY_IVARS = %i[headers].freeze
210
209
 
211
210
  # checks whether +other+ matches the same connection-level options
212
211
  def connection_options_match?(other, ignore_ivars = nil)
@@ -214,20 +213,18 @@ module HTTPX
214
213
 
215
214
  # headers and other request options do not play a role, as they are
216
215
  # relevant only for the request.
217
- ivars = instance_variables
218
- ivars.reject! { |iv| REQUEST_BODY_IVARS.include?(iv) }
219
- ivars.reject! { |iv| ignore_ivars.include?(iv) } if ignore_ivars
216
+ ivars = self.class.options_names.reject { |iv| REQUEST_BODY_IVARS.include?(iv) }
217
+ ivars = ivars.reject { |iv| ignore_ivars.include?(iv) } if ignore_ivars
220
218
 
221
- other_ivars = other.instance_variables
222
- other_ivars.reject! { |iv| REQUEST_BODY_IVARS.include?(iv) }
223
- other_ivars.reject! { |iv| ignore_ivars.include?(iv) } if ignore_ivars
219
+ other_ivars = other.class.options_names.reject { |iv| REQUEST_BODY_IVARS.include?(iv) }
220
+ other_ivars = other_ivars.reject { |iv| ignore_ivars.include?(iv) } if ignore_ivars
224
221
 
225
222
  return false if ivars.size != other_ivars.size
226
223
 
227
- return false if ivars.sort != other_ivars.sort
224
+ return false if ivars != other_ivars
228
225
 
229
226
  ivars.all? do |ivar|
230
- instance_variable_get(ivar) == other.instance_variable_get(ivar)
227
+ send(ivar) == other.send(ivar)
231
228
  end
232
229
  end
233
230
 
@@ -296,6 +293,7 @@ module HTTPX
296
293
  opts.instance_variable_set(ivar, v)
297
294
  end
298
295
 
296
+ opts.freeze
299
297
  opts
300
298
  end
301
299
 
@@ -455,20 +453,22 @@ module HTTPX
455
453
  end
456
454
 
457
455
  def option_timeout(value)
456
+ return value if value.is_a?(Timeouts)
457
+
458
458
  timeout_hash = Hash[value]
459
459
 
460
- default_timeouts = DEFAULT_OPTIONS[:timeout]
460
+ default_timeouts = Timeouts.members
461
461
 
462
462
  # Validate keys and values
463
463
  timeout_hash.each do |key, val|
464
- raise TypeError, "invalid timeout: `:#{key}`" unless default_timeouts.key?(key)
464
+ raise TypeError, "invalid timeout: `:#{key}`" unless default_timeouts.include?(key)
465
465
 
466
466
  next if val.nil?
467
467
 
468
468
  raise TypeError, "`:#{key}` must be numeric" unless val.is_a?(Numeric)
469
469
  end
470
470
 
471
- timeout_hash
471
+ Timeouts.new(**timeout_hash)
472
472
  end
473
473
 
474
474
  def option_supported_compression_formats(value)
@@ -483,7 +483,14 @@ module HTTPX
483
483
  end
484
484
 
485
485
  def option_addresses(value)
486
- Array(value).map { |entry| Resolver::Entry.convert(entry) }
486
+ case value
487
+ when Hash
488
+ value.transform_values do |value|
489
+ Array(value).map { |entry| Resolver::Entry.convert(entry) }
490
+ end
491
+ else
492
+ Array(value).map { |entry| Resolver::Entry.convert(entry) }
493
+ end
487
494
  end
488
495
 
489
496
  def option_ip_families(value)
@@ -539,12 +546,67 @@ module HTTPX
539
546
  end
540
547
  end
541
548
 
542
- def access_option(obj, k, ivar_map)
543
- case obj
544
- when Hash
545
- obj[ivar_map[k]]
546
- else
547
- obj.instance_variable_get(k)
549
+ class Timeouts < Struct.new(
550
+ :connect_timeout, :settings_timeout,
551
+ :read_timeout, :write_timeout,
552
+ :request_timeout, :total_request_timeout,
553
+ :operation_timeout, :keep_alive_timeout, :ping_timeout, :close_handshake_timeout,
554
+ keyword_init: true
555
+ )
556
+ def initialize(
557
+ connect_timeout: nil,
558
+ settings_timeout: nil,
559
+ read_timeout: nil,
560
+ write_timeout: nil,
561
+ request_timeout: nil,
562
+ total_request_timeout: nil,
563
+ operation_timeout: nil,
564
+ close_handshake_timeout: nil,
565
+ keep_alive_timeout: nil,
566
+ ping_timeout: nil
567
+ )
568
+ super
569
+ freeze
570
+ end
571
+
572
+ def connect_timeout
573
+ self[:connect_timeout] || (CONNECT_TIMEOUT unless deadline_timeouts?)
574
+ end
575
+
576
+ def settings_timeout
577
+ self[:settings_timeout] || (SETTINGS_TIMEOUT unless deadline_timeouts?)
578
+ end
579
+
580
+ def read_timeout
581
+ self[:read_timeout] || (READ_TIMEOUT unless deadline_timeouts?)
582
+ end
583
+
584
+ def write_timeout
585
+ self[:write_timeout] || (WRITE_TIMEOUT unless deadline_timeouts?)
586
+ end
587
+
588
+ def close_handshake_timeout
589
+ self[:close_handshake_timeout] || CLOSE_HANDSHAKE_TIMEOUT
590
+ end
591
+
592
+ def keep_alive_timeout
593
+ self[:keep_alive_timeout] || KEEP_ALIVE_TIMEOUT
594
+ end
595
+
596
+ def ping_timeout
597
+ self[:ping_timeout] || PING_TIMEOUT
598
+ end
599
+
600
+ def merge(other)
601
+ other_h = other.to_h
602
+ other_h.compact!
603
+ Timeouts.new(**to_h, **other_h)
604
+ end
605
+
606
+ private
607
+
608
+ def deadline_timeouts?
609
+ request_timeout || total_request_timeout || operation_timeout
548
610
  end
549
611
  end
550
612
 
@@ -572,18 +634,7 @@ module HTTPX
572
634
  :max_response_headers => 1000,
573
635
  :max_response_header_value_size => nil,
574
636
  :max_response_body_size => Float::INFINITY,
575
- :timeout => {
576
- connect_timeout: CONNECT_TIMEOUT,
577
- settings_timeout: SETTINGS_TIMEOUT,
578
- close_handshake_timeout: CLOSE_HANDSHAKE_TIMEOUT,
579
- operation_timeout: OPERATION_TIMEOUT,
580
- keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
581
- ping_timeout: PING_TIMEOUT,
582
- read_timeout: READ_TIMEOUT,
583
- write_timeout: WRITE_TIMEOUT,
584
- request_timeout: REQUEST_TIMEOUT,
585
- total_request_timeout: TOTAL_REQUEST_TIMEOUT,
586
- }.freeze,
637
+ :timeout => Timeouts.new,
587
638
  :headers_class => Class.new(Headers, &SET_TEMPORARY_NAME),
588
639
  :headers => EMPTY_HASH,
589
640
  :window_size => WINDOW_SIZE,
@@ -68,7 +68,7 @@ module HTTPX
68
68
  version, code, _ = m.captures
69
69
  raise(Error, "unsupported HTTP version (HTTP/#{version})") unless version && VERSIONS.include?(version)
70
70
 
71
- @http_version = version.split(".").map(&:to_i)
71
+ @http_version = version
72
72
  @status_code = code.to_i
73
73
  raise(Error, "wrong status code (#{@status_code})") unless (100..599).cover?(@status_code)
74
74
 
@@ -78,9 +78,7 @@ module HTTPX
78
78
 
79
79
  def parse_headers
80
80
  headers = @headers
81
- buffer = @buffer
82
-
83
- #: @type var buffer: String
81
+ buffer = @buffer #: String
84
82
 
85
83
  while (idx = buffer.index("\n"))
86
84
  # @type var line: String
@@ -114,8 +112,7 @@ module HTTPX
114
112
  key = line.byteslice(0..(separator_index - 1))
115
113
 
116
114
  key.rstrip! # was lstripped previously!
117
- # @type var value: String
118
- value = line.byteslice((separator_index + 1)..-1)
115
+ value = line.byteslice((separator_index + 1)..-1) #: String
119
116
  value.strip!
120
117
  raise Error, "wrong header format" if value.nil?
121
118
 
@@ -14,7 +14,6 @@ module HTTPX
14
14
  def initialize(user, password, hashed: false, **)
15
15
  @user = user
16
16
  @password = password
17
- @nonce = 0
18
17
  @hashed = hashed
19
18
  end
20
19
 
@@ -137,7 +136,7 @@ module HTTPX
137
136
  end
138
137
 
139
138
  def next_nonce
140
- @nonce += 1
139
+ SecureRandom.random_number(2**32)
141
140
  end
142
141
 
143
142
  def raise_format_error
@@ -69,14 +69,18 @@ module HTTPX
69
69
  @skip_auth_header_value = false
70
70
  end
71
71
 
72
+ # returns a new session where requests will be authorized using +token+ (or what the +blk+ yields),
73
+ # using +auth_header_type+ as header value prefix.
72
74
  def authorization(token = nil, auth_header_type: nil, &blk)
73
75
  with(auth_header_type: auth_header_type, auth_header_value: token || blk)
74
76
  end
75
77
 
78
+ # returns a new session which call #authorize while setting <tt>"Bearer"</tt> as the +auth_header_type+.
76
79
  def bearer_auth(token = nil, &blk)
77
80
  authorization(token, auth_header_type: "Bearer", &blk)
78
81
  end
79
82
 
83
+ # yields a block where authorization (if available) will be bypassed.
80
84
  def skip_auth_header
81
85
  @skip_auth_header_value = true
82
86
  yield
@@ -84,6 +88,7 @@ module HTTPX
84
88
  @skip_auth_header_value = false
85
89
  end
86
90
 
91
+ # resets authorization information.
87
92
  def reset_auth_header_value!
88
93
  @auth_header_value_mtx.synchronize do
89
94
  @auth_header_value = @auth_header_expires_at = nil
@@ -186,11 +191,15 @@ module HTTPX
186
191
  auth_error?(response, options) || super
187
192
  end
188
193
 
189
- def prepare_to_retry(request, response)
194
+ def prepare_to_retry(request, response, retry_after)
190
195
  super
191
196
 
192
197
  return unless auth_error?(response, request.options) ||
193
- (@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response))
198
+ (@options.generate_auth_value_on_retry && @options.generate_auth_value_on_retry.call(response)) ||
199
+ (
200
+ (expires_at = @auth_header_expires_at) &&
201
+ expires_at < (Time.now.utc.to_i + (retry_after || 0))
202
+ )
194
203
 
195
204
  # regenerate token before retry, but only if it's the first request from batch failing.
196
205
  # otherwise, it means that the first request already passed here, so this request should
@@ -85,9 +85,13 @@ module HTTPX
85
85
 
86
86
  module InstanceMethods
87
87
  #
88
- # aws_authentication
89
- # aws_authentication(credentials: Aws::Credentials.new('akid', 'secret'))
90
- # aws_authentication()
88
+ # returns a session which will use the AWS credentials provided by your AWS system configuration.
89
+ #
90
+ # session = HTTPX.plugin(:aws_sdk_authentication)
91
+ # auth_session = session.aws_authentication
92
+ # # OR
93
+ # auth_session = session.aws_authentication(credentials: Aws::Credentials.new('akid', 'secret'))
94
+ # auth_session.get("http://...")
91
95
  #
92
96
  def aws_sdk_authentication(
93
97
  credentials: AwsSdkAuthentication.credentials(@options.aws_profile),
@@ -156,7 +156,6 @@ module HTTPX
156
156
 
157
157
  class << self
158
158
  def load_dependencies(*)
159
- require "set"
160
159
  require "digest/sha2"
161
160
  require "cgi/escape"
162
161
  end
@@ -178,6 +177,7 @@ module HTTPX
178
177
  end
179
178
 
180
179
  module InstanceMethods
180
+ # returns a session which will authenticate requests using the AWS Sigv4 protocol.
181
181
  def aws_sigv4_authentication(**options)
182
182
  with(sigv4_signer: Signer.new(**options))
183
183
  end
@@ -19,6 +19,8 @@ module HTTPX
19
19
  end
20
20
 
21
21
  module InstanceMethods
22
+ # returns a new session which will authorize requests using HTTP Basic Auth, with
23
+ # +user+ and +password+ as credentials.
22
24
  def basic_auth(user, password)
23
25
  authorization(Authentication::Basic.new(user, password).authenticate)
24
26
  end
@@ -106,7 +106,7 @@ module HTTPX
106
106
  def fetch_response(request, *)
107
107
  response = super
108
108
 
109
- return unless response
109
+ return unless response && response.is_a?(Response)
110
110
 
111
111
  if cacheable_request?(request) && cacheable_response?(request, response) && !response.cached?
112
112
  log { "caching response for #{request.uri}..." }
@@ -25,6 +25,7 @@ module HTTPX
25
25
 
26
26
  CALLBACKS.each do |meth|
27
27
  class_eval(<<-MOD, __FILE__, __LINE__ + 1)
28
+ # sets a <tt>:#{meth}</tt> callback on the session
28
29
  def on_#{meth}(&blk) # def on_connection_opened(&blk)
29
30
  on(:#{meth}, &blk) # on(:connection_opened, &blk)
30
31
  self # self
@@ -33,6 +34,7 @@ module HTTPX
33
34
  end
34
35
 
35
36
  def plugin(*args, &blk)
37
+ # ruby 2.7 requires explicit named splat
36
38
  super(*args).tap do |sess|
37
39
  CALLBACKS.each do |cb|
38
40
  next unless callbacks_for?(cb)
@@ -44,6 +46,12 @@ module HTTPX
44
46
  end
45
47
  end
46
48
 
49
+ def close(*)
50
+ super
51
+ rescue CallbackError => e
52
+ raise e.cause
53
+ end
54
+
47
55
  private
48
56
 
49
57
  def branch(options, &blk)
@@ -114,12 +122,6 @@ module HTTPX
114
122
  rescue CallbackError => e
115
123
  raise e.cause
116
124
  end
117
-
118
- def close(*)
119
- super
120
- rescue CallbackError => e
121
- raise e.cause
122
- end
123
125
  end
124
126
 
125
127
  module RequestMethods
@@ -34,6 +34,7 @@ module HTTPX
34
34
 
35
35
  %i[circuit_open].each do |meth|
36
36
  class_eval(<<-MOD, __FILE__, __LINE__ + 1)
37
+ # sets a <tt>:#{meth}</tt> callback on the session.
37
38
  def on_#{meth}(&blk) # def on_circuit_open(&blk)
38
39
  on(:#{meth}, &blk) # on(:circuit_open, &blk)
39
40
  self # self
@@ -81,15 +81,13 @@ module HTTPX
81
81
  end
82
82
 
83
83
  def write(chunk)
84
- @content_digest_buffer.write(chunk) if @content_digest_buffer
84
+ @content_digest_buffer&.write(chunk)
85
85
  super
86
86
  end
87
87
 
88
88
  def close
89
- if @content_digest_buffer
90
- @content_digest_buffer.close
91
- @content_digest_buffer = nil
92
- end
89
+ @content_digest_buffer&.close
90
+ @content_digest_buffer = nil
93
91
  super
94
92
  end
95
93
  end
@@ -33,6 +33,8 @@ module HTTPX
33
33
  end
34
34
 
35
35
  module InstanceMethods
36
+ # returns a session which will authenticate requests using HTTP Digest Auth, with
37
+ # +user+ and +password+ as credentials.
36
38
  def digest_auth(user, password, hashed: false)
37
39
  with(digest: Authentication::Digest.new(user, password, hashed: hashed))
38
40
  end
@@ -104,9 +104,15 @@ module HTTPX
104
104
  end
105
105
 
106
106
  module ConnectionMethods
107
- def send_request_to_parser(request)
107
+ private
108
+
109
+ def set_request_timeouts(request)
108
110
  super
109
111
 
112
+ set_request_expect_timeout(request)
113
+ end
114
+
115
+ def set_request_expect_timeout(request)
110
116
  return unless request.headers["expect"] == "100-continue"
111
117
 
112
118
  expect_timeout = request.options.expect_timeout
@@ -118,6 +124,7 @@ module HTTPX
118
124
  if request.state == :expect && !request.expects?
119
125
  Expect.no_expect_store.add(request.origin)
120
126
  request.headers.delete("expect")
127
+ request.transition(:body)
121
128
  consume
122
129
  end
123
130
  end
@@ -125,6 +132,8 @@ module HTTPX
125
132
  end
126
133
 
127
134
  module InstanceMethods
135
+ private
136
+
128
137
  def fetch_response(request, selector, options)
129
138
  response = super
130
139