faraday 0.8.11 → 0.9.0.rc1

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 (70) hide show
  1. data/.document +6 -0
  2. data/CONTRIBUTING.md +36 -0
  3. data/Gemfile +7 -6
  4. data/LICENSE.md +1 -1
  5. data/README.md +38 -51
  6. data/Rakefile +2 -18
  7. data/faraday.gemspec +34 -0
  8. data/lib/faraday.rb +181 -67
  9. data/lib/faraday/adapter.rb +19 -34
  10. data/lib/faraday/adapter/em_http.rb +24 -10
  11. data/lib/faraday/adapter/em_synchrony.rb +1 -15
  12. data/lib/faraday/adapter/excon.rb +6 -12
  13. data/lib/faraday/adapter/httpclient.rb +92 -0
  14. data/lib/faraday/adapter/net_http.rb +2 -3
  15. data/lib/faraday/adapter/net_http_persistent.rb +3 -15
  16. data/lib/faraday/adapter/patron.rb +13 -21
  17. data/lib/faraday/adapter/rack.rb +0 -2
  18. data/lib/faraday/adapter/test.rb +35 -36
  19. data/lib/faraday/adapter/typhoeus.rb +10 -12
  20. data/lib/faraday/autoload.rb +87 -0
  21. data/lib/faraday/connection.rb +196 -99
  22. data/lib/faraday/error.rb +33 -33
  23. data/lib/faraday/options.rb +215 -0
  24. data/lib/faraday/parameters.rb +193 -0
  25. data/lib/faraday/{builder.rb → rack_builder.rb} +78 -21
  26. data/lib/faraday/request.rb +12 -25
  27. data/lib/faraday/request/authorization.rb +3 -3
  28. data/lib/faraday/request/basic_authentication.rb +1 -1
  29. data/lib/faraday/request/instrumentation.rb +38 -0
  30. data/lib/faraday/request/multipart.rb +10 -9
  31. data/lib/faraday/request/retry.rb +70 -6
  32. data/lib/faraday/request/token_authentication.rb +2 -2
  33. data/lib/faraday/request/url_encoded.rb +7 -6
  34. data/lib/faraday/response.rb +17 -22
  35. data/lib/faraday/response/logger.rb +4 -4
  36. data/lib/faraday/response/raise_error.rb +4 -5
  37. data/lib/faraday/utils.rb +54 -67
  38. data/script/console +7 -0
  39. data/script/release +6 -3
  40. data/script/server +3 -1
  41. data/script/test +7 -33
  42. data/test/adapters/em_http_test.rb +6 -1
  43. data/test/adapters/em_synchrony_test.rb +7 -1
  44. data/test/adapters/excon_test.rb +0 -7
  45. data/test/adapters/httpclient_test.rb +16 -0
  46. data/test/adapters/integration.rb +8 -39
  47. data/test/adapters/logger_test.rb +1 -1
  48. data/test/adapters/net_http_test.rb +0 -31
  49. data/test/adapters/patron_test.rb +1 -1
  50. data/test/adapters/rack_test.rb +0 -5
  51. data/test/adapters/test_middleware_test.rb +19 -4
  52. data/test/adapters/typhoeus_test.rb +20 -3
  53. data/test/authentication_middleware_test.rb +7 -7
  54. data/test/connection_test.rb +52 -75
  55. data/test/env_test.rb +33 -24
  56. data/test/helper.rb +15 -13
  57. data/test/live_server.rb +10 -4
  58. data/test/middleware/instrumentation_test.rb +75 -0
  59. data/test/middleware/retry_test.rb +44 -38
  60. data/test/middleware_stack_test.rb +12 -11
  61. data/test/options_test.rb +126 -0
  62. data/test/request_middleware_test.rb +17 -7
  63. data/test/response_middleware_test.rb +2 -4
  64. data/test/strawberry.rb +2 -0
  65. metadata +82 -28
  66. checksums.yaml +0 -7
  67. data/script/proxy-server +0 -41
  68. data/test/multibyte.txt +0 -1
  69. data/test/parameters_test.rb +0 -24
  70. data/test/utils_test.rb +0 -30
@@ -1,11 +1,12 @@
1
1
  module Faraday
2
- # Possibly going to extend this a bit.
2
+ # A Builder that processes requests into responses by passing through an inner
3
+ # middleware stack (heavily inspired by Rack).
3
4
  #
