faraday 0.14.0 → 0.17.6

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +232 -0
  3. data/README.md +21 -7
  4. data/Rakefile +13 -0
  5. data/lib/faraday/adapter/em_http.rb +9 -9
  6. data/lib/faraday/adapter/em_synchrony.rb +5 -5
  7. data/lib/faraday/adapter/excon.rb +6 -3
  8. data/lib/faraday/adapter/httpclient.rb +4 -4
  9. data/lib/faraday/adapter/net_http.rb +25 -7
  10. data/lib/faraday/adapter/net_http_persistent.rb +33 -19
  11. data/lib/faraday/adapter/patron.rb +7 -12
  12. data/lib/faraday/adapter/rack.rb +1 -1
  13. data/lib/faraday/adapter.rb +2 -0
  14. data/lib/faraday/deprecate.rb +109 -0
  15. data/lib/faraday/error.rb +129 -34
  16. data/lib/faraday/options.rb +6 -5
  17. data/lib/faraday/parameters.rb +2 -1
  18. data/lib/faraday/rack_builder.rb +2 -2
  19. data/lib/faraday/request/retry.rb +65 -16
  20. data/lib/faraday/request.rb +22 -0
  21. data/lib/faraday/response/logger.rb +3 -3
  22. data/lib/faraday/response/raise_error.rb +7 -3
  23. data/lib/faraday/response.rb +3 -3
  24. data/lib/faraday/upload_io.rb +16 -6
  25. data/lib/faraday.rb +2 -3
  26. data/spec/faraday/deprecate_spec.rb +147 -0
  27. data/spec/faraday/error_spec.rb +102 -0
  28. data/spec/faraday/response/raise_error_spec.rb +106 -0
  29. data/spec/spec_helper.rb +105 -0
  30. data/test/adapters/default_test.rb +14 -0
  31. data/test/adapters/em_http_test.rb +30 -0
  32. data/test/adapters/em_synchrony_test.rb +32 -0
  33. data/test/adapters/excon_test.rb +30 -0
  34. data/test/adapters/httpclient_test.rb +34 -0
  35. data/test/adapters/integration.rb +263 -0
  36. data/test/adapters/logger_test.rb +136 -0
  37. data/test/adapters/net_http_persistent_test.rb +114 -0
  38. data/test/adapters/net_http_test.rb +79 -0
  39. data/test/adapters/patron_test.rb +40 -0
  40. data/test/adapters/rack_test.rb +38 -0
  41. data/test/adapters/test_middleware_test.rb +157 -0
  42. data/test/adapters/typhoeus_test.rb +38 -0
  43. data/test/authentication_middleware_test.rb +65 -0
  44. data/test/composite_read_io_test.rb +109 -0
  45. data/test/connection_test.rb +738 -0
  46. data/test/env_test.rb +268 -0
  47. data/test/helper.rb +75 -0
  48. data/test/live_server.rb +67 -0
  49. data/test/middleware/instrumentation_test.rb +88 -0
  50. data/test/middleware/retry_test.rb +282 -0
  51. data/test/middleware_stack_test.rb +260 -0
  52. data/test/multibyte.txt +1 -0
  53. data/test/options_test.rb +333 -0
  54. data/test/parameters_test.rb +157 -0
  55. data/test/request_middleware_test.rb +126 -0
  56. data/test/response_middleware_test.rb +72 -0
  57. data/test/strawberry.rb +2 -0
  58. data/test/utils_test.rb +98 -0
  59. metadata +48 -7
@@ -8,7 +8,8 @@ module Faraday
8
8
  # TODO: support streaming requests
9
9
  env[:body] = env[:body].read if env[:body].respond_to? :read
10
10
 
