faraday 0.8.11 → 0.9.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/.document +6 -0
  3. data/CHANGELOG.md +15 -0
  4. data/CONTRIBUTING.md +36 -0
  5. data/Gemfile +10 -3
  6. data/LICENSE.md +1 -1
  7. data/README.md +17 -51
  8. data/Rakefile +2 -18
  9. data/faraday.gemspec +34 -0
  10. data/lib/faraday/adapter/em_http.rb +34 -0
  11. data/lib/faraday/adapter/em_http_ssl_patch.rb +56 -0
  12. data/lib/faraday/adapter/em_synchrony.rb +11 -24
  13. data/lib/faraday/adapter/excon.rb +12 -2
  14. data/lib/faraday/adapter/httpclient.rb +106 -0
  15. data/lib/faraday/adapter/net_http.rb +10 -6
  16. data/lib/faraday/adapter/patron.rb +2 -8
  17. data/lib/faraday/adapter/rack.rb +0 -2
  18. data/lib/faraday/adapter/test.rb +39 -39
  19. data/lib/faraday/adapter/typhoeus.rb +12 -3
  20. data/lib/faraday/adapter.rb +20 -35
  21. data/lib/faraday/autoload.rb +85 -0
  22. data/lib/faraday/connection.rb +232 -125
  23. data/lib/faraday/error.rb +42 -34
  24. data/lib/faraday/options.rb +350 -0
  25. data/lib/faraday/parameters.rb +193 -0
  26. data/lib/faraday/{builder.rb → rack_builder.rb} +79 -22
  27. data/lib/faraday/request/authorization.rb +7 -5
  28. data/lib/faraday/request/basic_authentication.rb +1 -1
  29. data/lib/faraday/request/instrumentation.rb +36 -0
  30. data/lib/faraday/request/multipart.rb +10 -9
  31. data/lib/faraday/request/retry.rb +99 -4
  32. data/lib/faraday/request/token_authentication.rb +2 -2
  33. data/lib/faraday/request/url_encoded.rb +7 -6
  34. data/lib/faraday/request.rb +21 -30
  35. data/lib/faraday/response/logger.rb +4 -4
  36. data/lib/faraday/response/raise_error.rb +4 -2
  37. data/lib/faraday/response.rb +17 -23
  38. data/lib/faraday/utils.rb +81 -71
  39. data/lib/faraday.rb +187 -68
  40. data/script/console +7 -0
  41. data/script/proxy-server +1 -0
  42. data/script/release +6 -3
  43. data/script/test +4 -2
  44. data/test/adapters/em_http_test.rb +6 -1
  45. data/test/adapters/em_synchrony_test.rb +7 -1
  46. data/test/adapters/httpclient_test.rb +21 -0
  47. data/test/adapters/integration.rb +23 -8
  48. data/test/adapters/logger_test.rb +1 -1
  49. data/test/adapters/net_http_persistent_test.rb +10 -1
  50. data/test/adapters/net_http_test.rb +0 -31
  51. data/test/adapters/patron_test.rb +4 -1
  52. data/test/adapters/test_middleware_test.rb +48 -4
  53. data/test/adapters/typhoeus_test.rb +8 -1
  54. data/test/authentication_middleware_test.rb +2 -2
  55. data/test/connection_test.rb +160 -84
  56. data/test/env_test.rb +51 -24
  57. data/test/helper.rb +13 -13
  58. data/test/live_server.rb +8 -0
  59. data/test/middleware/instrumentation_test.rb +88 -0
  60. data/test/middleware/retry_test.rb +88 -35
  61. data/test/middleware_stack_test.rb +13 -12
  62. data/test/options_test.rb +252 -0
  63. data/test/request_middleware_test.rb +11 -1
  64. data/test/response_middleware_test.rb +2 -4
  65. data/test/strawberry.rb +2 -0
  66. data/test/utils_test.rb +34 -6
  67. metadata +71 -11
  68. data/test/parameters_test.rb +0 -24
