httpx 0.24.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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?