faraday 0.6.0 → 0.8.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 (58) hide show
  1. data/Gemfile +22 -15
  2. data/{LICENSE → LICENSE.md} +1 -1
  3. data/README.md +216 -146
  4. data/Rakefile +14 -58
  5. data/config.ru +6 -0
  6. data/faraday.gemspec +31 -38
  7. data/lib/faraday/adapter/em_http.rb +204 -0
  8. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +66 -0
  9. data/lib/faraday/adapter/em_synchrony.rb +67 -16
  10. data/lib/faraday/adapter/excon.rb +1 -2
  11. data/lib/faraday/adapter/net_http.rb +82 -39
  12. data/lib/faraday/adapter/net_http_persistent.rb +37 -0
  13. data/lib/faraday/adapter/patron.rb +42 -8
  14. data/lib/faraday/adapter/rack.rb +59 -0
  15. data/lib/faraday/adapter/test.rb +49 -9
  16. data/lib/faraday/adapter/typhoeus.rb +70 -15
  17. data/lib/faraday/adapter.rb +41 -18
  18. data/lib/faraday/builder.rb +34 -16
  19. data/lib/faraday/connection.rb +177 -115
  20. data/lib/faraday/error.rb +4 -2
  21. data/lib/faraday/middleware.rb +18 -10
  22. data/lib/faraday/request/authorization.rb +40 -0
  23. data/lib/faraday/request/basic_authentication.rb +13 -0
  24. data/lib/faraday/request/multipart.rb +5 -6
  25. data/lib/faraday/request/retry.rb +21 -0
  26. data/lib/faraday/request/token_authentication.rb +15 -0
  27. data/lib/faraday/request/url_encoded.rb +1 -3
  28. data/lib/faraday/request.rb +48 -38
  29. data/lib/faraday/response/raise_error.rb +1 -1
  30. data/lib/faraday/response.rb +6 -5
  31. data/lib/faraday/upload_io.rb +1 -1
  32. data/lib/faraday/utils.rb +196 -29
  33. data/lib/faraday.rb +69 -13
  34. data/test/adapters/default_test.rb +14 -0
  35. data/test/adapters/em_http_test.rb +14 -0
  36. data/test/adapters/em_synchrony_test.rb +14 -0
  37. data/test/adapters/excon_test.rb +23 -0
  38. data/test/adapters/integration.rb +190 -0
  39. data/test/adapters/logger_test.rb +2 -2
  40. data/test/adapters/net_http_persistent_test.rb +11 -0
  41. data/test/adapters/net_http_test.rb +37 -21
  42. data/test/adapters/patron_test.rb +17 -0
  43. data/test/adapters/rack_test.rb +26 -0
  44. data/test/adapters/test_middleware_test.rb +28 -1
  45. data/test/adapters/typhoeus_test.rb +14 -0
  46. data/test/authentication_middleware_test.rb +65 -0
  47. data/test/connection_test.rb +211 -81
  48. data/test/env_test.rb +80 -31
  49. data/test/helper.rb +23 -13
  50. data/test/live_server.rb +38 -29
  51. data/test/middleware/retry_test.rb +25 -0
  52. data/test/middleware_stack_test.rb +85 -4
  53. data/test/request_middleware_test.rb +42 -19
  54. data/test/response_middleware_test.rb +30 -3
  55. metadata +104 -110
  56. data/lib/faraday/adapter/action_dispatch.rb +0 -30
  57. data/lib/faraday/request/json.rb +0 -31
  58. data/test/adapters/live_test.rb +0 -186
