faraday 0.9.0.rc5 → 0.9.0.rc6

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