httpx 0.24.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -48
  3. data/README.md +4 -13
  4. data/doc/release_notes/0_24_4.md +3 -3
  5. data/doc/release_notes/1_0_0.md +60 -0
  6. data/lib/httpx/adapters/datadog.rb +28 -97
  7. data/lib/httpx/adapters/faraday.rb +9 -52
  8. data/lib/httpx/adapters/webmock.rb +2 -7
  9. data/lib/httpx/altsvc.rb +4 -22
  10. data/lib/httpx/base64.rb +27 -0
  11. data/lib/httpx/chainable.rb +0 -23
  12. data/lib/httpx/connection.rb +11 -32
  13. data/lib/httpx/domain_name.rb +5 -12
  14. data/lib/httpx/errors.rb +0 -2
  15. data/lib/httpx/extensions.rb +0 -124
  16. data/lib/httpx/io/ssl.rb +26 -59
  17. data/lib/httpx/io/tcp.rb +27 -68
  18. data/lib/httpx/io/udp.rb +13 -48
  19. data/lib/httpx/io/unix.rb +1 -8
  20. data/lib/httpx/loggable.rb +4 -19
  21. data/lib/httpx/options.rb +24 -84
  22. data/lib/httpx/plugins/{authentication → auth}/basic.rb +1 -5
  23. data/lib/httpx/plugins/{authentication → auth}/digest.rb +2 -5
  24. data/lib/httpx/plugins/{authentication → auth}/ntlm.rb +1 -3
  25. data/lib/httpx/plugins/{authentication → auth}/socks5.rb +0 -2
  26. data/lib/httpx/plugins/auth.rb +25 -0
  27. data/lib/httpx/plugins/aws_sigv4.rb +0 -1
  28. data/lib/httpx/plugins/{basic_authentication.rb → basic_auth.rb} +5 -6
  29. data/lib/httpx/plugins/brotli.rb +50 -0
  30. data/lib/httpx/plugins/circuit_breaker/circuit.rb +40 -16
  31. data/lib/httpx/plugins/circuit_breaker/circuit_store.rb +16 -5
  32. data/lib/httpx/plugins/circuit_breaker.rb +11 -4
  33. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +0 -2
  34. data/lib/httpx/plugins/cookies.rb +1 -1
  35. data/lib/httpx/plugins/{digest_authentication.rb → digest_auth.rb} +5 -5
  36. data/lib/httpx/plugins/follow_redirects.rb +21 -24
  37. data/lib/httpx/plugins/grpc/grpc_encoding.rb +82 -0
  38. data/lib/httpx/plugins/grpc/message.rb +7 -39
  39. data/lib/httpx/plugins/grpc.rb +15 -21
  40. data/lib/httpx/plugins/h2c.rb +0 -1
  41. data/lib/httpx/plugins/{ntlm_authentication.rb → ntlm_auth.rb} +5 -5
  42. data/lib/httpx/plugins/oauth.rb +2 -2
  43. data/lib/httpx/plugins/proxy/http.rb +0 -2
  44. data/lib/httpx/plugins/proxy/socks4.rb +0 -4
  45. data/lib/httpx/plugins/proxy/socks5.rb +1 -5
  46. data/lib/httpx/plugins/proxy.rb +3 -32
  47. data/lib/httpx/plugins/retries.rb +3 -4
  48. data/lib/httpx/plugins/stream.rb +4 -6
  49. data/lib/httpx/punycode.rb +9 -291
  50. data/lib/httpx/request/body.rb +145 -0
  51. data/lib/httpx/request.rb +2 -119
  52. data/lib/httpx/resolver/https.rb +1 -1
  53. data/lib/httpx/resolver/native.rb +6 -14
  54. data/lib/httpx/resolver/resolver.rb +1 -1
  55. data/lib/httpx/resolver/system.rb +11 -9
  56. data/lib/httpx/response/body.rb +206 -0
  57. data/lib/httpx/response/buffer.rb +90 -0
  58. data/lib/httpx/response.rb +5 -208
  59. data/lib/httpx/selector.rb +0 -2
  60. data/lib/httpx/session.rb +0 -10
  61. data/lib/httpx/transcoder/body.rb +0 -1
  62. data/lib/httpx/transcoder/deflate.rb +37 -0
  63. data/lib/httpx/transcoder/form.rb +52 -32
  64. data/lib/httpx/transcoder/gzip.rb +74 -0
  65. data/lib/httpx/transcoder/json.rb +2 -4
  66. data/lib/httpx/transcoder/multipart/decoder.rb +139 -0
  67. data/lib/httpx/{plugins → transcoder}/multipart/encoder.rb +3 -3
  68. data/lib/httpx/{plugins → transcoder}/multipart/mime_type_detector.rb +1 -1
  69. data/lib/httpx/{plugins → transcoder}/multipart/part.rb +3 -2
  70. data/lib/httpx/transcoder/multipart.rb +17 -0
  71. data/lib/httpx/transcoder/utils/body_reader.rb +46 -0
  72. data/lib/httpx/transcoder/utils/deflater.rb +72 -0
  73. data/lib/httpx/transcoder/xml.rb +0 -2
  74. data/lib/httpx/transcoder.rb +2 -2
  75. data/lib/httpx/utils.rb +10 -20
  76. data/lib/httpx/version.rb +1 -1
  77. data/lib/httpx.rb +0 -8
  78. data/sig/chainable.rbs +5 -6
  79. data/sig/connection.rbs +0 -1
  80. data/sig/errors.rbs +0 -3
  81. data/sig/httpx.rbs +2 -1
  82. data/sig/io/unix.rbs +1 -1
  83. data/sig/options.rbs +12 -8
  84. data/sig/plugins/{authentication → auth}/basic.rbs +0 -2
  85. data/sig/plugins/auth.rbs +13 -0
  86. data/sig/plugins/{basic_authentication.rbs → basic_auth.rbs} +2 -2
  87. data/sig/plugins/brotli.rbs +22 -0
  88. data/sig/plugins/circuit_breaker.rbs +7 -3
  89. data/sig/plugins/compression.rbs +0 -2
  90. data/sig/plugins/{digest_authentication.rbs → digest_auth.rbs} +2 -2
  91. data/sig/plugins/follow_redirects.rbs +0 -1
  92. data/sig/plugins/grpc/call.rbs +19 -0
  93. data/sig/plugins/grpc/grpc_encoding.rbs +33 -0
  94. data/sig/plugins/grpc/message.rbs +17 -0
  95. data/sig/plugins/grpc.rbs +2 -32
  96. data/sig/plugins/{ntlm_authentication.rbs → ntlm_auth.rbs} +2 -2
  97. data/sig/plugins/oauth.rbs +1 -1
  98. data/sig/plugins/proxy/socks4.rbs +2 -3
  99. data/sig/plugins/proxy/socks5.rbs +0 -1
  100. data/sig/plugins/proxy/ssh.rbs +1 -1
  101. data/sig/plugins/response_cache.rbs +5 -2
  102. data/sig/request/body.rbs +42 -0
  103. data/sig/request.rbs +1 -27
  104. data/sig/resolver/resolver.rbs +1 -1
  105. data/sig/response/body.rbs +52 -0
  106. data/sig/response/buffer.rbs +24 -0
  107. data/sig/response.rbs +0 -39
  108. data/sig/transcoder/body.rbs +4 -3
  109. data/sig/transcoder/deflate.rbs +11 -0
  110. data/sig/transcoder/form.rbs +5 -3
  111. data/sig/transcoder/gzip.rbs +24 -0
  112. data/sig/transcoder/json.rbs +4 -2
  113. data/sig/{plugins → transcoder}/multipart.rbs +3 -10
  114. data/sig/transcoder/utils/body_reader.rbs +15 -0
  115. data/sig/transcoder/utils/deflater.rbs +29 -0
  116. data/sig/transcoder.rbs +18 -2
  117. metadata +50 -34
  118. data/lib/httpx/plugins/authentication.rb +0 -24
  119. data/lib/httpx/plugins/compression/brotli.rb +0 -54
  120. data/lib/httpx/plugins/compression/deflate.rb +0 -54
  121. data/lib/httpx/plugins/compression/gzip.rb +0 -90
  122. data/lib/httpx/plugins/compression.rb +0 -165
  123. data/lib/httpx/plugins/multipart/decoder.rb +0 -137
  124. data/lib/httpx/plugins/multipart.rb +0 -96
  125. data/sig/plugins/authentication.rbs +0 -13
  126. data/sig/plugins/compression/brotli.rbs +0 -21
  127. data/sig/plugins/compression/deflate.rbs +0 -17
  128. data/sig/plugins/compression/gzip.rbs +0 -29
  129. /data/sig/plugins/{authentication → auth}/digest.rbs +0 -0
  130. /data/sig/plugins/{authentication → auth}/ntlm.rbs +0 -0
  131. /data/sig/plugins/{authentication → auth}/socks5.rbs +0 -0