11
- session = @session ||= create_session
11
+ session = ::Patron::Session.new
12
+ @config_block.call(session) if @config_block
12
13
  configure_ssl(session, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
13
14
 
14
15
  if req = env[:request]
@@ -27,7 +28,7 @@ module Faraday
27
28
  data = env[:body] ? env[:body].to_s : nil
28
29
  session.request(env[:method], env[:url].to_s, env[:request_headers], :data => data)
29
30
  rescue Errno::ECONNREFUSED, ::Patron::ConnectionFailed
30
- raise Error::ConnectionFailed, $!
31
+ raise Faraday::ConnectionFailed, $!
31
32
  end
32
33
 
33
34
  # Remove the "HTTP/1.1 200", leaving just the reason phrase
@@ -38,15 +39,15 @@ module Faraday
38
39
  @app.call env
39
40
  rescue ::Patron::TimeoutError => err
40
41
  if connection_timed_out_message?(err.message)
41
- raise Faraday::Error::ConnectionFailed, err
42
+ raise Faraday::ConnectionFailed, err
42
43
  else
43
- raise Faraday::Error::TimeoutError, err
44
+ raise Faraday::TimeoutError, err
44
45
  end
45
46
  rescue ::Patron::Error => err
46
47
  if err.message.include?("code 407")
47
- raise Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
48
+ raise Faraday::ConnectionFailed, %{407 "Proxy Authentication Required "}
48
49
  else
49
- raise Error::ConnectionFailed, err
50
+ raise Faraday::ConnectionFailed, err
50
51
  end
51
52
  end
52
53
 
@@ -65,12 +66,6 @@ module Faraday
65
66
  end
66
67
  end
67
68
 
68
- def create_session
69
- session = ::Patron::Session.new
70
- @config_block.call(session) if @config_block
71
- session
72
- end
73
-
74
69
  def configure_ssl(session, ssl)
75
70
  if ssl.fetch(:verify, true)
76
71
  session.cacert = ssl[:ca_file]
@@ -41,7 +41,7 @@ module Faraday
41
41
 
42
42
  timeout = env[:request][:timeout] || env[:request][:open_timeout]
43
43
  response = if timeout
44
- Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
44
+ Timer.timeout(timeout, Faraday::TimeoutError) { execute_request(env, rack_env) }
45
45
  else
46
46
  execute_request(env, rack_env)
47
47
  end
@@ -40,6 +40,8 @@ module Faraday
40
40
  env.clear_body if env.needs_body?
41
41
  end
42
42
 
43
+ private
44
+
43
45
  def save_response(env, status, body, headers = nil, reason_phrase = nil)
44
46
  env.status = status
45
47
  env.body = body
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faraday
4
+ # @param new_klass [Class] new Klass to use
5
+ #
6
+ # @return [Class] A modified version of new_klass that warns on
7
+ # usage about deprecation.
8
+ # @see Faraday::Deprecate
9
+ module DeprecatedClass
10
+ def self.proxy_class(origclass, ver = '1.0')
11
+ proxy = Class.new(origclass) do
12
+ const_set("ORIG_CLASS", origclass)
13
+
14
+ class << self
15
+ extend Faraday::Deprecate
16
+
17
+ def ===(other)
18
+ (superclass == const_get("ORIG_CLASS") && other.is_a?(superclass)) || super
19
+ end
20
+ end
21
+ end
22
+ proxy.singleton_class.send(:deprecate, :new, "#{origclass}.new", ver)
23
+ proxy.singleton_class.send(:deprecate, :inherited, origclass.name, ver)
24
+ proxy
25
+ end
26
+ end
27
+
28
+ # Deprecation using semver instead of date, based on Gem::Deprecate
29
+ # Provides a single method +deprecate+ to be used to declare when
30
+ # something is going away.
31
+ #
32
+ # class Legacy
33
+ # def self.klass_method
34
+ # # ...
35
+ # end
36
+ #
37
+ # def instance_method
38
+ # # ...
39
+ # end
40
+ #
41
+ # extend Faraday::Deprecate
42
+ # deprecate :instance_method, "X.z", '1.0'
43
+ #
44
+ # class << self
45
+ # extend Faraday::Deprecate
46
+ # deprecate :klass_method, :none, '1.0'
47
+ # end
48
+ # end
49
+ module Deprecate
50
+ def self.skip # :nodoc:
51
+ @skip ||= begin
52
+ case ENV['FARADAY_DEPRECATE'].to_s.downcase
53
+ when '1', 'warn' then :warn
54
+ else :skip
55
+ end
56
+ end
57
+ @skip == :skip
58
+ end
59
+
60
+ def self.skip=(value) # :nodoc:
61
+ @skip = value ? :skip : :warn
62
+ end
63
+
64
+ # Temporarily turn off warnings. Intended for tests only.
65
+ def skip_during
66
+ original = Faraday::Deprecate.skip
67
+ Faraday::Deprecate.skip, = true
68
+ yield
69
+ ensure
70
+ Faraday::Deprecate.skip = original
71
+ end
72
+
73
+ # Simple deprecation method that deprecates +name+ by wrapping it up
74
+ # in a dummy method. It warns on each call to the dummy method
75
+ # telling the user of +repl+ (unless +repl+ is :none) and the
76
+ # semver that it is planned to go away.
77
+ # @param name [Symbol] the method symbol to deprecate
78
+ # @param repl [#to_s, :none] the replacement to use, when `:none` it will
79
+ # alert the user that no replacemtent is present.
80
+ # @param ver [String] the semver the method will be removed.
81
+ def deprecate(name, repl, ver)
82
+ class_eval do
83
+ gem_ver = Gem::Version.new(ver)
84
+ old = "_deprecated_#{name}"
85
+ alias_method old, name
86
+ define_method name do |*args, &block|
87
+ mod = is_a? Module
88
+ target = mod ? "#{self}." : "#{self.class}#"
89
+ target_message = if name == :inherited
90
+ "Inheriting #{self}"
91
+ else
92
+ "#{target}#{name}"
93
+ end
94
+
95
+ msg = [
96
+ "NOTE: #{target_message} is deprecated",
97
+ repl == :none ? ' with no replacement' : "; use #{repl} instead. ",
98
+ "It will be removed in or after version #{gem_ver}",
99
+ "\n#{target}#{name} called from #{Gem.location_of_caller.join(':')}"
100
+ ]
101
+ warn "#{msg.join}." unless Faraday::Deprecate.skip
102
+ send old, *args, &block
103
+ end
104
+ end
105
+ end
106
+
107
+ module_function :deprecate, :skip_during
108
+ end
109
+ end
data/lib/faraday/error.rb CHANGED
@@ -1,23 +1,17 @@
1
- module Faraday
2
- class Error < StandardError; end
3
- class MissingDependency < Error; end
1
+ # frozen_string_literal: true
4
2
 
5
- class ClientError < Error
6
- attr_reader :response, :wrapped_exception
3
+ require 'faraday/deprecate'
7
4
 
8
- def initialize(ex, response = nil)
9
- @wrapped_exception = nil
10
- @response = response
5
+ # Faraday namespace.
6
+ module Faraday
7
+ # Faraday error base class.
8
+ class Error < StandardError
9
+ attr_reader :response, :wrapped_exception
11
10
 
12
- if ex.respond_to?(:backtrace)
13
- super(ex.message)
14
- @wrapped_exception = ex
15
- elsif ex.respond_to?(:each_key)
16
- super("the server responded with status #{ex[:status]}")
17
- @response = ex
18
- else
19
- super(ex.to_s)
20
- end
11
+ def initialize(exc, response = nil)
12
+ @wrapped_exception = nil unless defined?(@wrapped_exception)
13
+ @response = nil unless defined?(@response)
14
+ super(exc_msg_and_response!(exc, response))
21
15
  end
22
16
 
23
17
  def backtrace
@@ -30,34 +24,135 @@ module Faraday
30
24
 
31
25
  def inspect
32
26
  inner = ''
33
- if @wrapped_exception
34
- inner << " wrapped=#{@wrapped_exception.inspect}"
35
- end
36
- if @response
37
- inner << " response=#{@response.inspect}"
38
- end
39
- if inner.empty?
40
- inner << " #{super}"
41
- end
27
+ inner += " wrapped=#{@wrapped_exception.inspect}" if @wrapped_exception
28
+ inner += " response=#{@response.inspect}" if @response
29
+ inner += " #{super}" if inner.empty?
42
30
  %(#<#{self.class}#{inner}>)
43
31
  end
32
+
33
+ protected
34
+
35
+ # Pulls out potential parent exception and response hash, storing them in
36
+ # instance variables.
37
+ # exc - Either an Exception, a string message, or a response hash.
38
+ # response - Hash
39
+ # :status - Optional integer HTTP response status
40
+ # :headers - String key/value hash of HTTP response header
41
+ # values.
42
+ # :body - Optional string HTTP response body.
43
+ #
44
+ # If a subclass has to call this, then it should pass a string message
45
+ # to `super`. See NilStatusError.
46
+ def exc_msg_and_response!(exc, response = nil)
47
+ if @response.nil? && @wrapped_exception.nil?
48
+ @wrapped_exception, msg, @response = exc_msg_and_response(exc, response)
49
+ return msg
50
+ end
51
+
52
+ exc.to_s
53
+ end
54
+
55
+ # Pulls out potential parent exception and response hash.
56
+ def exc_msg_and_response(exc, response = nil)
57
+ return [exc, exc.message, response] if exc.respond_to?(:backtrace)
58
+
59
+ return [nil, "the server responded with status #{exc[:status]}", exc] \
60
+ if exc.respond_to?(:each_key)
61
+
62
+ [nil, exc.to_s, response]
63
+ end
64
+ end
65
+
66
+ # Faraday client error class. Represents 4xx status responses.
67
+ class ClientError < Error
68
+ end
69
+
70
+ # Raised by Faraday::Response::RaiseError in case of a 400 response.
71
+ class BadRequestError < ClientError
72
+ end
73
+
74
+ # Raised by Faraday::Response::RaiseError in case of a 401 response.
75
+ class UnauthorizedError < ClientError
44
76
  end
45
77
 
46
- class ConnectionFailed < ClientError; end
47
- class ResourceNotFound < ClientError; end
48
- class ParsingError < ClientError; end
78
+ # Raised by Faraday::Response::RaiseError in case of a 403 response.
79
+ class ForbiddenError < ClientError
80
+ end
81
+
82
+ # Raised by Faraday::Response::RaiseError in case of a 404 response.
83
+ class ResourceNotFound < ClientError
84
+ end
85
+
86
+ # Raised by Faraday::Response::RaiseError in case of a 407 response.
87
+ class ProxyAuthError < ClientError
88
+ end
89
+
90
+ # Raised by Faraday::Response::RaiseError in case of a 409 response.
91
+ class ConflictError < ClientError
92
+ end
49
93
 
94
+ # Raised by Faraday::Response::RaiseError in case of a 422 response.
95
+ class UnprocessableEntityError < ClientError
96
+ end
97
+
98
+ # Faraday server error class. Represents 5xx status responses.
99
+ class ServerError < Error
100
+ end
101
+
102
+ # A unified client error for timeouts.
50
103
  class TimeoutError < ClientError
51
- def initialize(ex = nil)
52
- super(ex || "timeout")
104
+ def initialize(exc = 'timeout', response = nil)
105
+ super(exc, response)
53
106
  end
54
107
  end
55
108
 
109
+ # Raised by Faraday::Response::RaiseError in case of a nil status in response.
110
+ class NilStatusError < ServerError
111
+ def initialize(exc, response = nil)
112
+ exc_msg_and_response!(exc, response)
113
+ @response = unwrap_resp!(@response)
114
+ super('http status could not be derived from the server response')
115
+ end
116
+
117
+ private
118
+
119
+ extend Faraday::Deprecate
120
+
121
+ def unwrap_resp(resp)
122
+ if inner = (resp.keys.size == 1 && resp[:response])
123
+ return unwrap_resp(inner)
124
+ end
125
+
126
+ resp
127
+ end
128
+
129
+ alias_method :unwrap_resp!, :unwrap_resp
130
+ deprecate('unwrap_resp', nil, '1.0')
131
+ end
132
+
133
+ # A unified error for failed connections.
134
+ class ConnectionFailed < ClientError
135
+ end
136
+
137
+ # A unified client error for SSL errors.
56
138
  class SSLError < ClientError
57
139
  end
58
140
 
59
- [:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
60
- :ParsingError, :TimeoutError, :SSLError].each do |const|
61
- Error.const_set(const, Faraday.const_get(const))
141
+ # Raised by FaradayMiddleware::ResponseMiddleware
142
+ class ParsingError < ClientError
143
+ end
144
+
145
+ # Exception used to control the Retry middleware.
146
+ #
147
+ # @see Faraday::Request::Retry
148
+ class RetriableResponse < ClientError
149
+ end
150
+
151
+ [:ClientError, :ConnectionFailed, :ResourceNotFound,
152
+ :ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
153
+ Error.const_set(
154
+ const,
155
+ DeprecatedClass.proxy_class(Faraday.const_get(const))
156
+ )
62
157
  end
63
158
  end
@@ -72,7 +72,7 @@ module Faraday
72
72
  if args.size > 0
73
73
  send(key_setter, args.first)
74
74
  elsif block_given?
75
- send(key_setter, Proc.new.call(key))
75
+ send(key_setter, yield(key))
76
76
  else
77
77
  raise self.class.fetch_error_class, "key not found: #{key.inspect}"
78
78
  end
@@ -162,8 +162,8 @@ module Faraday
162
162
  @attribute_options ||= {}
163
163
  end
164
164
 
165
- def self.memoized(key)
166
- memoized_attributes[key.to_sym] = Proc.new
165
+ def self.memoized(key, &block)
166
+ memoized_attributes[key.to_sym] = block
167
167
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
168
168
  def #{key}() self[:#{key}]; end
169
169
  RUBY
@@ -202,7 +202,7 @@ module Faraday
202
202
  end
203
203
 
204
204
  class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
205
- :timeout, :open_timeout, :boundary, :oauth, :context)
205
+ :timeout, :open_timeout, :write_timeout, :boundary, :oauth, :context)
206
206
 
207
207
  def []=(key, value)
208
208
  if key && key.to_sym == :proxy
@@ -214,7 +214,8 @@ module Faraday
214
214
  end
215
215
 
216
216
  class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
217
- :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth, :version)
217
+ :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth,
218
+ :version, :min_version, :max_version)
218
219
 