@@ -0,0 +1,37 @@
1
+ require 'faraday/adapter/net_http'
2
+
3
+ module Faraday
4
+ class Adapter
5
+ # Experimental adapter for net-http-persistent
6
+ class NetHttpPersistent < NetHttp
7
+ dependency 'net/http/persistent'
8
+
9
+ # TODO: investigate is it safe to create a new Persistent instance for
10
+ # every request, or does it defy the purpose of persistent connections
11
+ def net_http_connection(env)
12
+ Net::HTTP::Persistent.new 'Faraday',
13
+ env[:request][:proxy] ? env[:request][:proxy][:uri] : nil
14
+ end
15
+
16
+ def perform_request(http, env)
17
+ http.request env[:url], create_request(env)
18
+ rescue Net::HTTP::Persistent::Error => error
19
+ if error.message.include? 'Timeout::Error'
20
+ raise Faraday::Error::TimeoutError, error
21
+ else
22
+ raise
23
+ end
24
+ end
25
+
26
+ def configure_ssl(http, ssl)
27
+ http.verify_mode = ssl_verify_mode(ssl)
28
+ http.cert_store = ssl_cert_store(ssl)
29
+
30
+ http.certificate = ssl[:client_cert] if ssl[:client_cert]
31
+ http.private_key = ssl[:client_key] if ssl[:client_key]
32
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
33
+ http.ssl_version = ssl[:version] if ssl[:version]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -3,29 +3,63 @@ module Faraday
3
3
  class Patron < Faraday::Adapter
4
4
  dependency 'patron'
5
5
 
6
+ def initialize(app, &block)
7
+ super(app)
8
+ @block = block if block_given?
9
+ end
10
+
6
11
  def call(env)
7
12
  super
8
13
 
9
14
  # TODO: support streaming requests
10
15
  env[:body] = env[:body].read if env[:body].respond_to? :read
11
16
 
12
- session = ::Patron::Session.new
17
+ session = @session ||= create_session
13
18
 
14
- response = begin
15
- if Connection::METHODS_WITH_BODIES.include? env[:method]
16
- session.send(env[:method], env[:url].to_s, env[:body].to_s, env[:request_headers])
17
- else
18
- session.send(env[:method], env[:url].to_s, env[:request_headers])
19
+ if req = env[:request]
20
+ session.timeout = session.connect_timeout = req[:timeout] if req[:timeout]
21
+ session.connect_timeout = req[:open_timeout] if req[:open_timeout]
22
+
23
+ if proxy = req[:proxy]
24
+ session.proxy = proxy[:uri].to_s
25
+ if proxy[:user] && proxy[:password]
26
+ prepend_proxy_auth_string(proxy, session)
27
+ end
19
28
  end
29
+ end
30
+
31
+ response = begin
32
+ data = env[:body] ? env[:body].to_s : nil
33
+ session.request(env[:method], env[:url].to_s, env[:request_headers], :data => data)
20
34
  rescue Errno::ECONNREFUSED
21
35
  raise Error::ConnectionFailed, $!
22
36
  end
23
37
 
24
- env.update :status => response.status, :body => response.body
25
- response_headers(env).update response.headers
38
+ save_response(env, response.status, response.body, response.headers)
26
39
 
27
40
  @app.call env
41
+ rescue ::Patron::TimeoutError => err
42
+ raise Faraday::Error::TimeoutError, err
28
43
  end
44
+
45
+ if loaded? && defined?(::Patron::Request::VALID_ACTIONS)
46
+ # HAX: helps but doesn't work completely
47
+ # https://github.com/toland/patron/issues/34
48
+ ::Patron::Request::VALID_ACTIONS.tap do |actions|
49
+ actions << :patch unless actions.include? :patch
50
+ actions << :options unless actions.include? :options
51
+ end
52
+ end
53
+
54
+ def create_session
55
+ session = ::Patron::Session.new
56
+ @block.call(session) if @block
57
+ session
58
+ end
59
+ end
60
+
61
+ def prepend_proxy_auth_string(proxy, session)
62
+ session.proxy.insert(7, "#{proxy[:user]}:#{proxy[:password]}@")
29
63
  end
30
64
  end
31
65
  end
