faraday 0.9.1 → 0.17.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 (73) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.md +1 -1
  3. data/README.md +192 -28
  4. data/lib/faraday/adapter/em_http.rb +8 -2
  5. data/lib/faraday/adapter/em_http_ssl_patch.rb +1 -1
  6. data/lib/faraday/adapter/em_synchrony.rb +16 -2
  7. data/lib/faraday/adapter/excon.rb +10 -8
  8. data/lib/faraday/adapter/httpclient.rb +27 -5
  9. data/lib/faraday/adapter/net_http.rb +34 -12
  10. data/lib/faraday/adapter/net_http_persistent.rb +35 -15
  11. data/lib/faraday/adapter/patron.rb +39 -16
  12. data/lib/faraday/adapter/test.rb +79 -28
  13. data/lib/faraday/adapter/typhoeus.rb +4 -115
  14. data/lib/faraday/adapter.rb +10 -1
  15. data/lib/faraday/autoload.rb +1 -1
  16. data/lib/faraday/connection.rb +72 -20
  17. data/lib/faraday/error.rb +18 -5
  18. data/lib/faraday/options.rb +42 -19
  19. data/lib/faraday/parameters.rb +56 -39
  20. data/lib/faraday/rack_builder.rb +27 -2
  21. data/lib/faraday/request/authorization.rb +1 -2
  22. data/lib/faraday/request/multipart.rb +7 -2
  23. data/lib/faraday/request/retry.rb +82 -18
  24. data/lib/faraday/request.rb +22 -0
  25. data/lib/faraday/response/logger.rb +29 -8
  26. data/lib/faraday/response.rb +6 -2
  27. data/lib/faraday/utils.rb +32 -3
  28. data/lib/faraday.rb +14 -34
  29. metadata +7 -94
  30. data/.document +0 -6
  31. data/CHANGELOG.md +0 -20
  32. data/CONTRIBUTING.md +0 -36
  33. data/Gemfile +0 -25
  34. data/Rakefile +0 -71
  35. data/faraday.gemspec +0 -34
  36. data/script/cached-bundle +0 -46
  37. data/script/console +0 -7
  38. data/script/generate_certs +0 -42
  39. data/script/package +0 -7
  40. data/script/proxy-server +0 -42
  41. data/script/release +0 -17
  42. data/script/s3-put +0 -71
  43. data/script/server +0 -36
  44. data/script/test +0 -172
  45. data/test/adapters/default_test.rb +0 -14
  46. data/test/adapters/em_http_test.rb +0 -20
  47. data/test/adapters/em_synchrony_test.rb +0 -20
  48. data/test/adapters/excon_test.rb +0 -20
  49. data/test/adapters/httpclient_test.rb +0 -21
  50. data/test/adapters/integration.rb +0 -254
  51. data/test/adapters/logger_test.rb +0 -82
  52. data/test/adapters/net_http_persistent_test.rb +0 -20
  53. data/test/adapters/net_http_test.rb +0 -14
  54. data/test/adapters/patron_test.rb +0 -20
  55. data/test/adapters/rack_test.rb +0 -31
  56. data/test/adapters/test_middleware_test.rb +0 -114
  57. data/test/adapters/typhoeus_test.rb +0 -28
  58. data/test/authentication_middleware_test.rb +0 -65
  59. data/test/composite_read_io_test.rb +0 -111
  60. data/test/connection_test.rb +0 -522
  61. data/test/env_test.rb +0 -218
  62. data/test/helper.rb +0 -81
  63. data/test/live_server.rb +0 -67
  64. data/test/middleware/instrumentation_test.rb +0 -88
  65. data/test/middleware/retry_test.rb +0 -177
  66. data/test/middleware_stack_test.rb +0 -173
  67. data/test/multibyte.txt +0 -1
  68. data/test/options_test.rb +0 -252
  69. data/test/parameters_test.rb +0 -64
  70. data/test/request_middleware_test.rb +0 -142
  71. data/test/response_middleware_test.rb +0 -72
  72. data/test/strawberry.rb +0 -2
  73. data/test/utils_test.rb +0 -58
data/lib/faraday/error.rb CHANGED
@@ -1,9 +1,8 @@
1
1
  module Faraday