219
220
  def verify?
220
221
  verify != false
@@ -40,9 +40,10 @@ module Faraday
40
40
  end
41
41
  return buffer.chop
42
42
  elsif value.is_a?(Array)
43
+ new_parent = "#{parent}%5B%5D"
44
+ return new_parent if value.empty?
43
45
  buffer = ""
44
46
  value.each_with_index do |val, i|
45
- new_parent = "#{parent}%5B%5D"
46
47
  buffer << "#{to_query.call(new_parent, val)}&"
47
48
  end
48
49
  return buffer.chop
@@ -49,10 +49,10 @@ module Faraday
49
49
  end
50
50
  end
51
51
 
52
- def initialize(handlers = [])
52
+ def initialize(handlers = [], &block)
53
53
  @handlers = handlers
54
54
  if block_given?
55
- build(&Proc.new)
55
+ build(&block)
56
56
  elsif @handlers.empty?
57
57
  # default stack, if nothing else is configured
58
58
  self.request :url_encoded
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  module Faraday
2
4
  # Catches exceptions and retries each request a limited number of times.
3
5
  #
@@ -19,11 +21,15 @@ module Faraday
19
21
  # interval that is random between 0.1 and 0.15
20
22
  #
21
23
  class Request::Retry < Faraday::Middleware