4
- # Faraday::Connection.new(:url => 'http://sushi.com') do |builder|
5
- # builder.request :url_encoded # Faraday::Request::UrlEncoded
6
- # builder.adapter :net_http # Faraday::Adapter::NetHttp
7
- # end
8
- class Builder
5
+ # Faraday::Connection.new(:url => 'http://sushi.com') do |builder|
6
+ # builder.request :url_encoded # Faraday::Request::UrlEncoded
7
+ # builder.adapter :net_http # Faraday::Adapter::NetHttp
8
+ # end
9
+ class RackBuilder
9
10
  attr_accessor :handlers
10
11
 
11
12
  # Error raised when trying to modify the stack after calling `lock!`
@@ -14,15 +15,19 @@ module Faraday
14
15
  # borrowed from ActiveSupport::Dependencies::Reference &
15
16
  # ActionDispatch::MiddlewareStack::Middleware
16
17
  class Handler
18
+ @@constants_mutex = Mutex.new
17
19
  @@constants = Hash.new { |h, k|
18
- h[k] = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
20
+ value = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
21
+ @@constants_mutex.synchronize { h[k] = value }
19
22
  }
20
23
 
21
24
  attr_reader :name
22
25
 
23
26
  def initialize(klass, *args, &block)
24
27
  @name = klass.to_s
25
- @@constants[@name] = klass if klass.respond_to?(:name)
28
+ if klass.respond_to?(:name)
29
+ @@constants_mutex.synchronize { @@constants[@name] = klass }
30
+ end
26
31
  @args, @block = args, block
27
32
  end
28
33
 
@@ -65,19 +70,6 @@ module Faraday
65
70
  @handlers[idx]
66
71
  end
67
72
 
68
- def ==(other)
69
- other.is_a?(self.class) && @handlers == other.handlers
70
- end
71
-
72
- def dup
73
- self.class.new(@handlers.dup)
74
- end
75
-
76
- def to_app(inner_app)
77
- # last added handler is the deepest and thus closest to the inner app
78
- @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
79
- end
80
-
81
73
  # Locks the middleware stack to ensure no further modifications are possible.
82
74
  def lock!
83
75
  @handlers.freeze
@@ -136,6 +128,71 @@ module Faraday
136
128
  @handlers.delete(handler)
137
129
  end
138
130
 
131
+ # Processes a Request into a Response by passing it through this Builder's
132
+ # middleware stack.
133
+ #
134
+ # connection - Faraday::Connection
135
+ # request - Faraday::Request
136
+ #
137
+ # Returns a Faraday::Response.
138
+ def build_response(connection, request)
139
+ app.call(build_env(connection, request))
140
+ end
141
+
142
+ # The "rack app" wrapped in middleware. All requests are sent here.
143
+ #
144
+ # The builder is responsible for creating the app object. After this,
145
+ # the builder gets locked to ensure no further modifications are made
146
+ # to the middleware stack.
147
+ #
148
+ # Returns an object that responds to `call` and returns a Response.
149
+ def app
150
+ @app ||= begin
151
+ lock!
152
+ to_app(lambda { |env|
153
+ response = Response.new
154
+ response.finish(env) unless env.parallel?
155
+ env.response = response
156
+ })
157
+ end
158
+ end
159
+
160
+ def to_app(inner_app)
161
+ # last added handler is the deepest and thus closest to the inner app
162
+ @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
163
+ end
164
+
165
+ def ==(other)
166
+ other.is_a?(self.class) && @handlers == other.handlers
167
+ end
168
+
169
+ def dup
170
+ self.class.new(@handlers.dup)
171
+ end
172
+
173
+ # ENV Keys
174
+ # :method - a symbolized request method (:get, :post)
175
+ # :body - the request body that will eventually be converted to a string.
176
+ # :url - URI instance for the current request.
177
+ # :status - HTTP response status code
178
+ # :request_headers - hash of HTTP Headers to be sent to the server
179
+ # :response_headers - Hash of HTTP headers from the server
180
+ # :parallel_manager - sent if the connection is in parallel mode
181
+ # :request - Hash of options for configuring the request.
182
+ # :timeout - open/read timeout Integer in seconds
183
+ # :open_timeout - read timeout Integer in seconds
184
+ # :proxy - Hash of proxy options
185
+ # :uri - Proxy Server URI
186
+ # :user - Proxy server username
187
+ # :password - Proxy server password
188
+ # :ssl - Hash of options for configuring SSL requests.
189
+ def build_env(connection, request)
190
+ Env.new(request.method, request.body,
191
+ connection.build_exclusive_url(request.path, request.params),
192
+ request.options, request.headers, connection.ssl,
193
+ connection.parallel_manager)
194
+ end
195
+
139
196
  private