2
2
  class Error < StandardError; end
3
- class MissingDependency < Error; end
4
3
 
5
4
  class ClientError < Error
6
- attr_reader :response
5
+ attr_reader :response, :wrapped_exception
7
6
 
8
7
  def initialize(ex, response = nil)
9
8
  @wrapped_exception = nil
@@ -29,7 +28,17 @@ module Faraday
29
28
  end
30
29
 
31
30
  def inspect
32
- %(#<#{self.class}>)
31
+ inner = ''
32
+ if @wrapped_exception
33
+ inner << " wrapped=#{@wrapped_exception.inspect}"
34
+ end
35
+ if @response
36
+ inner << " response=#{@response.inspect}"
37
+ end
38
+ if inner.empty?
39
+ inner << " #{super}"
40
+ end
41
+ %(#<#{self.class}#{inner}>)
33
42
  end
34
43
  end
35
44
 
@@ -46,8 +55,12 @@ module Faraday
46
55
  class SSLError < ClientError
47
56
  end
48
57
 
49
- [:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
50
- :ParsingError, :TimeoutError, :SSLError].each do |const|
58
+ class RetriableResponse < ClientError; end
59
+
60
+ [:ClientError, :ConnectionFailed, :ResourceNotFound,
61
+ :ParsingError, :TimeoutError, :SSLError, :RetriableResponse].each do |const|
51
62
  Error.const_set(const, Faraday.const_get(const))
52
63
  end
64
+
65
+
53
66
  end
@@ -18,23 +18,20 @@ module Faraday
18
18
  # Public
19
19
  def update(obj)
20
20
  obj.each do |key, value|
21
- if sub_options = self.class.options_for(key)
22
- value = sub_options.from(value) if value
23
- elsif Hash === value
24
- hash = {}
25
- value.each do |hash_key, hash_value|
26
- hash[hash_key] = hash_value
27
- end
28
- value = hash
21
+ sub_options = self.class.options_for(key)
22
+ if sub_options
23
+ new_value = sub_options.from(value) if value
24
+ elsif value.is_a?(Hash)
25
+ new_value = value.dup
26
+ else
27
+ new_value = value
29
28
  end
30
29
 
31
- self.send("#{key}=", value) unless value.nil?
30
+ self.send("#{key}=", new_value) unless new_value.nil?
32
31
  end
33
32
  self
34
33
  end
35
34
 
36
- alias merge! update
37
-
38
35
  # Public
39
36
  def delete(key)
40
37
  value = send(key)
@@ -48,8 +45,24 @@ module Faraday
48
45
  end
49
46
 
50
47
  # Public
51
- def merge(value)
52
- dup.update(value)
48
+ def merge!(other)
49
+ other.each do |key, other_value|
50
+ self_value = self.send(key)
51
+ sub_options = self.class.options_for(key)
52
+ new_value = (self_value && sub_options && other_value) ? self_value.merge(other_value) : other_value
53
+ self.send("#{key}=", new_value) unless new_value.nil?
54
+ end
55
+ self
56
+ end
57
+
58
+ # Public
59
+ def merge(other)
60
+ dup.merge!(other)
61
+ end
62
+
63
+ # Public
64
+ def deep_dup
65
+ self.class.from(self)
53
66
  end
54
67
 
55
68
  # Public
@@ -189,8 +202,7 @@ module Faraday
189
202
  end
190
203
 
191
204
  class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
192
- :timeout, :open_timeout, :boundary,
193
- :oauth)
205
+ :timeout, :open_timeout, :write_timeout, :boundary, :oauth, :context)
194
206
 
195
207
  def []=(key, value)
196
208
  if key && key.to_sym == :proxy
@@ -202,7 +214,8 @@ module Faraday
202
214
  end
203
215
 
204
216
  class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
205
- :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)
206
219
 
207
220
  def verify?
208
221
  verify != false
@@ -231,8 +244,8 @@ module Faraday
231
244
  super(value)
232
245
  end
233
246
 
234
- memoized(:user) { uri.user && Utils.unescape(uri.user) }
235
- memoized(:password) { uri.password && Utils.unescape(uri.password) }
247
+ memoized(:user) { uri && uri.user && Utils.unescape(uri.user) }
248
+ memoized(:password) { uri && uri.password && Utils.unescape(uri.password) }
236
249
  end