22
-
24
+ DEFAULT_EXCEPTIONS = [Errno::ETIMEDOUT, 'Timeout::Error',
25
+ Faraday::TimeoutError, Faraday::RetriableResponse
26
+ ].freeze
23
27
  IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
24
28
 
25
29
  class Options < Faraday::Options.new(:max, :interval, :max_interval, :interval_randomness,
26
- :backoff_factor, :exceptions, :methods, :retry_if)
30
+ :backoff_factor, :exceptions, :methods, :retry_if, :retry_block,
31
+ :retry_statuses)
32
+
27
33
  DEFAULT_CHECK = lambda { |env,exception| false }
28
34
 
29
35
  def self.from(value)
@@ -55,8 +61,7 @@ module Faraday
55
61
  end
56
62
 
57
63
  def exceptions
58
- Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
59
- Error::TimeoutError])
64
+ Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
60
65
  end
61
66
 
62
67
  def methods
@@ -67,6 +72,13 @@ module Faraday
67
72
  self[:retry_if] ||= DEFAULT_CHECK
68
73
  end
69
74
 
75
+ def retry_block
76
+ self[:retry_block] ||= Proc.new {}
77
+ end
78
+
79
+ def retry_statuses
80
+ Array(self[:retry_statuses] ||= [])
81
+ end
70
82
  end