data/lib/httpx/options.rb CHANGED
@@ -7,11 +7,10 @@ module HTTPX
7
7
  BUFFER_SIZE = 1 << 14
8
8
  WINDOW_SIZE = 1 << 14 # 16K
9
9
  MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
10
- CONNECT_TIMEOUT = 60
11
- OPERATION_TIMEOUT = 60
12
10
  KEEP_ALIVE_TIMEOUT = 20
13
11
  SETTINGS_TIMEOUT = 10
14
- READ_TIMEOUT = WRITE_TIMEOUT = REQUEST_TIMEOUT = Float::INFINITY
12
+ CONNECT_TIMEOUT = READ_TIMEOUT = WRITE_TIMEOUT = 60
13
+ REQUEST_TIMEOUT = OPERATION_TIMEOUT = Float::INFINITY
15
14
 
16
15
  # https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
17
16
  ip_address_families = begin
@@ -31,6 +30,9 @@ module HTTPX
31
30
  :ssl => {},
32
31
  :http2_settings => { settings_enable_push: 0 },
33
32
  :fallback_protocol => "http/1.1",
33
+ :supported_compression_formats => %w[gzip deflate],
34
+ :decompress_response_body => true,
35
+ :compress_request_body => true,
34
36
  :timeout => {
35
37
  connect_timeout: CONNECT_TIMEOUT,
36
38
  settings_timeout: SETTINGS_TIMEOUT,
@@ -52,7 +54,6 @@ module HTTPX
52
54
  :connection_class => Class.new(Connection),
53
55
  :options_class => Class.new(self),
54
56
  :transport => nil,
55
- :transport_options => nil,
56
57
  :addresses => nil,
57
58
  :persistent => false,
58
59
  :resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
@@ -60,28 +61,6 @@ module HTTPX
60
61
  :ip_families => ip_address_families,
61
62
  }.freeze