@@ -0,0 +1,59 @@
1
+ require 'timeout'
2
+
3
+ module Faraday
4
+ class Adapter
5
+ # Sends requests to a Rack app.
6
+ #
7
+ # Examples
8
+ #
9
+ # class MyRackApp
10
+ # def call(env)
11
+ # [200, {'Content-Type' => 'text/html'}, ["hello world"]]
12
+ # end
13
+ # end
14
+ #
15
+ # Faraday.new do |conn|
16
+ # conn.adapter :rack, MyRackApp
17
+ # end
18
+ class Rack < Faraday::Adapter
19
+ dependency 'rack/test'
20
+
21
+ # not prefixed with "HTTP_"
22
+ SPECIAL_HEADERS = %w[ CONTENT_LENGTH CONTENT_TYPE ]
23
+
24
+ def initialize(faraday_app, rack_app)
25
+ super(faraday_app)
26
+ mock_session = ::Rack::MockSession.new(rack_app)
27
+ @session = ::Rack::Test::Session.new(mock_session)
28
+ end
29
+
30
+ def call(env)
31
+ super
32
+ rack_env = {
33
+ :method => env[:method],
34
+ :input => env[:body].respond_to?(:read) ? env[:body].read : env[:body]
35
+ }
36
+
37
+ env[:request_headers].each do |name, value|
38
+ name = name.upcase.tr('-', '_')
39
+ name = "HTTP_#{name}" unless SPECIAL_HEADERS.include? name
40
+ rack_env[name] = value
41
+ end if env[:request_headers]
42
+
43
+ timeout = env[:request][:timeout] || env[:request][:open_timeout]
44
+ response = if timeout
45
+ Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
46
+ else
47
+ execute_request(env, rack_env)
48
+ end
49
+
50
+ save_response(env, response.status, response.body, response.headers)
51
+ @app.call env
52
+ end
53
+
54
+ def execute_request(env, rack_env)
55
+ @session.request(env[:url].to_s, rack_env)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -14,9 +14,10 @@ module Faraday
14
14
  class Test < Faraday::Adapter
15
15
  attr_accessor :stubs
16
16
 
17
- def self.loaded?() false end
18
-
19
17
  class Stubs
18
+ class NotFound < StandardError
19
+ end
20
+
20
21
  def initialize
21
22
  # {:get => [Stub, Stub]}
22
23
  @stack, @consumed = {}, {}
@@ -31,6 +32,7 @@ module Faraday
31
32
  return false if !@stack.key?(request_method)
32
33
  stack = @stack[request_method]
33
34
  consumed = (@consumed[request_method] ||= [])
35
+ path = normalize_path(path)
34
36
 
35
37
  if stub = matches?(stack, path, body)
36
38
  consumed << stack.delete(stub)
@@ -56,10 +58,18 @@ module Faraday
56
58
  new_stub(:put, path, body, &block)
57
59
  end
58
60
 
61
+ def patch(path, body=nil, &block)
62
+ new_stub(:patch, path, body, &block)
63
+ end
64
+
59
65
  def delete(path, &block)
60
66
  new_stub(:delete, path, &block)
61
67
  end
62
68
 
69
+ def options(path, &block)
70
+ new_stub(:options, path, &block)
71
+ end
72
+
63
73
  # Raises an error if any of the stubbed calls have not been made.
64
74
  def verify_stubbed_calls
65
75
  failed_stubs = []
@@ -76,17 +86,45 @@ module Faraday
76
86
  protected
77
87
 
78
88
  def new_stub(request_method, path, body=nil, &block)
79
- (@stack[request_method] ||= []) << Stub.new(path, body, block)
89
+ (@stack[request_method] ||= []) << Stub.new(normalize_path(path), body, block)
80
90
  end
81
91
 
82
92
  def matches?(stack, path, body)
83
93
  stack.detect { |stub| stub.matches?(path, body) }
84
94
  end
95
+
96
+ # ensure leading + trailing slash
97
+ def normalize_path(path)
98
+ path = '/' + path if path.index('/') != 0
99
+ path = path.sub('?', '/?')
100
+ path = path + '/' unless $&
101
+ path.gsub('//', '/')
102
+ end
85
103
  end
86
104
 