71
83
 
72
84
  # Public: Initialize middleware
@@ -83,8 +95,8 @@ module Faraday
83
95
  # (default: 1)
84
96
  # exceptions - The list of exceptions to handle. Exceptions can be
85
97
  # given as Class, Module, or String. (default:
86
- # [Errno::ETIMEDOUT, Timeout::Error,
87
- # Error::TimeoutError])
98
+ # [Errno::ETIMEDOUT, 'Timeout::Error',
99
+ # Faraday::TimeoutError, Faraday::RetriableResponse])
88
100
  # methods - A list of HTTP methods to retry without calling retry_if. Pass
89
101
  # an empty Array to call retry_if for all exceptions.
90
102
  # (defaults to the idempotent HTTP methods in IDEMPOTENT_METHODS)
@@ -94,18 +106,21 @@ module Faraday
94
106
  # if the exception produced is non-recoverable or if the
95
107
  # the HTTP method called is not idempotent.
96
108
  # (defaults to return false)
109
+ # retry_block - block that is executed after every retry. Request environment, middleware options,
110
+ # current number of retries and the exception is passed to the block as parameters.
97
111
  def initialize(app, options = nil)
98
112
  super(app)
99
113
  @options = Options.from(options)
100
114
  @errmatch = build_exception_matcher(@options.exceptions)
