faraday 0.9.0.rc5 → 0.9.0.rc6

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 (45) hide show
  1. data/Gemfile +6 -5
  2. data/README.md +0 -22
  3. data/faraday.gemspec +2 -2
  4. data/lib/faraday.rb +6 -2
  5. data/lib/faraday/adapter/em_http.rb +34 -14
  6. data/lib/faraday/adapter/em_http_ssl_patch.rb +56 -0
  7. data/lib/faraday/adapter/em_synchrony.rb +21 -20
  8. data/lib/faraday/adapter/excon.rb +16 -0
  9. data/lib/faraday/adapter/httpclient.rb +14 -0
  10. data/lib/faraday/adapter/net_http.rb +6 -2
  11. data/lib/faraday/adapter/net_http_persistent.rb +15 -3
  12. data/lib/faraday/adapter/patron.rb +13 -11
  13. data/lib/faraday/adapter/typhoeus.rb +11 -0
  14. data/lib/faraday/autoload.rb +2 -4
  15. data/lib/faraday/connection.rb +30 -3
  16. data/lib/faraday/error.rb +4 -1
  17. data/lib/faraday/options.rb +117 -23
  18. data/lib/faraday/request/instrumentation.rb +1 -3
  19. data/lib/faraday/request/multipart.rb +1 -1
  20. data/lib/faraday/request/retry.rb +38 -9
  21. data/lib/faraday/response.rb +1 -2
  22. data/lib/faraday/response/raise_error.rb +3 -0
  23. data/lib/faraday/utils.rb +10 -4
  24. data/script/proxy-server +42 -0
  25. data/script/server +1 -3
  26. data/script/test +35 -7
  27. data/test/adapters/excon_test.rb +4 -0
  28. data/test/adapters/httpclient_test.rb +5 -0
  29. data/test/adapters/integration.rb +48 -2
  30. data/test/adapters/net_http_persistent_test.rb +10 -1
  31. data/test/adapters/patron_test.rb +3 -0
  32. data/test/adapters/rack_test.rb +5 -0
  33. data/test/adapters/typhoeus_test.rb +3 -13
  34. data/test/authentication_middleware_test.rb +6 -6
  35. data/test/connection_test.rb +123 -49
  36. data/test/env_test.rb +19 -1
  37. data/test/helper.rb +2 -4
  38. data/test/live_server.rb +4 -2
  39. data/test/middleware/instrumentation_test.rb +13 -0
  40. data/test/middleware/retry_test.rb +47 -0
  41. data/test/multibyte.txt +1 -0
  42. data/test/options_test.rb +93 -17
  43. data/test/request_middleware_test.rb +6 -6
  44. data/test/utils_test.rb +34 -13
  45. metadata +69 -44
@@ -56,6 +56,17 @@ module Faraday
56
56
  end
57
57
  end
58
58
 
59
+ case resp.curl_return_code
60
+ when 0
61
+ # everything OK
62
+ when 7
63
+ raise Error::ConnectionFailed, resp.curl_error_message
64
+ when 60
65
+ raise Faraday::SSLError, resp.curl_error_message
66
+ else
67
+ raise Error::ClientError, resp.curl_error_message
68
+ end
69
+
59
70
  save_response(env, resp.code, resp.body) do |response_headers|
60
71
  response_headers.parse resp.headers
61
72
  end
@@ -1,5 +1,3 @@
1
- require 'faraday'
2
-
3
1
  module Faraday
4
2
  # Internal: Adds the ability for other modules to manage autoloadable
5
3
  # constants.
@@ -74,7 +72,8 @@ module Faraday
74
72
  :Timeout => 'timeout',
75
73
  :Authorization => 'authorization',
76
74
  :BasicAuthentication => 'basic_authentication',
77
- :TokenAuthentication => 'token_authentication'
75
+ :TokenAuthentication => 'token_authentication',
76
+ :Instrumentation => 'instrumentation'
78
77
  end
79
78
 
80
79
  class Response
@@ -84,4 +83,3 @@ module Faraday
84
83
  :Logger => 'logger'
85
84
  end
86
85
  end
87
-
@@ -79,7 +79,14 @@ module Faraday
79
79
  @params.update(options.params) if options.params
80
80
  @headers.update(options.headers) if options.headers
81
81
 
82
- proxy(options.fetch(:proxy) { ENV['http_proxy'] })
82
+ @proxy = nil
83
+ proxy(options.fetch(:proxy) {
84
+ uri = ENV['http_proxy']
85
+ if uri && !uri.empty?
86
+ uri = 'http://' + uri if uri !~ /^http/i
87
+ uri
88
+ end
89
+ })
83
90
 
84
91
  yield self if block_given?
85
92
 