62
63
 
63
- begin
64
- module HashExtensions
65
- refine Hash do
66
- def >=(other)
67
- Hash[other] <= self
68
- end
69
-
70
- def <=(other)
71
- other = Hash[other]
72
- return false unless size <= other.size
73
-
74
- each do |k, v|
75
- v2 = other.fetch(k) { return false }
76
- return false unless v2 == v
77
- end
78
- true
79
- end
80
- end
81
- end
82
- using HashExtensions
83
- end unless Hash.method_defined?(:>=)
84
-
85
64
  class << self
86
65
  def new(options = {})
87
66
  # let enhanced options go through
@@ -100,38 +79,10 @@ module HTTPX
100
79
 
101
80
  attr_reader(optname)
102
81
  end
103
-
104
- def def_option(optname, *args, &block)
105
- if args.empty? && !block
106
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
107
- def option_#{optname}(v); v; end # def option_smth(v); v; end
108
- OUT
109
- return
110
- end
111
-
112
- deprecated_def_option(optname, *args, &block)
113
- end
114
-
115
- def deprecated_def_option(optname, layout = nil, &interpreter)
116
- warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
117
- "Define module OptionsMethods and `def option_#{optname}(val)` instead."
118
-
119
- if layout
120
- class_eval(<<-OUT, __FILE__, __LINE__ + 1)
121
- def option_#{optname}(value) # def option_origin(v)
122
- #{layout} # URI(v)
123
- end # end
124
- OUT
125
- elsif interpreter
126
- define_method(:"option_#{optname}") do |value|
127
- instance_exec(value, &interpreter)
128
- end
129
- end
130
- end
131
82
  end
