faraday 0.8.11 → 0.9.0.rc1

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