@@ -312,7 +319,7 @@ module Faraday
312
319
  end
313
320
 
314
321
  # Public: Sets the path prefix and ensures that it always has a leading
315
- # but no trailing slash.
322
+ # slash.
316
323
  #
317
324
  # value - A String.
318
325
  #
@@ -324,6 +331,27 @@ module Faraday
324
331
  end
325
332
  end
326
333
 
334
+ # Public: Takes a relative url for a request and combines it with the defaults
335
+ # set on the connection instance.
336
+ #
337
+ # conn = Faraday::Connection.new { ... }
338
+ # conn.url_prefix = "https://sushi.com/api?token=abc"
339
+ # conn.scheme # => https
340
+ # conn.path_prefix # => "/api"
341
+ #
342
+ # conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
343
+ # conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
344
+ #
345
+ def build_url(url = nil, extra_params = nil)
346
+ uri = build_exclusive_url(url)
347
+
348
+ query_values = params.dup.merge_query(uri.query, options.params_encoder)
349
+ query_values.update extra_params if extra_params
350
+ uri.query = query_values.empty? ? nil : query_values.to_query(options.params_encoder)
351
+
352
+ uri
353
+ end
354
+
327
355
  # Builds and runs the Faraday::Request.
328
356
  #
329
357
  # method - The Symbol HTTP method.
@@ -400,4 +428,3 @@ module Faraday
400
428
  end
401
429
  end
402
430
  end
403
-
@@ -38,8 +38,11 @@ module Faraday
38
38
  class ParsingError < ClientError; end
39
39
  class TimeoutError < ClientError; end
40
40
 
41
+ class SSLError < ClientError
42
+ end
43
+
41
44
  [:MissingDependency, :ClientError, :ConnectionFailed, :ResourceNotFound,
42
- :ParsingError, :TimeoutError].each do |const|
45
+ :ParsingError, :TimeoutError, :SSLError].each do |const|
43
46
  Error.const_set(const, Faraday.const_get(const))
44
47
  end
45
48
  end
@@ -18,7 +18,7 @@ module Faraday
18
18
  def update(obj)
19
19
  obj.each do |key, value|
20
20
  if sub_options = self.class.options_for(key)
21
- value = sub_options.from(value)
21
+ value = sub_options.from(value) if value
22
22
  elsif Hash === value
23
23
  hash = {}
24
24
  value.each do |hash_key, hash_value|
@@ -27,7 +27,7 @@ module Faraday
27
27
  value = hash
28
28
  end
29
29
 
30
- self.send("#{key}=", value)
30
+ self.send("#{key}=", value) unless value.nil?
31
31
  end
32
32
  self
33
33
  end
@@ -47,9 +47,20 @@ module Faraday
47
47
  end
48
48
 
49
49
  # Public
50
- def fetch(key, default = nil)
51
- send(key) || send("#{key}=", default ||
52
- (block_given? ? Proc.new.call : nil))
50
+ def fetch(key, *args)
51
+ return send(key) if symbolized_key_set.include?(key.to_sym)
52
+
53
+ key_setter = "#{key}="
54
+
55
+ if args.size > 0
56
+ return send(key_setter, args.first)
57
+ end
58
+
59
+ if block_given?
60
+ return send(key_setter, Proc.new.call(key))
61
+ end
62
+
63
+ raise self.class.fetch_error_class, "key not found: #{key.inspect}"
53
64
  end
54
65
 
55
66
  # Public
@@ -98,6 +109,44 @@ module Faraday
98
109
  def self.attribute_options
99
110
  @attribute_options ||= {}
100
111
  end
112
+
113
+ def self.memoized(key)
114
+ memoized_attributes[key.to_sym] = Proc.new
115
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
116
+ def #{key}() self[:#{key}]; end
117
+ RUBY
118
+ end
119
+
120
+ def self.memoized_attributes
121
+ @memoized_attributes ||= {}
122
+ end
123
+
124
+ def [](key)
125
+ key = key.to_sym
126
+ if method = self.class.memoized_attributes[key]
127
+ super(key) || (self[key] = instance_eval(&method))
128
+ else
129
+ super
130
+ end
131
+ end
132
+
133
+ def symbolized_key_set
134
+ @symbolized_key_set ||= Set.new(keys.map { |k| k.to_sym })
135
+ end
136
+
137
+ def self.inherited(subclass)
138
+ super
139
+ subclass.attribute_options.update(attribute_options)
140
+ subclass.memoized_attributes.update(memoized_attributes)
141
+ end
142
+
143
+ def self.fetch_error_class
144
+ @fetch_error_class ||= if Object.const_defined?(:KeyError)
145
+ ::KeyError
146
+ else
147
+ ::IndexError
148
+ end
149
+ end
101
150
  end