132
83
 
133
84
  def initialize(options = {})
134
- __initialize__(options)
85
+ do_initialize(options)
135
86
  freeze
136
87
  end
137
88
 
@@ -142,6 +93,7 @@ module HTTPX
142
93
  @timeout.freeze
143
94
  @headers.freeze
144
95
  @addresses.freeze
96
+ @supported_compression_formats.freeze
145
97
  end
146
98
 
147
99
  def option_origin(value)
@@ -157,14 +109,11 @@ module HTTPX
157
109
  end
158
110
 
159
111
  def option_timeout(value)
160
- timeouts = Hash[value]
161
-
162
- if timeouts.key?(:loop_timeout)
163
- warn ":loop_timeout is deprecated, use :operation_timeout instead"
164
- timeouts[:operation_timeout] = timeouts.delete(:loop_timeout)
165
- end
112
+ Hash[value]
113
+ end
166
114
 
167
- timeouts
115
+ def option_supported_compression_formats(value)
116
+ Array(value).map(&:to_s)
168
117
  end
169
118
 
170
119
  def option_max_concurrent_requests(value)
@@ -196,7 +145,10 @@ module HTTPX
196
145
  end
197
146
 
198
147
  def option_body_threshold_size(value)
199
- Integer(value)
148
+ bytes = Integer(value)
149
+ raise TypeError, ":body_threshold_size must be positive" unless bytes.positive?
150
+
151
+ bytes
200
152
  end
201
153
 
202
154
  def option_transport(value)
@@ -218,10 +170,13 @@ module HTTPX
218
170
  params form json xml body ssl http2_settings
219
171
  request_class response_class headers_class request_body_class
220
172
  response_body_class connection_class options_class
221
- io fallback_protocol debug debug_level transport_options resolver_class resolver_options
173
+ io fallback_protocol debug debug_level resolver_class resolver_options
174
+ compress_request_body decompress_response_body
222
175
  persistent
223
176
  ].each do |method_name|
224
- def_option(method_name)
177
+ class_eval(<<-OUT, __FILE__, __LINE__ + 1)
178
+ def option_#{method_name}(v); v; end # def option_smth(v); v; end
179
+ OUT
225
180
  end
226
181
 
227
182
  REQUEST_IVARS = %i[@params @form @xml @json @body].freeze
@@ -270,30 +225,15 @@ module HTTPX
270
225
  end
271
226
  end
272
227
 
273
- if RUBY_VERSION > "2.4.0"
274
- def initialize_dup(other)
275
- instance_variables.each do |ivar|
276
- instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
277
- end
278
- end
279
- else
280
- def initialize_dup(other)
281
- instance_variables.each do |ivar|
282
- value = other.instance_variable_get(ivar)
283
- value = case value
284
- when Symbol, Numeric, TrueClass, FalseClass
285
- value
286
- else
287
- value.dup
288
- end
289
- instance_variable_set(ivar, value)
290
- end
228
+ def initialize_dup(other)
229
+ instance_variables.each do |ivar|
230
+ instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
291
231
  end
292
232
  end
293
233
 
294
234
  private
295
235
 
296
- def __initialize__(options = {})
236
+ def do_initialize(options = {})
297
237
  defaults = DEFAULT_OPTIONS.merge(options)
298
238
  defaults.each do |k, v|
299
239
  next if v.nil?
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
3
+ require "httpx/base64"
4
4
 
5
5
  module HTTPX
6
6
  module Plugins
@@ -11,10 +11,6 @@ module HTTPX
11
11
  @password = password
12
12
  end
13
13
 
14
- def can_authenticate?(authenticate)
15
- authenticate && /Basic .*/.match?(authenticate)
16
- end
17
-
18
14
  def authenticate(*)
19
15
  "Basic #{Base64.strict_encode64("#{@user}:#{@password}")}"