@@ -0,0 +1,36 @@
1
+ module Faraday
2
+ class Request::Instrumentation < Faraday::Middleware
3
+ class Options < Faraday::Options.new(:name, :instrumenter)
4
+ def name
5
+ self[:name] ||= 'request.faraday'
6
+ end
7
+
8
+ def instrumenter
9
+ self[:instrumenter] ||= ActiveSupport::Notifications
10
+ end
11
+ end
12
+
13
+ # Public: Instruments requests using Active Support.
14
+ #
15
+ # Measures time spent only for synchronous requests.
16
+ #
17
+ # Examples
18
+ #
19
+ # ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env|
20
+ # url = env[:url]
21
+ # http_method = env[:method].to_s.upcase
22
+ # duration = ends - starts
23
+ # $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
24
+ # end
25
+ def initialize(app, options = nil)
26
+ super(app)
27
+ @name, @instrumenter = Options.from(options).values_at(:name, :instrumenter)
28
+ end
29
+
30
+ def call(env)
31
+ @instrumenter.instrument(@name, env) do
32
+ @app.call(env)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,22 +1,23 @@
1
+ require File.expand_path("../url_encoded", __FILE__)
2
+
1
3
  module Faraday
2
4
  class Request::Multipart < Request::UrlEncoded
3
5
  self.mime_type = 'multipart/form-data'.freeze
4
- DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze
6
+ DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze unless defined? DEFAULT_BOUNDARY
5
7
 
6
8
  def call(env)
7
9
  match_content_type(env) do |params|
8
- env[:request] ||= {}
9
- env[:request][:boundary] ||= DEFAULT_BOUNDARY
10
- env[:request_headers][CONTENT_TYPE] += "; boundary=#{env[:request][:boundary]}"
11
- env[:body] = create_multipart(env, params)
10
+ env.request.boundary ||= DEFAULT_BOUNDARY
11
+ env.request_headers[CONTENT_TYPE] += "; boundary=#{env.request.boundary}"
12
+ env.body = create_multipart(env, params)
12
13
  end
13
14
  @app.call env
14
15
  end
15
16
 
16
17
  def process_request?(env)
17
18
  type = request_type(env)
18
- env[:body].respond_to?(:each_key) and !env[:body].empty? and (
19
- (type.empty? and has_multipart?(env[:body])) or
19
+ env.body.respond_to?(:each_key) and !env.body.empty? and (
20
+ (type.empty? and has_multipart?(env.body)) or
20
21
  type == self.class.mime_type
21
22
  )
22
23
  end
@@ -32,14 +33,14 @@ module Faraday
32
33
  end
33
34
 
34
35
  def create_multipart(env, params)
35
- boundary = env[:request][:boundary]
36
+ boundary = env.request.boundary
36
37
  parts = process_params(params) do |key, value|
37
38
  Faraday::Parts::Part.new(boundary, key, value)
38
39
  end
39
40
  parts << Faraday::Parts::EpiloguePart.new(boundary)
40
41
 
41
42
  body = Faraday::CompositeReadIO.new(parts)
42
- env[:request_headers]['Content-Length'] = body.length.to_s
43
+ env.request_headers[Faraday::Env::ContentLength] = body.length.to_s
43
44
  return body
44
45
  end
45
46
 
@@ -1,23 +1,118 @@
1
1
  module Faraday
2
+ # Catches exceptions and retries each request a limited number of times.
3
+ #
4
+ # By default, it retries 2 times and handles only timeout exceptions. It can
5
+ # be configured with an arbitrary number of retries, a list of exceptions to
6
+ # handle, a retry interval, a percentage of randomness to add to the retry
7
+ # interval, and a backoff factor.
8
+ #
9
+ # Examples
10
+ #
11
+ # Faraday.new do |conn|
12
+ # conn.request :retry, max: 2, interval: 0.05,
13
+ # interval_randomness: 0.5, backoff_factor: 2
14
+ # exceptions: [CustomException, 'Timeout::Error']
15
+ # conn.adapter ...
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
+ #
2
21
  class Request::Retry < Faraday::Middleware
3
- def initialize(app, retries = 2)
4
- @retries = retries
22
+ class Options < Faraday::Options.new(:max, :interval, :interval_randomness, :backoff_factor, :exceptions)
23
+ def self.from(value)
24
+ if Fixnum === value
25
+ new(value)
26
+ else
27
+ super(value)
28
+ end
29
+ end
30
+
31
+ def max
32
+ (self[:max] ||= 2).to_i
33
+ end
34
+
35
+ def interval
36
+ (self[:interval] ||= 0).to_f
37
+ end
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
+
47
+ def exceptions
48
+ Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
49
+ Error::TimeoutError])
50
+ end
51
+
52
+ end
53
+
54
+ # Public: Initialize middleware
55
+ #
56
+ # Options:
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])
69
+ def initialize(app, options = nil)
5
70
  super(app)