237
250
 
238
251
  class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
@@ -252,7 +265,8 @@ module Faraday
252
265
  end
253
266
 
254
267
  class Env < Options.new(:method, :body, :url, :request, :request_headers,
255
- :ssl, :parallel_manager, :params, :response, :response_headers, :status)
268
+ :ssl, :parallel_manager, :params, :response, :response_headers, :status,
269
+ :reason_phrase)
256
270
 
257
271
  ContentLength = 'Content-Length'.freeze
258
272
  StatusesWithoutBody = Set.new [204, 304]
@@ -269,6 +283,15 @@ module Faraday
269
283
 
270
284
  def_delegators :request, :params_encoder
271
285
 
286
+ # Public
287
+ def self.from(value)
288
+ env = super(value)
289
+ if value.respond_to?(:custom_members)
290
+ env.custom_members.update(value.custom_members)
291
+ end
292
+ env
293
+ end
294
+
272
295
  # Public
273
296
  def [](key)
274
297
  if in_member_set?(key)
@@ -40,12 +40,15 @@ 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
50
+ elsif value.nil?
51
+ return parent
49
52
  else
50
53
  encoded_value = escape(value)
51
54
  return "#{parent}=#{encoded_value}"
@@ -63,50 +66,64 @@ module Faraday
63
66
 
64
67
  def self.decode(query)
65
68
  return nil if query == nil
66
- # Recursive helper lambda
67
- dehash = lambda do |hash|
68
- hash.each do |(key, value)|
69
- if value.kind_of?(Hash)
70
- hash[key] = dehash.call(value)
69
+
70
+ params = {}
71
+ query.split("&").each do |pair|
72
+ next if pair.empty?
73
+ key, value = pair.split("=", 2)
74
+ key = unescape(key)
75
+ value = unescape(value.gsub(/\+/, ' ')) if value
76
+
77
+ subkeys = key.scan(/[^\[\]]+(?:\]?\[\])?/)
78
+ context = params
79
+ subkeys.each_with_index do |subkey, i|
80
+ is_array = subkey =~ /[\[\]]+\Z/
81
+ subkey = $` if is_array
82
+ last_subkey = i == subkeys.length - 1
83
+
84
+ if !last_subkey || is_array
85
+ value_type = is_array ? Array : Hash
86
+ if context[subkey] && !context[subkey].is_a?(value_type)
87
+ raise TypeError, "expected %s (got %s) for param `%s'" % [
88
+ value_type.name,
89
+ context[subkey].class.name,
90
+ subkey
91
+ ]
92
+ end
93
+ context = (context[subkey] ||= value_type.new)
71
94
  end
72
- end
73
- # Numeric keys implies an array
74
- if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
75
- hash.sort.inject([]) do |accu, (_, value)|
76
- accu << value; accu
95
+
96
+ if context.is_a?(Array) && !is_array
97
+ if !context.last.is_a?(Hash) || context.last.has_key?(subkey)
98
+ context << {}
99
+ end
100
+ context = context.last
101
+ end
102
+
103
+ if last_subkey
104
+ if is_array
105
+ context << value
106
+ else
107
+ context[subkey] = value
108
+ end
77
109
  end
78
- else
79
- hash
80
110
  end
81
111
  end
82
112
 