20
16
  end
@@ -8,8 +8,6 @@ module HTTPX
8
8
  module Plugins
9
9
  module Authentication
10
10
  class Digest
11
- using RegexpExtensions unless Regexp.method_defined?(:match?)
12
-
13
11
  def initialize(user, password, hashed: false, **)
14
12
  @user = user
15
13
  @password = password
@@ -31,9 +29,8 @@ module HTTPX
31
29
  # discard first token, it's Digest
32
30
  auth_info = authenticate[/^(\w+) (.*)/, 2]
33
31
 
34
- params = Hash[auth_info.split(/ *, */)
35
- .map { |val| val.split("=") }
36
- .map { |k, v| [k, v.delete("\"")] }]
32
+ params = auth_info.split(/ *, */)
33
+ .to_h { |val| val.split("=") }.transform_values { |v| v.delete("\"") }
37
34
  nonce = params["nonce"]
38
35
  nc = next_nonce
39
36
 
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
3
+ require "httpx/base64"
4
4
  require "ntlm"
5
5
 
6
6
  module HTTPX
7
7
  module Plugins
8
8
  module Authentication
9
9
  class Ntlm
10
- using RegexpExtensions unless Regexp.method_defined?(:match?)
11
-
12
10
  def initialize(user, password, domain: nil)
13
11
  @user = user
14
12
  @password = password
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "base64"
4
-
5
3
  module HTTPX
6
4
  module Plugins
7
5
  module Authentication
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds a shim +authorization+ method to the session, which will fill
7
+ # the HTTP Authorization header, and another, +bearer_auth+, which fill the "Bearer " prefix
8
+ # in its value.
9
+ #
10
+ # https://gitlab.com/os85/httpx/wikis/Auth#authorization
11
+ #
12
+ module Auth
13
+ module InstanceMethods
14
+ def authorization(token)
15
+ with(headers: { "authorization" => token })
16
+ end
17
+
18
+ def bearer_auth(token)
19
+ authorization("Bearer #{token}")
20
+ end
21
+ end
22
+ end
23
+ register_plugin :auth, Auth
24
+ end
25
+ end
@@ -146,7 +146,6 @@ module HTTPX
146
146
 
147
147
  def configure(klass)
148
148
  klass.plugin(:expect)
149
- klass.plugin(:compression)
150
149
  end
151
150
  end
152
151
 
@@ -5,26 +5,25 @@ module HTTPX
5
5
  #
6
6
  # This plugin adds helper methods to implement HTTP Basic Auth (https://tools.ietf.org/html/rfc7617)
7
7
  #
8
- # https://gitlab.com/os85/httpx/wikis/Authentication#basic-authentication
8
+ # https://gitlab.com/os85/httpx/wikis/Authorization#basic-auth
9
9
  #
10
10
  module BasicAuth
11
11
  class << self
12
12
  def load_dependencies(_klass)
13
- require_relative "authentication/basic"
13
+ require_relative "auth/basic"
14
14
  end
15
15
 
16
16
  def configure(klass)
17
- klass.plugin(:authentication)
17
+ klass.plugin(:auth)
18
18
  end
19
19
  end
20
20
 
21
21
  module InstanceMethods
22
22
  def basic_auth(user, password)
23
- authentication(Authentication::Basic.new(user, password).authenticate)
23
+ authorization(Authentication::Basic.new(user, password).authenticate)
24
24
  end
25
- alias_method :basic_authentication, :basic_auth
26
25
  end
27
26
  end
28
- register_plugin :basic_authentication, BasicAuth
27
+ register_plugin :basic_auth, BasicAuth
29
28
  end