140
197
 
141
198
  def raise_if_locked
@@ -10,25 +10,16 @@ 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|
@@ -60,7 +51,7 @@ module Faraday
60
51
  path, query = path.split('?', 2)
61
52
  end
62
53
  self.path = path
63
- self.params.merge_query query
54
+ self.params.merge_query query, options.params_encoder
64
55
  self.params.update(params) if params
65
56
  end
66
57
 
@@ -89,13 +80,9 @@ module Faraday
89
80
  # :password - Proxy server password
90
81
  # :ssl - Hash of options for configuring SSL requests.
91
82
  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}
83
+ Env.new(method, body, connection.build_exclusive_url(path, params),
84
+ options, headers, connection.ssl, connection.parallel_manager)
99
85
  end
100
86
  end
101
87
  end
88
+
@@ -1,6 +1,6 @@
1
1
  module Faraday
2
2
  class Request::Authorization < Faraday::Middleware
3
- KEY = "Authorization".freeze
3
+ KEY = "Authorization".freeze unless defined? KEY
4
4
 
5
5
  # Public
6
6
  def self.header(type, token)
@@ -30,8 +30,8 @@ module Faraday
30
30
 
31
31
  # Public
32
32
  def call(env)
33
- unless env[:request_headers][KEY]
34
- env[:request_headers][KEY] = @header_value
33
+ unless env.request_headers[KEY]
34
+ env.request_headers[KEY] = @header_value
35
35
  end
36
36
  @app.call(env)
37
37
  end
@@ -1,7 +1,7 @@
1
1
  require 'base64'
2
2
 
3
3
  module Faraday
4
- class Request::BasicAuthentication < Request::Authorization
4
+ class Request::BasicAuthentication < Request.load_middleware(:authorization)
5
5
  # Public
6
6
  def self.header(login, pass)
7
7
  value = Base64.encode64([login, pass].join(':'))
@@ -0,0 +1,38 @@
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
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
37
+
38
+
@@ -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,87 @@
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 an a retry interval.
7
+ #
8
+ # Examples
9
+ #
10
+ # Faraday.new do |conn|
11
+ # conn.request :retry, max: 2, interval: 0.05,
12
+ # exceptions: [CustomException, 'Timeout::Error']
13
+ # conn.adapter ...
14
+ # end
2
15
  class Request::Retry < Faraday::Middleware
3
- def initialize(app, retries = 2)
4
- @retries = retries
16
+ class Options < Faraday::Options.new(:max, :interval, :exceptions)
17
+ def self.from(value)
18
+ if Fixnum === value
19
+ new(value)
20
+ else
21
+ super(value)
22
+ end
23
+ end
24
+
25
+ def max
26
+ (self[:max] ||= 2).to_i
27
+ end
28
+
29
+ def interval
30
+ (self[:interval] ||= 0).to_f
31
+ end
32
+
33
+ def exceptions
34
+ Array(self[:exceptions] ||= [Errno::ETIMEDOUT, 'Timeout::Error',
35
+ Error::TimeoutError])
36
+ end
37
+
38
+ end
39
+
40
+ # Public: Initialize middleware
41
+ #
42
+ # 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])
48
+ def initialize(app, options = nil)
5
49
  super(app)
50
+ @options = Options.from(options)
51
+ @errmatch = build_exception_matcher(@options.exceptions)
6
52
  end
7
53
 
8
54
  def call(env)
9
- retries = @retries
10
- request_body = env[:body]
55
+ retries = @options.max
11
56
  begin
12
- env[:body] = request_body # after failure env[:body] is set to the response body
13
57
  @app.call(env)
14
- rescue StandardError, Timeout::Error
58
+ rescue @errmatch
15
59
  if retries > 0
16
60
  retries -= 1
61
+ sleep @options.interval if @options.interval > 0
17
62
  retry
18
63
  end
19
64
  raise
20
65
  end
21
66
  end
67
+
68
+ # Private: construct an exception matcher object.
69
+ #
70
+ # An exception matcher for the rescue clause can usually be any object that
71
+ # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
72
+ def build_exception_matcher(exceptions)
73
+ matcher = Module.new
74
+ (class << matcher; self; end).class_eval do
75
+ define_method(:===) do |error|
76
+ exceptions.any? do |ex|
77
+ if ex.is_a? Module then error.is_a? ex
78
+ else error.class.to_s == ex.to_s
79
+ end
80
+ end
81
+ end
82
+ end
83
+ matcher
84
+ end
22
85
  end
23
86
  end
87
+
@@ -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