87
- class Stub < Struct.new(:path, :body, :block)
88
- def matches?(request_path, request_body)
89
- request_path == path && (body.to_s.size.zero? || request_body == body)
105
+ class Stub < Struct.new(:path, :params, :body, :block)
106
+ def initialize(full, body, block)
107
+ path, query = full.split('?')
108
+ params = query ?
109
+ Faraday::Utils.parse_nested_query(query) :
110
+ {}
111
+ super path, params, body, block
112
+ end
113
+
114
+ def matches?(request_uri, request_body)
115
+ request_path, request_query = request_uri.split('?')
116
+ request_params = request_query ?
117
+ Faraday::Utils.parse_nested_query(request_query) :
118
+ {}
119
+ request_path == path &&
120
+ params_match?(request_params) &&
121
+ (body.to_s.size.zero? || request_body == body)
122
+ end
123
+
124
+ def params_match?(request_params)
125
+ params.keys.all? do |key|
126
+ request_params[key] == params[key]
127
+ end
90
128
  end
91
129
 
92
130
  def to_s
@@ -109,11 +147,13 @@ module Faraday
109
147
  normalized_path = Faraday::Utils.normalize_path(env[:url])
110
148
 
111
149
  if stub = stubs.match(env[:method], normalized_path, env[:body])
150
+ env[:params] = (query = env[:url].query) ?
151
+ Faraday::Utils.parse_nested_query(query) :
152
+ {}
112
153
  status, headers, body = stub.block.call(env)
113
- env.update :status => status, :body => body
114
- response_headers(env).update headers
154
+ save_response(env, status, body, headers)
115
155
  else
116
- raise "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
156
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
117
157
  end
118
158
  @app.call(env)
119
159
  end
@@ -1,7 +1,7 @@
1
1
  module Faraday
2
2
  class Adapter
3
3
  class Typhoeus < Faraday::Adapter
4
- self.supports_parallel_requests = true
4
+ self.supports_parallel = true
5
5
 
6
6
  def self.setup_parallel_manager(options = {})
7
7
  options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
@@ -11,34 +11,89 @@ module Faraday
11
11
 
12
12
  def call(env)
13
13
  super
14
+ perform_request env
15
+ @app.call env
16
+ end
17
+
18
+ def perform_request(env)
19
+ read_body env
20
+
21
+ hydra = env[:parallel_manager] || self.class.setup_parallel_manager
22
+ hydra.queue request(env)
23
+ hydra.run unless parallel?(env)
24
+ rescue Errno::ECONNREFUSED
25
+ raise Error::ConnectionFailed, $!
26
+ end
14
27
 
15
- # TODO: support streaming requests
28
+ # TODO: support streaming requests
29
+ def read_body(env)
16
30
  env[:body] = env[:body].read if env[:body].respond_to? :read
31
+ end
17
32
 
33
+ def request(env)
18
34
  req = ::Typhoeus::Request.new env[:url].to_s,
19
35
  :method => env[:method],
20
36
  :body => env[:body],
21
37
  :headers => env[:request_headers],
22
38
  :disable_ssl_peer_verification => (env[:ssl] && !env[:ssl].fetch(:verify, true))
23
39
 
24
- env_req = env[:request]
25
- req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
26
- req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
40
+ configure_ssl req, env
41
+ configure_proxy req, env
42
+ configure_timeout req, env
27
43
 
28
- is_parallel = !!env[:parallel_manager]
29
44
  req.on_complete do |resp|
30
- env.update :status => resp.code, :body => resp.body
31
- response_headers(env).parse resp.headers
32
- env[:response].finish(env) if is_parallel
45
+ if resp.timed_out?
46
+ if parallel?(env)
47
+ # TODO: error callback in async mode
48
+ else
49
+ raise Faraday::Error::TimeoutError, "request timed out"
50
+ end
51
+ end
52
+
53
+ save_response(env, resp.code, resp.body) do |response_headers|
54
+ response_headers.parse resp.headers
55
+ end
56
+ # in async mode, :response is initialized at this point
57
+ env[:response].finish(env) if parallel?(env)
33
58
  end
34
59
 
35
- hydra = env[:parallel_manager] || self.class.setup_parallel_manager
36
- hydra.queue req
37
- hydra.run unless is_parallel
60
+ req
61
+ end
38
62
 