71
+ @options = Options.from(options)
72
+ @errmatch = build_exception_matcher(@options.exceptions)
73
+ end
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
6
80
  end
7
81
 
8
82
  def call(env)
9
- retries = @retries
83
+ retries = @options.max
10
84
  request_body = env[:body]
11
85
  begin
12
86
  env[:body] = request_body # after failure env[:body] is set to the response body
13
87
  @app.call(env)
14
- rescue StandardError, Timeout::Error
88
+ rescue @errmatch
15
89
  if retries > 0
16
90
  retries -= 1
91
+ sleep sleep_amount(retries + 1)
17
92
  retry
18
93
  end
19
94
  raise
20
95
  end
21
96
  end
97
+
98
+ # Private: construct an exception matcher object.
99
+ #
100
+ # An exception matcher for the rescue clause can usually be any object that
101
+ # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
102
+ def build_exception_matcher(exceptions)
103
+ matcher = Module.new
104
+ (class << matcher; self; end).class_eval do
105
+ define_method(:===) do |error|
106
+ exceptions.any? do |ex|
107
+ if ex.is_a? Module
108
+ error.is_a? ex
109
+ else
110
+ error.class.to_s == ex.to_s
111
+ end
112
+ end
113
+ end
114
+ end
115
+ matcher
116
+ end
22
117
  end
23
118
  end
@@ -1,10 +1,10 @@
1
1
  module Faraday
2
- class Request::TokenAuthentication < Request::Authorization
2
+ class Request::TokenAuthentication < Request.load_middleware(:authorization)
3
3
  # Public
4
4
  def self.header(token, options = nil)
5
5
  options ||= {}
6
6
  options[:token] = token
7
- super :Token, options
7
+ super(:Token, options)
8
8
  end
9
9
 
10
10
  def initialize(app, token, options = nil)
@@ -1,6 +1,6 @@
1
1
  module Faraday
2
2
  class Request::UrlEncoded < Faraday::Middleware
3
- CONTENT_TYPE = 'Content-Type'.freeze
3
+ CONTENT_TYPE = 'Content-Type'.freeze unless defined? CONTENT_TYPE
4
4
 
5
5
  class << self
6
6
  attr_accessor :mime_type
@@ -9,25 +9,26 @@ module Faraday
9
9
 
10
10
  def call(env)
11
11
  match_content_type(env) do |data|
12
- env[:body] = Faraday::Utils.build_nested_query data
12
+ params = Faraday::Utils::ParamsHash[data]
13
+ env.body = params.to_query(env.params_encoder)
13
14
  end
14
15
  @app.call env
15
16
  end
16
17
 
17
18
  def match_content_type(env)
18
19
  if process_request?(env)
19
- env[:request_headers][CONTENT_TYPE] ||= self.class.mime_type
20
- yield env[:body] unless env[:body].respond_to?(:to_str)
20
+ env.request_headers[CONTENT_TYPE] ||= self.class.mime_type
21
+ yield(env.body) unless env.body.respond_to?(:to_str)
21
22
  end
22
23
  end
23
24
 
24
25
  def process_request?(env)
25
26
  type = request_type(env)
26
- env[:body] and (type.empty? or type == self.class.mime_type)
27
+ env.body and (type.empty? or type == self.class.mime_type)
27
28
  end
28
29
 
29
30
  def request_type(env)
30
- type = env[:request_headers][CONTENT_TYPE].to_s
31
+ type = env.request_headers[CONTENT_TYPE].to_s
31
32
  type = type.split(';', 2).first if type.index(';')