83
- empty_accumulator = {}
84
- return ((query.split('&').map do |pair|
85
- pair.split('=', 2) if pair && !pair.empty?
86
- end).compact.inject(empty_accumulator.dup) do |accu, (key, value)|
87
- key = unescape(key)
88
- if value.kind_of?(String)
89
- value = unescape(value.gsub(/\+/, ' '))
90
- end
113
+ dehash(params, 0)
114
+ end
91
115
 
92
- array_notation = !!(key =~ /\[\]$/)
93
- subkeys = key.split(/[\[\]]+/)
94
- current_hash = accu
95
- for i in 0...(subkeys.size - 1)
96
- subkey = subkeys[i]
97
- current_hash[subkey] = {} unless current_hash[subkey]
98
- current_hash = current_hash[subkey]
99
- end
100
- if array_notation
101
- current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
102
- current_hash[subkeys.last] << value
103
- else
104
- current_hash[subkeys.last] = value
105
- end
106
- accu
107
- end).inject(empty_accumulator.dup) do |accu, (key, value)|
108
- accu[key] = value.kind_of?(Hash) ? dehash.call(value) : value
109
- accu
116
+ # Internal: convert a nested hash with purely numeric keys into an array.
117
+ # FIXME: this is not compatible with Rack::Utils.parse_nested_query
118
+ def self.dehash(hash, depth)
119
+ hash.each do |key, value|
120
+ hash[key] = dehash(value, depth + 1) if value.kind_of?(Hash)
121
+ end
122
+
123
+ if depth > 0 && !hash.empty? && hash.keys.all? { |k| k =~ /^\d+$/ }
124
+ hash.keys.sort.inject([]) { |all, key| all << hash[key] }
125
+ else
126
+ hash
110
127
  end
111
128
  end
112
129
  end
@@ -84,6 +84,7 @@ module Faraday
84
84
  use_symbol(Faraday::Middleware, klass, *args, &block)
85
85
  else
86
86
  raise_if_locked
87
+ warn_middleware_after_adapter if adapter_set?
87
88
  @handlers << self.class::Handler.new(klass, *args, &block)
88
89
  end
89
90
  end
@@ -105,6 +106,7 @@ module Faraday
105
106
  def insert(index, *args, &block)
106
107
  raise_if_locked
107
108
  index = assert_index(index)
109
+ warn_middleware_after_adapter if inserting_after_adapter?(index)
108
110
  handler = self.class::Handler.new(*args, &block)
109
111
  @handlers.insert(index, handler)
110
112
  end
@@ -136,6 +138,8 @@ module Faraday
136
138
  #
137
139
  # Returns a Faraday::Response.
138
140
  def build_response(connection, request)
141
+ warn 'WARNING: No adapter was configured for this request' unless adapter_set?
142
+
139
143
  app.call(build_env(connection, request))
140
144
  end
141
145
 
@@ -151,8 +155,9 @@ module Faraday
151
155
  lock!
152
156
  to_app(lambda { |env|
153
157
  response = Response.new
154
- response.finish(env) unless env.parallel?
155
158
  env.response = response
159
+ response.finish(env) unless env.parallel?
160
+ response
156
161
  })
157
162
  end
158
163
  end
@@ -188,7 +193,7 @@ module Faraday
188
193
  # :ssl - Hash of options for configuring SSL requests.
189
194
  def build_env(connection, request)
190
195
  Env.new(request.method, request.body,
191
- connection.build_exclusive_url(request.path, request.params),
196
+ connection.build_exclusive_url(request.path, request.params, request.options.params_encoder),
192
197
  request.options, request.headers, connection.ssl,
193
198
  connection.parallel_manager)
194
199
  end
@@ -199,6 +204,26 @@ module Faraday
199
204
  raise StackLocked, "can't modify middleware stack after making a request" if locked?
200
205
  end
201
206
 
207
+ def warn_middleware_after_adapter
208
+ warn "WARNING: Unexpected middleware set after the adapter. " \
209
+ "This won't be supported from Faraday 1.0."
210
+ end
211
+
212
+ def adapter_set?
213
+ @handlers.any? { |handler| is_adapter?(handler) }
214
+ end
215
+
216
+ def inserting_after_adapter?(index)
217
+ adapter_index = @handlers.find_index { |handler| is_adapter?(handler) }
218
+ return false if adapter_index.nil?
219
+
220
+ index > adapter_index
221
+ end
222
+
223
+ def is_adapter?(handler)
224
+ handler.klass.ancestors.include? Faraday::Adapter
225
+ end
226
+
202
227
  def use_symbol(mod, key, *args, &block)
203
228
  use(mod.lookup_middleware(key), *args, &block)
204
229
  end
@@ -16,8 +16,7 @@ module Faraday
16
16
 
17
17
  # Internal
18
18
  def self.build_hash(type, hash)
19
- offset = KEY.size + type.size + 3
20
- comma = ",\n#{' ' * offset}"
19
+ comma = ", "
21
20
  values = []
22
21
  hash.each do |key, value|
23
22
  values << "#{key}=#{value.to_s.inspect}"