39
- @app.call env
40
- rescue Errno::ECONNREFUSED
41
- raise Error::ConnectionFailed, $!
63
+ def configure_ssl(req, env)
64
+ ssl = env[:ssl]
65
+
66
+ req.ssl_version = ssl[:version] if ssl[:version]
67
+ req.ssl_cert = ssl[:client_cert_file] if ssl[:client_cert_file]
68
+ req.ssl_key = ssl[:client_key_file] if ssl[:client_key_file]
69
+ req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file]
70
+ req.ssl_capath = ssl[:ca_path] if ssl[:ca_path]
71
+ end
72
+
73
+ def configure_proxy(req, env)
74
+ proxy = request_options(env)[:proxy]
75
+ return unless proxy
76
+
77
+ req.proxy = "#{proxy[:uri].host}:#{proxy[:uri].port}"
78
+
79
+ if proxy[:username] && proxy[:password]
80
+ req.proxy_username = proxy[:username]
81
+ req.proxy_password = proxy[:password]
82
+ end
83
+ end
84
+
85
+ def configure_timeout(req, env)
86
+ env_req = request_options(env)
87
+ req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
88
+ req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
89
+ end
90
+
91
+ def request_options(env)
92
+ env[:request]
93
+ end
94
+
95
+ def parallel?(env)
96
+ !!env[:parallel_manager]
42
97
  end
43
98
  end
44
99
  end
@@ -3,24 +3,42 @@ module Faraday
3
3
  CONTENT_LENGTH = 'Content-Length'.freeze
4
4
 
5
5
  extend AutoloadHelper
6
+ extend MiddlewareRegistry
6
7
 
7
8
  autoload_all 'faraday/adapter',
8
- :ActionDispatch => 'action_dispatch',
9
- :NetHttp => 'net_http',
10
- :Typhoeus => 'typhoeus',
11
- :EMSynchrony => 'em_synchrony',
12
- :Patron => 'patron',
13
- :Excon => 'excon',
14
- :Test => 'test'
15
-
16
- register_lookup_modules \
17
- :action_dispatch => :ActionDispatch,
18
- :test => :Test,
19
- :net_http => :NetHttp,
20
- :typhoeus => :Typhoeus,
21
- :patron => :Patron,
22
- :em_synchrony => :EMSynchrony,
23
- :excon => :Excon
9
+ :NetHttp => 'net_http',
10
+ :NetHttpPersistent => 'net_http_persistent',
11
+ :Typhoeus => 'typhoeus',
12
+ :EMSynchrony => 'em_synchrony',
13
+ :EMHttp => 'em_http',
14
+ :Patron => 'patron',
15
+ :Excon => 'excon',
16
+ :Test => 'test',
17
+ :Rack => 'rack'
18
+
19
+ register_middleware \
20
+ :test => :Test,
21
+ :net_http => :NetHttp,
22
+ :net_http_persistent => :NetHttpPersistent,
23
+ :typhoeus => :Typhoeus,
24
+ :patron => :Patron,
25
+ :em_synchrony => :EMSynchrony,
26
+ :em_http => :EMHttp,
27
+ :excon => :Excon,
28
+ :rack => :Rack
29
+
30
+ module Parallelism
31
+ attr_writer :supports_parallel
32
+ def supports_parallel?() @supports_parallel end
33
+
34
+ def inherited(subclass)
35
+ super
36
+ subclass.supports_parallel = self.supports_parallel?
37
+ end
38
+ end
39
+
40
+ extend Parallelism
41
+ self.supports_parallel = false
24
42
 
25
43
  def call(env)
26
44
  if !env[:body] and Connection::METHODS_WITH_BODIES.include? env[:method]
@@ -31,8 +49,13 @@ module Faraday
31
49
  end
32
50
  end
33
51
 
34
- def response_headers(env)
35
- env[:response_headers] ||= Utils::Headers.new
52
+ def save_response(env, status, body, headers = nil)
53
+ env[:status] = status
54
+ env[:body] = body
55
+ env[:response_headers] = Utils::Headers.new.tap do |response_headers|
56
+ response_headers.update headers unless headers.nil?
57
+ yield response_headers if block_given?
58
+ end
36
59
  end