102
151
 
103
152
  class RequestOptions < Options.new(:params_encoder, :proxy, :bind,
@@ -114,7 +163,7 @@ module Faraday
114
163
  end
115
164
 
116
165
  class SSLOptions < Options.new(:verify, :ca_file, :ca_path, :verify_mode,
117
- :cert_store, :client_cert, :client_key, :verify_depth, :version)
166
+ :cert_store, :client_cert, :client_key, :certificate, :private_key, :verify_depth, :version)
118
167
 
119
168
  def verify?
120
169
  verify != false
@@ -141,13 +190,8 @@ module Faraday
141
190
  super(value)
142
191
  end
143
192
 
144
- def user
145
- self[:user] ||= Utils.unescape(uri.user)
146
- end
147
-
148
- def password
149
- self[:password] ||= Utils.unescape(uri.password)
150
- end
193
+ memoized(:user) { uri.user && Utils.unescape(uri.user) }
194
+ memoized(:password) { uri.password && Utils.unescape(uri.password) }
151
195
  end
152
196
 
153
197
  class ConnectionOptions < Options.new(:request, :proxy, :ssl, :builder, :url,
@@ -155,17 +199,11 @@ module Faraday
155
199
 
156
200
  options :request => RequestOptions, :ssl => SSLOptions
157
201
 
158
- def request
159
- self[:request] ||= self.class.options_for(:request).new
160
- end
202
+ memoized(:request) { self.class.options_for(:request).new }
161
203
 
162
- def ssl
163
- self[:ssl] ||= self.class.options_for(:ssl).new
164
- end
204
+ memoized(:ssl) { self.class.options_for(:ssl).new }
165
205
 
166
- def builder_class
167
- self[:builder_class] ||= RackBuilder
168
- end
206
+ memoized(:builder_class) { RackBuilder }
169
207
 
170
208
  def new_builder(block)
171
209
  builder_class.new(&block)
@@ -190,26 +228,82 @@ module Faraday
190
228
 
191
229
  def_delegators :request, :params_encoder
192
230
 
231
+ # Public
232
+ def [](key)
233
+ if in_member_set?(key)
234
+ super(key)
235
+ else
236
+ custom_members[key]
237
+ end
238
+ end
239
+
240
+ # Public
241
+ def []=(key, value)
242
+ if in_member_set?(key)
243
+ super(key, value)
244
+ else
245
+ custom_members[key] = value
246
+ end
247
+ end
248
+
249
+ # Public
193
250
  def success?
194
251
  SuccessfulStatuses.include?(status)
195
252
  end
196
253
 
254
+ # Public
197
255
  def needs_body?
198
256
  !body && MethodsWithBodies.include?(method)
199
257
  end
200
258
 
259
+ # Public
201
260
  def clear_body
202
261
  request_headers[ContentLength] = '0'
203
262
  self.body = ''
204
263
  end
205
264
 
265
+ # Public
206
266
  def parse_body?
207
267
  !StatusesWithoutBody.include?(status)
208
268
  end
209
269
 
270
+ # Public
210
271
  def parallel?
211
272
  !!parallel_manager
212
273
  end
274
+
275
+ def inspect
276
+ attrs = [nil]
277
+ members.each do |mem|
278
+ if value = send(mem)
279
+ attrs << "@#{mem}=#{value.inspect}"
280
+ end
281
+ end
282
+ if !custom_members.empty?
283
+ attrs << "@custom=#{custom_members.inspect}"
284
+ end
285
+ %(#<#{self.class}#{attrs.join(" ")}>)
286
+ end
287
+
288
+ # Internal
289
+ def custom_members
290
+ @custom_members ||= {}
291
+ end
292
+
293
+ # Internal
294
+ if members.first.is_a?(Symbol)
295
+ def in_member_set?(key)
296
+ self.class.member_set.include?(key.to_sym)
297
+ end
298
+ else
299
+ def in_member_set?(key)
300
+ self.class.member_set.include?(key.to_s)
301
+ end
302
+ end
303
+
304
+ # Internal
305
+ def self.member_set
306
+ @member_set ||= Set.new(members)
307
+ end
213
308
  end
214
309
  end
215
-
@@ -24,7 +24,7 @@ module Faraday
24
24
  # end
25
25
  def initialize(app, options = nil)
26
26
  super(app)
27
- @name, @instrumenter = Options.from(options).values
27
+ @name, @instrumenter = Options.from(options).values_at(:name, :instrumenter)
28
28
  end
29
29
 
30
30
  def call(env)
@@ -34,5 +34,3 @@ module Faraday
34
34
  end
35
35
  end
36
36
  end
37
-
38
-
@@ -8,7 +8,7 @@ module Faraday
8
8
  def call(env)
9
9
  match_content_type(env) do |params|
10
10
  env.request.boundary ||= DEFAULT_BOUNDARY
11
- env.request_headers[CONTENT_TYPE] += ";boundary=#{env.request.boundary}"
11
+ env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}"
12
12
  env.body = create_multipart(env, params)
13
13
  end
14
14
  @app.call env
@@ -3,17 +3,23 @@ module Faraday
3
3
  #
4
4
  # By default, it retries 2 times and handles only timeout exceptions. It can
5
5
  # be configured with an arbitrary number of retries, a list of exceptions to
6
- # handle an a retry interval.
6
+ # handle, a retry interval, a percentage of randomness to add to the retry
7
+ # interval, and a backoff factor.
7
8
  #
8
9
  # Examples
9
10
  #
10
11
  # Faraday.new do |conn|
11
12
  # conn.request :retry, max: 2, interval: 0.05,
13
+ # interval_randomness: 0.5, backoff_factor: 2
12
14
  # exceptions: [CustomException, 'Timeout::Error']
13
15
  # conn.adapter ...
14
16
  # end
17
+ #
18
+ # This example will result in a first interval that is random between 0.05 and 0.075 and a second
19
+ # interval that is random between 0.1 and 0.15
20
+ #
15
21
  class Request::Retry < Faraday::Middleware
16
- class Options < Faraday::Options.new(:max, :interval, :exceptions)
22
+ class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor, :exceptions)
17
23
  def self.from(value)