30
29
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ module Brotli
6
+ class Deflater < Transcoder::Deflater
7
+ def deflate(chunk)
8
+ return unless chunk
9
+
10
+ ::Brotli.deflate(chunk)
11
+ end
12
+ end
13
+
14
+ module RequestBodyClassMethods
15
+ def initialize_deflater_body(body, encoding)
16
+ return Brotli.encode(body) if encoding == "br"
17
+
18
+ super
19
+ end
20
+ end
21
+
22
+ module ResponseBodyClassMethods
23
+ def initialize_inflater_by_encoding(encoding, response, **kwargs)
24
+ return Brotli.decode(response, **kwargs) if encoding == "br"
25
+
26
+ super
27
+ end
28
+ end
29
+
30
+ module_function
31
+
32
+ def load_dependencies(*)
33
+ require "brotli"
34
+ end
35
+
36
+ def self.extra_options(options)
37
+ options.merge(supported_compression_formats: %w[br] + options.supported_compression_formats)
38
+ end
39
+
40
+ def encode(body)
41
+ Deflater.new(body)
42
+ end
43
+
44
+ def decode(_response, **)
45
+ ::Brotli.method(:inflate)
46
+ end
47
+ end
48
+ register_plugin :brotli, Brotli
49
+ end
50
+ end
@@ -15,8 +15,11 @@ module HTTPX
15
15
  @max_attempts = max_attempts
16
16
  @reset_attempts_in = reset_attempts_in
17
17
  @break_in = break_in
18
- @circuit_breaker_half_open_drip_rate = 1 - circuit_breaker_half_open_drip_rate
18
+ @circuit_breaker_half_open_drip_rate = circuit_breaker_half_open_drip_rate
19
19
  @attempts = 0
20
+
21
+ total_real_attempts = @max_attempts * @circuit_breaker_half_open_drip_rate
22
+ @drip_factor = (@max_attempts / total_real_attempts).round
20
23
  @state = :closed
21
24
  end
22
25
 
@@ -27,8 +30,13 @@ module HTTPX
27
30
  when :closed
28
31
  nil
29
32
  when :half_open
30
- # return nothing or smth based on ratio
31
- return if Random.rand >= @circuit_breaker_half_open_drip_rate
33
+ @attempts += 1
34
+
35
+ # do real requests while drip rate valid
36
+ if (@real_attempts % @drip_factor).zero?
37
+ @real_attempts += 1
38
+ return
39
+ end
32
40
 
33
41
  @response
34
42
  when :open
@@ -38,23 +46,31 @@ module HTTPX
38
46
  end
39
47
 
40
48
  def try_open(response)
41
- return unless @state == :closed
49
+ case @state
50
+ when :closed
51
+ now = Utils.now
42
52
 
43
- now = Utils.now
53
+ if @attempts.positive?
54
+ # reset if error happened long ago
55
+ @attempts = 0 if now - @attempted_at > @reset_attempts_in
56
+ else
57
+ @attempted_at = now
58
+ end
44
59
 
45
- if @attempts.positive?
46
- @attempts = 0 if now - @attempted_at > @reset_attempts_in
47
- else
48
- @attempted_at = now
49
- end
60
+ @attempts += 1
50
61
 
51
- @attempts += 1
62
+ return unless @attempts >= @max_attempts
52
63
 
53
- return unless @attempts >= @max_attempts
64
+ @state = :open
65
+ @opened_at = now
66
+ @response = response
67
+ when :half_open
68
+ # open immediately
54
69
 
55
- @state = :open
56
- @opened_at = now
57
- @response = response
70
+ @state = :open
71
+ @attempted_at = @opened_at = Utils.now
72
+ @response = response
73
+ end
58
74
  end
59
75
 
60
76
  def try_close
@@ -62,13 +78,21 @@ module HTTPX
62
78
  when :closed
63
79
  nil
64
80
  when :half_open
81
+
82
+ # do not close circuit unless attempts exhausted
83
+ return unless @attempts >= @max_attempts
84
+
65
85
  # reset!
66
86
  @attempts = 0
67
87
  @opened_at = @attempted_at = @response = nil
68
88
  @state = :closed
69
89
 
70
90
  when :open
71
- @state = :half_open if Utils.elapsed_time(@opened_at) > @break_in
91
+ if Utils.elapsed_time(@opened_at) > @break_in
92
+ @state = :half_open
93
+ @attempts = 0
94
+ @real_attempts = 0
95
+ end
72
96
  end
73
97
  end