@@ -1,13 +1,14 @@
1
1
  require File.expand_path("../url_encoded", __FILE__)
2
+ require 'securerandom'
2
3
 
3
4
  module Faraday
4
5
  class Request::Multipart < Request::UrlEncoded
5
6
  self.mime_type = 'multipart/form-data'.freeze
6
- DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze unless defined? DEFAULT_BOUNDARY
7
+ DEFAULT_BOUNDARY_PREFIX = "-----------RubyMultipartPost".freeze unless defined? DEFAULT_BOUNDARY_PREFIX
7
8
 
8
9
  def call(env)
9
10
  match_content_type(env) do |params|
10
- env.request.boundary ||= DEFAULT_BOUNDARY
11
+ env.request.boundary ||= unique_boundary
11
12
  env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}"
12
13
  env.body = create_multipart(env, params)
13
14
  end
@@ -44,6 +45,10 @@ module Faraday
44
45
  return body
45
46
  end
46
47
 
48
+ def unique_boundary
49
+ "#{DEFAULT_BOUNDARY_PREFIX}-#{SecureRandom.hex}"
50
+ end
51
+
47
52
  def process_params(params, prefix = nil, pieces = nil, &block)
48
53
  params.inject(pieces || []) do |all, (key, value)|
49
54
  key = "#{prefix}[#{key}]" if prefix
@@ -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
  #
@@ -10,7 +12,7 @@ module Faraday
10
12
  #
11
13
  # Faraday.new do |conn|
12
14
  # conn.request :retry, max: 2, interval: 0.05,
13
- # interval_randomness: 0.5, backoff_factor: 2
15
+ # interval_randomness: 0.5, backoff_factor: 2,
14
16
  # exceptions: [CustomException, 'Timeout::Error']
15
17
  # conn.adapter ...
16
18
  # end
@@ -20,14 +22,17 @@ module Faraday
20
22
  #
21
23
  class Request::Retry < Faraday::Middleware
22
24
 
25
+ DEFAULT_EXCEPTIONS = [Errno::ETIMEDOUT, 'Timeout::Error', Error::TimeoutError, Faraday::Error::RetriableResponse].freeze
23
26
  IDEMPOTENT_METHODS = [:delete, :get, :head, :options, :put]
24
27
 
25
- class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor,
26
- :exceptions, :methods, :retry_if)
28
+ class Options < Faraday::Options.new(:max, :interval, :max_interval, :interval_randomness,
29
+ :backoff_factor, :exceptions, :methods, :retry_if, :retry_block,
30
+ :retry_statuses)
31
+
27
32
  DEFAULT_CHECK = lambda { |env,exception| false }
28
33
 
29
34
  def self.from(value)
30
- if Fixnum === value
35
+ if Integer === value
31
36
  new(value)
32
37
  else
33
38
  super(value)
@@ -42,8 +47,12 @@ module Faraday
42
47
  (self[:interval] ||= 0).to_f
43
48
  end
44
49
 
50
+ def max_interval
51
+ (self[:max_interval] ||= Float::MAX).to_f
52
+ end
53
+
45
54
  def interval_randomness
46
- (self[:interval_randomness] ||= 0).to_i
55
+ (self[:interval_randomness] ||= 0).to_f
47
56
  end
48
57
 
49
58
  def backoff_factor
@@ -51,8 +60,7 @@ module Faraday
51
60
  end
52
61
 
53
62
  def exceptions
54
- Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
55
- Error::TimeoutError])
63
+ Array(self[:exceptions] ||= DEFAULT_EXCEPTIONS)
56
64
  end
57
65
 
58
66
  def methods
@@ -63,6 +71,13 @@ module Faraday
63
71
  self[:retry_if] ||= DEFAULT_CHECK
64
72
  end
65
73
 
74
+ def retry_block
75
+ self[:retry_block] ||= Proc.new {}
76
+ end
77
+
78
+ def retry_statuses
79
+ Array(self[:retry_statuses] ||= [])
80
+ end
66
81
  end
67
82
 
68
83
  # Public: Initialize middleware
@@ -73,13 +88,14 @@ module Faraday
73
88
  # interval_randomness - The maximum random interval amount expressed