18
24
  if Fixnum === value
19
25
  new(value)
@@ -30,6 +36,14 @@ module Faraday
30
36
  (self[:interval] ||= 0).to_f
31
37
  end
32
38
 
39
+ def interval_randomness
40
+ (self[:interval_randomness] ||= 0).to_i
41
+ end
42
+
43
+ def backoff_factor
44
+ (self[:backoff_factor] ||= 1).to_f
45
+ end
46
+
33
47
  def exceptions
34
48
  Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
35
49
  Error::TimeoutError])
@@ -40,25 +54,41 @@ module Faraday
40
54
  # Public: Initialize middleware
41
55
  #
42
56
  # Options:
43
- # max - Maximum number of retries (default: 2).
44
- # interval - Pause in seconds between retries (default: 0).
45
- # exceptions - The list of exceptions to handle. Exceptions can be
46
- # given as Class, Module, or String. (default:
47
- # [Errno::ETIMEDOUT, Timeout::Error, Error::TimeoutError])
57
+ # max - Maximum number of retries (default: 2)
58
+ # interval - Pause in seconds between retries (default: 0)
59
+ # interval_randomness - The maximum random interval amount expressed
60
+ # as a float between 0 and 1 to use in addition to the
61
+ # interval. (default: 0)
62
+ # backoff_factor - The amount to multiple each successive retry's
63
+ # interval amount by in order to provide backoff
64
+ # (default: 1)
65
+ # exceptions - The list of exceptions to handle. Exceptions can be
66
+ # given as Class, Module, or String. (default:
67
+ # [Errno::ETIMEDOUT, Timeout::Error,
68
+ # Error::TimeoutError])
48
69
  def initialize(app, options = nil)
49
70
  super(app)
50
71
  @options = Options.from(options)
51
72
  @errmatch = build_exception_matcher(@options.exceptions)
52
73
  end
53
74
 
75
+ def sleep_amount(retries)
76
+ retry_index = @options.max - retries
77
+ current_interval = @options.interval * (@options.backoff_factor ** retry_index)
78
+ random_interval = rand * @options.interval_randomness.to_f * @options.interval
79
+ current_interval + random_interval
80
+ end
81
+
54
82
  def call(env)
55
83
  retries = @options.max
84
+ request_body = env[:body]
56
85
  begin
86
+ env[:body] = request_body # after failure env[:body] is set to the response body
57
87
  @app.call(env)
58
88
  rescue @errmatch
59
89
  if retries > 0
60
90
  retries -= 1
61
- sleep @options.interval if @options.interval > 0
91
+ sleep sleep_amount(retries + 1)
62
92
  retry
63
93
  end
64
94
  raise
@@ -84,4 +114,3 @@ module Faraday
84
114
  end
85
115
  end
86
116
  end
87
-
@@ -86,9 +86,8 @@ module Faraday
86
86
  # Useful for applying request params after restoring a marshalled Response.
87
87
  def apply_request(request_env)
88
88
  raise "response didn't finish yet" unless finished?
89
- @env = Env.from(request_env).merge(@env)
89
+ @env = Env.from(request_env).update(@env)
90
90
  return self
91
91
  end
92
92
  end
93
93
  end
94
-