32
33
  type
33
34
  end
@@ -10,43 +10,38 @@ module Faraday
10
10
  # end
11
11
  #
12
12
  class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
13
- extend AutoloadHelper
14
13
  extend MiddlewareRegistry
15
14
 
16
- autoload_all 'faraday/request',
17
- :UrlEncoded => 'url_encoded',
18
- :Multipart => 'multipart',
19
- :Retry => 'retry',
20
- :Timeout => 'timeout',
21
- :Authorization => 'authorization',
22
- :BasicAuthentication => 'basic_authentication',
23
- :TokenAuthentication => 'token_authentication'
24
-
25
- register_middleware \
26
- :url_encoded => :UrlEncoded,
27
- :multipart => :Multipart,
28
- :retry => :Retry,
29
- :authorization => :Authorization,
30
- :basic_auth => :BasicAuthentication,
31
- :token_auth => :TokenAuthentication
15
+ register_middleware File.expand_path('../request', __FILE__),
16
+ :url_encoded => [:UrlEncoded, 'url_encoded'],
17
+ :multipart => [:Multipart, 'multipart'],
18
+ :retry => [:Retry, 'retry'],
19
+ :authorization => [:Authorization, 'authorization'],
20
+ :basic_auth => [:BasicAuthentication, 'basic_authentication'],
21
+ :token_auth => [:TokenAuthentication, 'token_authentication'],
22
+ :instrumentation => [:Instrumentation, 'instrumentation']
32
23
 
33
24
  def self.create(request_method)
34
25
  new(request_method).tap do |request|
35
- yield request if block_given?
26
+ yield(request) if block_given?
36
27
  end
37
28
  end
38
29
 
39
30
  # Public: Replace params, preserving the existing hash type
40
31
  def params=(hash)
41
- if params then params.replace hash
42
- else super
32
+ if params
33
+ params.replace hash
34
+ else
35
+ super
43
36
  end
44
37
  end
45
38
 
46
39
  # Public: Replace request headers, preserving the existing hash type
47
40
  def headers=(hash)
48
- if headers then headers.replace hash
49
- else super
41
+ if headers
42
+ headers.replace hash
43
+ else
44
+ super
50
45
  end
51
46
  end
52
47
 
@@ -60,7 +55,7 @@ module Faraday
60
55
  path, query = path.split('?', 2)
61
56
  end
62
57
  self.path = path
63
- self.params.merge_query query
58
+ self.params.merge_query query, options.params_encoder
64
59
  self.params.update(params) if params
65
60
  end
66
61
 
@@ -89,13 +84,9 @@ module Faraday
89
84
  # :password - Proxy server password
90
85
  # :ssl - Hash of options for configuring SSL requests.
91
86
  def to_env(connection)
92
- { :method => method,
93
- :body => body,
94
- :url => connection.build_exclusive_url(path, params),
95
- :request_headers => headers,
96
- :parallel_manager => connection.parallel_manager,
97
- :request => options,
98
- :ssl => connection.ssl}
87
+ Env.new(method, body, connection.build_exclusive_url(path, params),
88
+ options, headers, connection.ssl, connection.parallel_manager)
99
89
  end
100
90
  end
101
91
  end
92
+
@@ -15,14 +15,14 @@ module Faraday
15
15
  def_delegators :@logger, :debug, :info, :warn, :error, :fatal
16
16
 
17
17
  def call(env)
18
- info "#{env[:method]} #{env[:url].to_s}"
19
- debug('request') { dump_headers env[:request_headers] }
18
+ info "#{env.method} #{env.url.to_s}"
19
+ debug('request') { dump_headers env.request_headers }
20
20
  super
21
21
  end
22
22
 
23
23
  def on_complete(env)
24
- info('Status') { env[:status].to_s }
25
- debug('response') { dump_headers env[:response_headers] }
24
+ info('Status') { env.status.to_s }
25
+ debug('response') { dump_headers env.response_headers }
26
26
  end
27
27
 
28
28
  private
@@ -1,5 +1,7 @@
1
1
  module Faraday
2
2
  class Response::RaiseError < Response::Middleware