74
89
  # as a float between 0 and 1 to use in addition to the
75
90
  # interval. (default: 0)
91
+ # max_interval - An upper limit for the interval (default: Float::MAX)
76
92
  # backoff_factor - The amount to multiple each successive retry's
77
93
  # interval amount by in order to provide backoff
78
94
  # (default: 1)
79
95
  # exceptions - The list of exceptions to handle. Exceptions can be
80
96
  # given as Class, Module, or String. (default:
81
- # [Errno::ETIMEDOUT, Timeout::Error,
82
- # Error::TimeoutError])
97
+ # [Errno::ETIMEDOUT, 'Timeout::Error',
98
+ # Error::TimeoutError, Faraday::Error::RetriableResponse])
83
99
  # methods - A list of HTTP methods to retry without calling retry_if. Pass
84
100
  # an empty Array to call retry_if for all exceptions.
85
101
  # (defaults to the idempotent HTTP methods in IDEMPOTENT_METHODS)
@@ -89,17 +105,21 @@ module Faraday
89
105
  # if the exception produced is non-recoverable or if the
90
106
  # the HTTP method called is not idempotent.
91
107
  # (defaults to return false)
108
+ # retry_block - block that is executed after every retry. Request environment, middleware options,
109
+ # current number of retries and the exception is passed to the block as parameters.
92
110
  def initialize(app, options = nil)
93
111
  super(app)
94
112
  @options = Options.from(options)
95
113
  @errmatch = build_exception_matcher(@options.exceptions)
96
114
  end
97
115
 
98
- def sleep_amount(retries)
99
- retry_index = @options.max - retries
100
- current_interval = @options.interval * (@options.backoff_factor ** retry_index)
101
- random_interval = rand * @options.interval_randomness.to_f * @options.interval
102
- current_interval + random_interval
116
+ def calculate_sleep_amount(retries, env)
117
+ retry_after = calculate_retry_after(env)
118
+ retry_interval = calculate_retry_interval(retries)
119
+
120
+ return if retry_after && retry_after > @options.max_interval
121
+
122
+ retry_after && retry_after >= retry_interval ? retry_after : retry_interval
103
123
  end
104
124
 
105
125
  def call(env)
@@ -107,14 +127,25 @@ module Faraday
107
127
  request_body = env[:body]
108
128
  begin
109
129
  env[:body] = request_body # after failure env[:body] is set to the response body
110
- @app.call(env)
130
+ @app.call(env).tap do |resp|
131
+ raise Faraday::Error::RetriableResponse.new(nil, resp) if @options.retry_statuses.include?(resp.status)
132
+ end
111
133
  rescue @errmatch => exception
112
134
  if retries > 0 && retry_request?(env, exception)
113
135
  retries -= 1
114
- sleep sleep_amount(retries + 1)
115
- retry
136
+ rewind_files(request_body)
137
+ @options.retry_block.call(env, @options, retries, exception)
138
+ if (sleep_amount = calculate_sleep_amount(retries + 1, env))
139
+ sleep sleep_amount
140
+ retry
141
+ end
142
+ end
143
+
144
+ if exception.is_a?(Faraday::Error::RetriableResponse)
145
+ exception.response
146
+ else
147
+ raise
116
148
  end
117
- raise
118
149
  end
119
150
  end
120
151
 
@@ -144,5 +175,38 @@ module Faraday
144
175
  @options.methods.include?(env[:method]) || @options.retry_if.call(env, exception)
145
176
  end
146
177
 
178
+ def rewind_files(body)
179
+ return unless body.is_a?(Hash)
180
+ body.each do |_, value|
181
+ if value.is_a? UploadIO
182
+ value.rewind
183
+ end
184
+ end
185
+ end
186
+
187
+ # MDN spec for Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
188
+ def calculate_retry_after(env)
189
+ response_headers = env[:response_headers]
190
+ return unless response_headers
191
+
192
+ retry_after_value = env[:response_headers]["Retry-After"]
193
+
194
+ # Try to parse date from the header value
195
+ begin
196
+ datetime = DateTime.rfc2822(retry_after_value)
197
+ datetime.to_time - Time.now.utc
198
+ rescue ArgumentError
199
+ retry_after_value.to_f
200
+ end
201
+ end
202
+
203
+ def calculate_retry_interval(retries)
204
+ retry_index = @options.max - retries
205
+ current_interval = @options.interval * (@options.backoff_factor ** retry_index)
206
+ current_interval = [current_interval, @options.max_interval].min
207
+ random_interval = rand * @options.interval_randomness.to_f * @options.interval
208
+
209
+ current_interval + random_interval
210
+ end
147
211
  end