74
98
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "mutex_m"
4
+
3
5
  module HTTPX::Plugins::CircuitBreaker
4
6
  using HTTPX::URIExtensions
5
7
 
@@ -13,18 +15,29 @@ module HTTPX::Plugins::CircuitBreaker
13
15
  options.circuit_breaker_half_open_drip_rate
14
16
  )
15
17
  end
18
+ @circuits.extend(Mutex_m)
16
19
  end
17
20
 
18
21
  def try_open(uri, response)
19
- circuit = get_circuit_for_uri(uri)
22
+ circuit = @circuits.synchronize { get_circuit_for_uri(uri) }
20
23
 
21
24
  circuit.try_open(response)
22
25
  end
23
26
 
27
+ def try_close(uri)
28
+ circuit = @circuits.synchronize do
29
+ return unless @circuits.key?(uri.origin) || @circuits.key?(uri.to_s)
30
+
31
+ get_circuit_for_uri(uri)
32
+ end
33
+
34
+ circuit.try_close
35
+ end
36
+
24
37
  # if circuit is open, it'll respond with the stored response.
25
38
  # if not, nil.
26
39
  def try_respond(request)
27
- circuit = get_circuit_for_uri(request.uri)
40
+ circuit = @circuits.synchronize { get_circuit_for_uri(request.uri) }
28
41
 
29
42
  circuit.respond
30
43
  end
@@ -32,9 +45,7 @@ module HTTPX::Plugins::CircuitBreaker
32
45
  private
33
46
 
34
47
  def get_circuit_for_uri(uri)
35
- uri = URI(uri)
36
-
37
- if @circuits.key?(uri.origin)
48
+ if uri.respond_to?(:origin) && @circuits.key?(uri.origin)
38
49
  @circuits[uri.origin]
39
50
  else
40
51
  @circuits[uri.to_s]
@@ -16,8 +16,12 @@ module HTTPX
16
16
  end
17
17
 
18
18
  def self.extra_options(options)
19
- options.merge(circuit_breaker_max_attempts: 3, circuit_breaker_reset_attempts_in: 60, circuit_breaker_break_in: 60,
20
- circuit_breaker_half_open_drip_rate: 1)
19
+ options.merge(
20
+ circuit_breaker_max_attempts: 3,
21
+ circuit_breaker_reset_attempts_in: 60,
22
+ circuit_breaker_break_in: 60,
23
+ circuit_breaker_half_open_drip_rate: 1
24
+ )
21
25
  end
22
26
 
23
27
  module InstanceMethods
@@ -47,13 +51,13 @@ module HTTPX
47
51
 
48
52
  # run all requests through the circuit breaker, see if the circuit is
49
53
  # open for any of them.
50
- real_requests = requests.each_with_object([]) do |req, real_reqs|
54
+ real_requests = requests.each_with_index.with_object([]) do |(req, idx), real_reqs|
51
55
  short_circuit_response = @circuit_store.try_respond(req)
52
56
  if short_circuit_response.nil?
53
57
  real_reqs << req
54
58
  next
55
59
  end
56
- short_circuit_responses[requests.index(req)] = short_circuit_response
60
+ short_circuit_responses[idx] = short_circuit_response
57
61
  end
58
62
 
59
63
  # run requests for the remainder
@@ -84,6 +88,9 @@ module HTTPX
84
88
  end
85
89
  elsif (break_on = request.options.circuit_breaker_break_on) && break_on.call(response)
86
90
  @circuit_store.try_open(request.uri, response)
91
+ else
92
+ @circuit_store.try_close(request.uri)
93
+ nil
87
94
  end
88
95
  end
89
96
  end
@@ -6,8 +6,6 @@ require "time"
6
6
  module HTTPX
7
7
  module Plugins::Cookies
8
8
  module SetCookieParser
9
- using(RegexpExtensions) unless Regexp.method_defined?(:match?)
10
-
11
9
  # Whitespace.
12
10
  RE_WSP = /[ \t]+/.freeze
13
11
 
@@ -71,7 +71,7 @@ module HTTPX
71
71
  end
72
72
 
73
73
  module OptionsMethods