101
115
  end
102
116
 
103
- def sleep_amount(retries)
104
- retry_index = @options.max - retries
105
- current_interval = @options.interval * (@options.backoff_factor ** retry_index)
106
- current_interval = [current_interval, @options.max_interval].min
107
- random_interval = rand * @options.interval_randomness.to_f * @options.interval
108
- current_interval + random_interval
117
+ def calculate_sleep_amount(retries, env)
118
+ retry_after = calculate_retry_after(env)
119
+ retry_interval = calculate_retry_interval(retries)
120
+
121
+ return if retry_after && retry_after > @options.max_interval
122
+
123
+ retry_after && retry_after >= retry_interval ? retry_after : retry_interval
109
124
  end
110
125
 
111
126
  def call(env)
@@ -113,15 +128,25 @@ module Faraday
113
128
  request_body = env[:body]
114
129
  begin
115
130
  env[:body] = request_body # after failure env[:body] is set to the response body
116
- @app.call(env)
131
+ @app.call(env).tap do |resp|
132
+ raise Faraday::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status)
133
+ end
117
134
  rescue @errmatch => exception
118
135
  if retries > 0 && retry_request?(env, exception)
119
136
  retries -= 1
120
137
  rewind_files(request_body)
121
- sleep sleep_amount(retries + 1)
122
- retry
138
+ @options.retry_block.call(env, @options, retries, exception)
139
+ if (sleep_amount = calculate_sleep_amount(retries + 1, env))
140
+ sleep sleep_amount
141
+ retry
142
+ end
143
+ end
144
+
145
+ if exception.is_a?(Faraday::RetriableResponse)
146
+ exception.response
147
+ else
148
+ raise
123
149
  end
124
- raise
125
150
  end
126
151
  end
127
152
 
@@ -160,5 +185,29 @@ module Faraday
160
185
  end
161
186
  end
162
187
 
188
+ # MDN spec for Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
189
+ def calculate_retry_after(env)
190
+ response_headers = env[:response_headers]
191
+ return unless response_headers
192
+
193
+ retry_after_value = env[:response_headers]["Retry-After"]
194
+
195
+ # Try to parse date from the header value
196
+ begin
197
+ datetime = DateTime.rfc2822(retry_after_value)
198
+ datetime.to_time - Time.now.utc
199
+ rescue ArgumentError
200
+ retry_after_value.to_f
201
+ end
202
+ end
203
+
204
+ def calculate_retry_interval(retries)
205
+ retry_index = @options.max - retries
206
+ current_interval = @options.interval * (@options.backoff_factor ** retry_index)
207
+ current_interval = [current_interval, @options.max_interval].min
208
+ random_interval = rand * @options.interval_randomness.to_f * @options.interval
209
+
210
+ current_interval + random_interval
211
+ end
163
212
  end