148
212
  end
@@ -52,6 +52,8 @@ module Faraday
52
52
  path.query = nil
53
53
  end
54
54
  else
55
+ anchor_index = path.index('#')
56
+ path = path.slice(0, anchor_index) unless anchor_index.nil?
55
57
  path, query = path.split('?', 2)
56
58
  end
57
59
  self.path = path
@@ -67,6 +69,26 @@ module Faraday
67
69
  headers[key] = value
68
70
  end
69
71
 
72
+ def marshal_dump
73
+ {
74
+ :method => method,
75
+ :body => body,
76
+ :headers => headers,
77
+ :path => path,
78
+ :params => params,
79
+ :options => options
80
+ }
81
+ end
82
+
83
+ def marshal_load(serialised)
84
+ self.method = serialised[:method]
85
+ self.body = serialised[:body]
86
+ self.headers = serialised[:headers]
87
+ self.path = serialised[:path]
88
+ self.params = serialised[:params]
89
+ self.options = serialised[:options]
90
+ end
91
+
70
92
  # ENV Keys
71
93
  # :method - a symbolized request method (:get, :post)
72
94
  # :body - the request body that will eventually be converted to a string.
@@ -4,30 +4,36 @@ module Faraday
4
4
  class Response::Logger < Response::Middleware
5
5
  extend Forwardable
6
6
 
7
- DEFAULT_OPTIONS = { :bodies => false }
7
+ DEFAULT_OPTIONS = { :headers => true, :bodies => false }
8
8
 
9
9
  def initialize(app, logger = nil, options = {})
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
+ @filter = []
15
16
  @options = DEFAULT_OPTIONS.merge(options)
17
+ yield self if block_given?
16
18
  end
17
19
 
18
20
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
19
21
 
20
22
  def call(env)
21
- info "#{env.method} #{env.url.to_s}"
22
- debug('request') { dump_headers env.request_headers }
23
- debug('request') { dump_body(env[:body]) } if env[:body] && log_body?(:request)
23
+ info('request') { "#{env.method.upcase} #{apply_filters(env.url.to_s)}" }
24
+ debug('request') { apply_filters( dump_headers env.request_headers ) } if log_headers?(:request)
25
+ debug('request') { apply_filters( dump_body(env[:body]) ) } if env[:body] && log_body?(:request)
24
26
  super
25
27
  end
26
28
 
27
29
  def on_complete(env)
28
- info('Status') { env.status.to_s }
29
- debug('response') { dump_headers env.response_headers }
30
- debug('response') { dump_body env[:body] } if env[:body] && log_body?(:response)
30
+ info('response') { "Status #{env.status.to_s}" }
31
+ debug('response') { apply_filters( dump_headers env.response_headers ) } if log_headers?(:response)
32
+ debug('response') { apply_filters( dump_body env[:body] ) } if env[:body] && log_body?(:response)
33
+ end
34
+
35
+ def filter(filter_word, filter_replacement)
36
+ @filter.push([ filter_word, filter_replacement ])
31
37
  end
32
38
 
33
39
  private
@@ -49,11 +55,26 @@ module Faraday
49
55
  body.pretty_inspect
50
56
  end
51
57
 
58
+ def log_headers?(type)
59
+ case @options[:headers]
60
+ when Hash then @options[:headers][type]
61
+ else @options[:headers]
62
+ end
63
+ end
64
+
52
65
  def log_body?(type)
53
66
  case @options[:bodies]
54
67
  when Hash then @options[:bodies][type]
55
68
  else @options[:bodies]
56
69
  end
57
70
  end
71
+
72
+ def apply_filters(output)
73
+ @filter.each do |pattern, replacement|
74
+ output = output.to_s.gsub(pattern, replacement)
75
+ end
76
+ output
77
+ end
78
+
58
79
  end
59
80
  end