74
- def __initialize__(*)
74
+ def do_initialize(*)
75
75
  super
76
76
 
77
77
  return unless @headers.key?("cookie")
@@ -5,7 +5,7 @@ module HTTPX
5
5
  #
6
6
  # This plugin adds helper methods to implement HTTP Digest Auth (https://tools.ietf.org/html/rfc7616)
7
7
  #
8
- # https://gitlab.com/os85/httpx/wikis/Authentication#authentication
8
+ # https://gitlab.com/os85/httpx/wikis/Authorization#digest-auth
9
9
  #
10
10
  module DigestAuth
11
11
  DigestError = Class.new(Error)
@@ -16,7 +16,7 @@ module HTTPX
16
16
  end
17
17
 
18
18
  def load_dependencies(*)
19
- require_relative "authentication/digest"
19
+ require_relative "auth/digest"
20
20
  end
21
21
  end
22
22
 
@@ -29,11 +29,11 @@ module HTTPX
29
29
  end
30
30
 
31
31
  module InstanceMethods
32
- def digest_authentication(user, password, hashed: false)
32
+ def digest_auth(user, password, hashed: false)
33
33
  with(digest: Authentication::Digest.new(user, password, hashed: hashed))
34
34
  end
35
35
 
36
- alias_method :digest_auth, :digest_authentication
36
+ private
37
37
 
38
38
  def send_requests(*requests)
39
39
  requests.flat_map do |request|
@@ -57,6 +57,6 @@ module HTTPX
57
57
  end
58
58
  end
59
59
 
60
- register_plugin :digest_authentication, DigestAuth
60
+ register_plugin :digest_auth, DigestAuth
61
61
  end
62
62
  end
@@ -48,7 +48,27 @@ module HTTPX
48
48
  return response unless REDIRECT_STATUS.include?(response.status) && response.headers.key?("location")
49
49
  return response unless max_redirects.positive?
50
50
 
51
- retry_request = build_redirect_request(redirect_request, response, options)
51
+ # build redirect request
52
+ redirect_uri = __get_location_from_response(response)
53
+
54
+ if response.status == 305 && options.respond_to?(:proxy)
55
+ # The requested resource MUST be accessed through the proxy given by
56
+ # the Location field. The Location field gives the URI of the proxy.
57
+ retry_options = options.merge(headers: redirect_request.headers,
58
+ proxy: { uri: redirect_uri },
59
+ body: redirect_request.body,
60
+ max_redirects: max_redirects - 1)
61
+ redirect_uri = redirect_request.uri
62
+ options = retry_options
63
+ else
64
+
65
+ # redirects are **ALWAYS** GET
66
+ retry_options = options.merge(headers: redirect_request.headers,
67
+ body: redirect_request.body,
68
+ max_redirects: max_redirects - 1)
69
+ end
70
+
71
+ retry_request = build_request("GET", redirect_uri, retry_options)
52
72
 
53
73
  request.redirect_request = retry_request
54
74
 
@@ -83,29 +103,6 @@ module HTTPX
83
103
  nil
84
104
  end
85
105
 
86
- def build_redirect_request(request, response, options)
87
- redirect_uri = __get_location_from_response(response)
88
- max_redirects = request.max_redirects
89
-
90
- if response.status == 305 && options.respond_to?(:proxy)
91
- # The requested resource MUST be accessed through the proxy given by
92
- # the Location field. The Location field gives the URI of the proxy.
93
- retry_options = options.merge(headers: request.headers,
94
- proxy: { uri: redirect_uri },
95
- body: request.body,
96
- max_redirects: max_redirects - 1)
97
- redirect_uri = request.url
98
- else
99
-
100
- # redirects are **ALWAYS** GET
101
- retry_options = options.merge(headers: request.headers,
102
- body: request.body,
103
- max_redirects: max_redirects - 1)
104
- end
105
-
106
- build_request("GET", redirect_uri, retry_options)
107
- end
108
-
109
106
  def __get_location_from_response(response)
110
107
  location_uri = URI(response.headers["location"])
111
108
  location_uri = response.uri.merge(location_uri) if location_uri.relative?