164
213
  end
@@ -12,6 +12,8 @@ module Faraday
12
12
  class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
13
13
  extend MiddlewareRegistry
14
14
 
15
+ alias_method :http_method, :method
16
+
15
17
  register_middleware File.expand_path('../request', __FILE__),
16
18
  :url_encoded => [:UrlEncoded, 'url_encoded'],
17
19
  :multipart => [:Multipart, 'multipart'],
@@ -69,6 +71,26 @@ module Faraday
69
71
  headers[key] = value
70
72
  end
71
73
 
74
+ def marshal_dump
75
+ {
76
+ :method => method,
77
+ :body => body,
78
+ :headers => headers,
79
+ :path => path,
80
+ :params => params,
81
+ :options => options
82
+ }
83
+ end
84
+
85
+ def marshal_load(serialised)
86
+ self.method = serialised[:method]
87
+ self.body = serialised[:body]
88
+ self.headers = serialised[:headers]
89
+ self.path = serialised[:path]
90
+ self.params = serialised[:params]
91
+ self.options = serialised[:options]
92
+ end
93
+
72
94
  # ENV Keys
73
95
  # :method - a symbolized request method (:get, :post)
74
96
  # :body - the request body that will eventually be converted to a string.
@@ -10,7 +10,7 @@ module Faraday
10
10
  super(app)
11
11
  @logger = logger || begin
12
12
  require 'logger'
13
- ::Logger.new(STDOUT)
13
+ ::Logger.new($stdout)
14
14
  end
15
15
  @filter = []
16
16
  @options = DEFAULT_OPTIONS.merge(options)
@@ -20,14 +20,14 @@ module Faraday
20
20
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
21
21
 
22
22
  def call(env)
23
- info "#{env.method} #{apply_filters(env.url.to_s)}"
23
+ info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
24
24
  debug('request') { apply_filters( dump_headers env.request_headers ) } if log_headers?(:request)
25
25
  debug('request') { apply_filters( dump_body(env[:body]) ) } if env[:body] && log_body?(:request)
26
26
  super
27
27
  end
28
28
 
29
29
  def on_complete(env)
30
- info('Status') { env.status.to_s }
30
+ info('response') { "Status #{env.status.to_s}" }
31
31
  debug('response') { apply_filters( dump_headers env.response_headers ) } if log_headers?(:response)
32
32
  debug('response') { apply_filters( dump_body env[:body] ) } if env[:body] && log_body?(:response)
33
33
  end
@@ -5,12 +5,16 @@ module Faraday
5
5
  def on_complete(env)
6
6
  case env[:status]
7
7
  when 404
8
- raise Faraday::Error::ResourceNotFound, response_values(env)
8
+ raise Faraday::ResourceNotFound, response_values(env)
9
9
  when 407
10
10
  # mimic the behavior that we get with proxy requests with HTTPS
11
- raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
11
+ raise Faraday::ConnectionFailed.new(
12
+ %{407 "Proxy Authentication Required "},
13
+ response_values(env))
12
14
  when ClientErrorStatuses
13
- raise Faraday::Error::ClientError, response_values(env)
15
+ raise Faraday::ClientError, response_values(env)
16
+ when nil
17
+ raise Faraday::NilStatusError, response_values(env)
14
18
  end
15
19
  end
16
20
 
@@ -54,9 +54,9 @@ module Faraday
54
54
  !!env
55
55
  end
56
56
 
57
- def on_complete
58
- if not finished?
59
- @on_complete_callbacks << Proc.new
57
+ def on_complete(&block)
58
+ if !finished?
59
+ @on_complete_callbacks << block
60
60
  else
61
61
  yield(env)
62
62
  end