faraday 0.9.1 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
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