3
+ ClientErrorStatuses = 400...600
4
+
3
5
  def on_complete(env)
4
6
  case env[:status]
5
7
  when 404
@@ -7,13 +9,13 @@ module Faraday
7
9
  when 407
8
10
  # mimic the behavior that we get with proxy requests with HTTPS
9
11
  raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
10
- when 400...600
12
+ when ClientErrorStatuses
11
13
  raise Faraday::Error::ClientError, response_values(env)
12
14
  end
13
15
  end
14
16
 
15
17
  def response_values(env)
16
- {:status => env[:status], :headers => env[:response_headers], :body => env[:body]}
18
+ {:status => env.status, :headers => env.response_headers, :body => env.body}
17
19
  end
18
20
  end
19
21
  end
@@ -13,43 +13,37 @@ module Faraday
13
13
  # Override this to modify the environment after the response has finished.
14
14
  # Calls the `parse` method if defined
15
15
  def on_complete(env)
16
- if respond_to? :parse
17
- env[:body] = parse(env[:body]) unless [204,304].index env[:status]
18
- end
16
+ env.body = parse(env.body) if respond_to?(:parse) && env.parse_body?
19
17
  end
20
18
  end
21
19
 
22
20
  extend Forwardable
23
- extend AutoloadHelper
24
21
  extend MiddlewareRegistry
25
22
 
26
- autoload_all 'faraday/response',
27
- :RaiseError => 'raise_error',
28
- :Logger => 'logger'
29
-
30
- register_middleware \
31
- :raise_error => :RaiseError,
32
- :logger => :Logger
23
+ register_middleware File.expand_path('../response', __FILE__),
24
+ :raise_error => [:RaiseError, 'raise_error'],
25
+ :logger => [:Logger, 'logger']
33
26
 
34
27
  def initialize(env = nil)
35
- @env = env
28
+ @env = Env.from(env) if env
36
29
  @on_complete_callbacks = []
37
30
  end
38
31
 
39
32
  attr_reader :env
40
- alias_method :to_hash, :env
33
+
34
+ def_delegators :env, :to_hash
41
35
 
42
36
  def status
43
- finished? ? env[:status] : nil
37
+ finished? ? env.status : nil
44
38
  end
45
39
 
46
40
  def headers
47
- finished? ? env[:response_headers] : {}
41
+ finished? ? env.response_headers : {}
48
42
  end
49
43
  def_delegator :headers, :[]
50
44
 
51
45
  def body
52
- finished? ? env[:body] : nil
46
+ finished? ? env.body : nil
53
47
  end
54
48
 
55
49
  def finished?
@@ -60,39 +54,39 @@ module Faraday
60
54
  if not finished?
61
55
  @on_complete_callbacks << Proc.new
62
56
  else
63
- yield env
57
+ yield(env)
64
58
  end
65
59
  return self
66
60
  end
67
61
 
68
62
  def finish(env)
69
63
  raise "response already finished" if finished?
70
- @env = env
64
+ @env = Env.from(env)
71
65
  @on_complete_callbacks.each { |callback| callback.call(env) }
72
66
  return self
73
67
  end
74
68
 
75
69
  def success?
76
- (200..299).include?(status)
70
+ finished? && env.success?
77
71
  end
78
72
 
79
73
  # because @on_complete_callbacks cannot be marshalled
80
74
  def marshal_dump
81
75
  !finished? ? nil : {
82
- :status => @env[:status], :body => @env[:body],
83
- :response_headers => @env[:response_headers]
76
+ :status => @env.status, :body => @env.body,
77
+ :response_headers => @env.response_headers
84
78
  }
85
79
  end
86
80
 
87
81
  def marshal_load(env)
88
- @env = env
82
+ @env = Env.from(env)
89
83
  end
90
84
 
91
85
  # Expand the env with more properties, without overriding existing ones.
92
86
  # Useful for applying request params after restoring a marshalled Response.
93
87
  def apply_request(request_env)
94
88
  raise "response didn't finish yet" unless finished?
95
- @env = request_env.merge @env
89
+ @env = Env.from(request_env).update(@env)
96
90
  return self
97
91
  end
98
92
  end