37
60
  end
38
61
  end
@@ -8,9 +8,8 @@ module Faraday
8
8
  class Builder
9
9
  attr_accessor :handlers
10
10
 
11
- def self.create
12
- new { |builder| yield builder }
13
- end
11
+ # Error raised when trying to modify the stack after calling `lock!`
12
+ class StackLocked < RuntimeError; end
14
13
 
15
14
  # borrowed from ActiveSupport::Dependencies::Reference &
16
15
  # ActionDispatch::MiddlewareStack::Middleware
@@ -31,7 +30,9 @@ module Faraday
31
30
  def inspect() @name end
32
31
 
33
32
  def ==(other)
34
- if other.respond_to? :name
33
+ if other.is_a? Handler
34
+ self.name == other.name
35
+ elsif other.respond_to? :name
35
36
  klass == other
36
37
  else
37
38
  @name == other.to_s
@@ -55,6 +56,7 @@ module Faraday
55
56
  end
56
57
 
57
58
  def build(options = {})
59
+ raise_if_locked
58
60
  @handlers.clear unless options[:keep]
59
61
  yield self if block_given?
60
62
  end
@@ -76,29 +78,40 @@ module Faraday
76
78
  @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
77
79
  end
78
80
 
79
- def use(klass, *args)
80
- block = block_given? ? Proc.new : nil
81
- @handlers << self.class::Handler.new(klass, *args, &block)
81
+ # Locks the middleware stack to ensure no further modifications are possible.
82
+ def lock!
83
+ @handlers.freeze
84
+ end
85
+
86
+ def locked?
87
+ @handlers.frozen?
88
+ end
89
+
90
+ def use(klass, *args, &block)
91
+ if klass.is_a? Symbol
92
+ use_symbol(Faraday::Middleware, klass, *args, &block)
93
+ else
94
+ raise_if_locked
95
+ @handlers << self.class::Handler.new(klass, *args, &block)
96
+ end
82
97
  end
83
98
 
84
- def request(key, *args)
85
- block = block_given? ? Proc.new : nil
99
+ def request(key, *args, &block)
86
100
  use_symbol(Faraday::Request, key, *args, &block)
87
101
  end
88
102
 
89
- def response(key, *args)
90
- block = block_given? ? Proc.new : nil
103
+ def response(key, *args, &block)
91
104
  use_symbol(Faraday::Response, key, *args, &block)
92
105
  end
93
106
 
94
- def adapter(key, *args)
95
- block = block_given? ? Proc.new : nil
107
+ def adapter(key, *args, &block)
96
108
  use_symbol(Faraday::Adapter, key, *args, &block)
97
109
  end
98
110
 
99
111
  ## methods to push onto the various positions in the stack:
100
112
 
101
113
  def insert(index, *args, &block)
114
+ raise_if_locked
102
115
  index = assert_index(index)
103
116
  handler = self.class::Handler.new(*args, &block)
104
117
  @handlers.insert(index, handler)
@@ -112,20 +125,25 @@ module Faraday
112
125
  end
113
126
 
114
127
  def swap(index, *args, &block)
128
+ raise_if_locked
115
129
  index = assert_index(index)
116
130
  @handlers.delete_at(index)
117
131
  insert(index, *args, &block)
118
132
  end
119
133
 
120
134
  def delete(handler)
135
+ raise_if_locked
121
136
  @handlers.delete(handler)
122
137
  end
123
138
 
124
139
  private
125
140
 
126
- def use_symbol(mod, key, *args)
127
- block = block_given? ? Proc.new : nil
128
- use(mod.lookup_module(key), *args, &block)
141
+ def raise_if_locked
142
+ raise StackLocked, "can't modify middleware stack after making a request" if locked?
143
+ end
144
+
145
+ def use_symbol(mod, key, *args, &block)
146
+ use(mod.lookup_middleware(key), *args, &block)
129
147
  end
130
148
 
131
149
  def assert_index(index)