avdi-faraday 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/Gemfile +27 -0
  2. data/LICENSE.md +20 -0
  3. data/README.md +250 -0
  4. data/Rakefile +87 -0
  5. data/config.ru +6 -0
  6. data/faraday.gemspec +86 -0
  7. data/lib/faraday.rb +276 -0
  8. data/lib/faraday/adapter.rb +71 -0
  9. data/lib/faraday/adapter/em_http.rb +217 -0
  10. data/lib/faraday/adapter/em_synchrony.rb +89 -0
  11. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +66 -0
  12. data/lib/faraday/adapter/excon.rb +59 -0
  13. data/lib/faraday/adapter/httpclient.rb +92 -0
  14. data/lib/faraday/adapter/net_http.rb +116 -0
  15. data/lib/faraday/adapter/net_http_persistent.rb +37 -0
  16. data/lib/faraday/adapter/patron.rb +65 -0
  17. data/lib/faraday/adapter/rack.rb +57 -0
  18. data/lib/faraday/adapter/test.rb +162 -0
  19. data/lib/faraday/adapter/typhoeus.rb +107 -0
  20. data/lib/faraday/builder.rb +184 -0
  21. data/lib/faraday/connection.rb +468 -0
  22. data/lib/faraday/error.rb +40 -0
  23. data/lib/faraday/middleware.rb +41 -0
  24. data/lib/faraday/request.rb +101 -0
  25. data/lib/faraday/request/authorization.rb +40 -0
  26. data/lib/faraday/request/basic_authentication.rb +13 -0
  27. data/lib/faraday/request/multipart.rb +62 -0
  28. data/lib/faraday/request/retry.rb +67 -0
  29. data/lib/faraday/request/token_authentication.rb +15 -0
  30. data/lib/faraday/request/url_encoded.rb +35 -0
  31. data/lib/faraday/response.rb +99 -0
  32. data/lib/faraday/response/logger.rb +34 -0
  33. data/lib/faraday/response/raise_error.rb +16 -0
  34. data/lib/faraday/upload_io.rb +23 -0
  35. data/lib/faraday/utils.rb +274 -0
  36. data/script/test +91 -0
  37. data/test/adapters/default_test.rb +14 -0
  38. data/test/adapters/em_http_test.rb +19 -0
  39. data/test/adapters/em_synchrony_test.rb +20 -0
  40. data/test/adapters/excon_test.rb +15 -0
  41. data/test/adapters/httpclient_test.rb +16 -0
  42. data/test/adapters/integration.rb +193 -0
  43. data/test/adapters/logger_test.rb +37 -0
  44. data/test/adapters/net_http_persistent_test.rb +11 -0
  45. data/test/adapters/net_http_test.rb +49 -0
  46. data/test/adapters/patron_test.rb +17 -0
  47. data/test/adapters/rack_test.rb +26 -0
  48. data/test/adapters/test_middleware_test.rb +70 -0
  49. data/test/adapters/typhoeus_test.rb +20 -0
  50. data/test/authentication_middleware_test.rb +65 -0
  51. data/test/connection_test.rb +375 -0
  52. data/test/env_test.rb +183 -0
  53. data/test/helper.rb +75 -0
  54. data/test/live_server.rb +57 -0
  55. data/test/middleware/retry_test.rb +62 -0
  56. data/test/middleware_stack_test.rb +203 -0
  57. data/test/middleware_test.rb +12 -0
  58. data/test/request_middleware_test.rb +108 -0
  59. data/test/response_middleware_test.rb +74 -0
  60. metadata +182 -0
@@ -0,0 +1,65 @@
1
+ module Faraday
2
+ class Adapter
3
+ class Patron < Faraday::Adapter
4
+ dependency 'patron'
5
+
6
+ def initialize(app, &block)
7
+ super(app)
8
+ @block = block if block_given?
9
+ end
10
+
11
+ def call(env)
12
+ super
13
+
14
+ # TODO: support streaming requests
15
+ env[:body] = env[:body].read if env[:body].respond_to? :read
16
+
17
+ session = @session ||= create_session
18
+
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
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)
34
+ rescue Errno::ECONNREFUSED
35
+ raise Error::ConnectionFailed, $!
36
+ end
37
+
38
+ save_response(env, response.status, response.body, response.headers)
39
+
40
+ @app.call env
41
+ rescue ::Patron::TimeoutError => err
42
+ raise Faraday::Error::TimeoutError, err
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]}@")
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,57 @@
1
+ module Faraday
2
+ class Adapter
3
+ # Sends requests to a Rack app.
4
+ #
5
+ # Examples
6
+ #
7
+ # class MyRackApp
8
+ # def call(env)
9
+ # [200, {'Content-Type' => 'text/html'}, ["hello world"]]
10
+ # end
11
+ # end
12
+ #
13
+ # Faraday.new do |conn|
14
+ # conn.adapter :rack, MyRackApp
15
+ # end
16
+ class Rack < Faraday::Adapter
17
+ dependency 'rack/test'
18
+
19
+ # not prefixed with "HTTP_"
20
+ SPECIAL_HEADERS = %w[ CONTENT_LENGTH CONTENT_TYPE ]
21
+
22
+ def initialize(faraday_app, rack_app)
23
+ super(faraday_app)
24
+ mock_session = ::Rack::MockSession.new(rack_app)
25
+ @session = ::Rack::Test::Session.new(mock_session)
26
+ end
27
+
28
+ def call(env)
29
+ super
30
+ rack_env = {
31
+ :method => env[:method],
32
+ :input => env[:body].respond_to?(:read) ? env[:body].read : env[:body]
33
+ }
34
+
35
+ env[:request_headers].each do |name, value|
36
+ name = name.upcase.tr('-', '_')
37
+ name = "HTTP_#{name}" unless SPECIAL_HEADERS.include? name
38
+ rack_env[name] = value
39
+ end if env[:request_headers]
40
+
41
+ timeout = env[:request][:timeout] || env[:request][:open_timeout]
42
+ response = if timeout
43
+ Timer.timeout(timeout, Faraday::Error::TimeoutError) { execute_request(env, rack_env) }
44
+ else
45
+ execute_request(env, rack_env)
46
+ end
47
+
48
+ save_response(env, response.status, response.body, response.headers)
49
+ @app.call env
50
+ end
51
+
52
+ def execute_request(env, rack_env)
53
+ @session.request(env[:url].to_s, rack_env)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,162 @@
1
+ module Faraday
2
+ class Adapter
3
+ # test = Faraday::Connection.new do
4
+ # use Faraday::Adapter::Test do |stub|
5
+ # stub.get '/nigiri/sake.json' do
6
+ # [200, {}, 'hi world']
7
+ # end
8
+ # end
9
+ # end
10
+ #
11
+ # resp = test.get '/nigiri/sake.json'
12
+ # resp.body # => 'hi world'
13
+ #
14
+ class Test < Faraday::Adapter
15
+ attr_accessor :stubs
16
+
17
+ class Stubs
18
+ class NotFound < StandardError
19
+ end
20
+
21
+ def initialize
22
+ # {:get => [Stub, Stub]}
23
+ @stack, @consumed = {}, {}
24
+ yield self if block_given?
25
+ end
26
+
27
+ def empty?
28
+ @stack.empty?
29
+ end
30
+
31
+ def match(request_method, path, body)
32
+ return false if !@stack.key?(request_method)
33
+ stack = @stack[request_method]
34
+ consumed = (@consumed[request_method] ||= [])
35
+ path = normalize_path(path)
36
+
37
+ if stub = matches?(stack, path, body)
38
+ consumed << stack.delete(stub)
39
+ stub
40
+ else
41
+ matches?(consumed, path, body)
42
+ end
43
+ end
44
+
45
+ def get(path, &block)
46
+ new_stub(:get, path, &block)
47
+ end
48
+
49
+ def head(path, &block)
50
+ new_stub(:head, path, &block)
51
+ end
52
+
53
+ def post(path, body=nil, &block)
54
+ new_stub(:post, path, body, &block)
55
+ end
56
+
57
+ def put(path, body=nil, &block)
58
+ new_stub(:put, path, body, &block)
59
+ end
60
+
61
+ def patch(path, body=nil, &block)
62
+ new_stub(:patch, path, body, &block)
63
+ end
64
+
65
+ def delete(path, &block)
66
+ new_stub(:delete, path, &block)
67
+ end
68
+
69
+ def options(path, &block)
70
+ new_stub(:options, path, &block)
71
+ end
72
+
73
+ # Raises an error if any of the stubbed calls have not been made.
74
+ def verify_stubbed_calls
75
+ failed_stubs = []
76
+ @stack.each do |method, stubs|
77
+ unless stubs.size == 0
78
+ failed_stubs.concat(stubs.map {|stub|
79
+ "Expected #{method} #{stub}."
80
+ })
81
+ end
82
+ end
83
+ raise failed_stubs.join(" ") unless failed_stubs.size == 0
84
+ end
85
+
86
+ protected
87
+
88
+ def new_stub(request_method, path, body=nil, &block)
89
+ (@stack[request_method] ||= []) << Stub.new(normalize_path(path), body, block)
90
+ end
91
+
92
+ def matches?(stack, path, body)
93
+ stack.detect { |stub| stub.matches?(path, body) }
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
103
+ end
104
+
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
128
+ end
129
+
130
+ def to_s
131
+ "#{path} #{body}"
132
+ end
133
+ end
134
+
135
+ def initialize(app, stubs=nil, &block)
136
+ super(app)
137
+ @stubs = stubs || Stubs.new
138
+ configure(&block) if block
139
+ end
140
+
141
+ def configure
142
+ yield stubs
143
+ end
144
+
145
+ def call(env)
146
+ super
147
+ normalized_path = Faraday::Utils.normalize_path(env[:url])
148
+
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
+ {}
153
+ status, headers, body = stub.block.call(env)
154
+ save_response(env, status, body, headers)
155
+ else
156
+ raise Stubs::NotFound, "no stubbed request for #{env[:method]} #{normalized_path} #{env[:body]}"
157
+ end
158
+ @app.call(env)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,107 @@
1
+ module Faraday
2
+ class Adapter
3
+ class Typhoeus < Faraday::Adapter
4
+ self.supports_parallel = true
5
+
6
+ def self.setup_parallel_manager(options = {})
7
+ options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
8
+ end
9
+
10
+ dependency 'typhoeus'
11
+
12
+ def call(env)
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
27
+
28
+ # TODO: support streaming requests
29
+ def read_body(env)
30
+ env[:body] = env[:body].read if env[:body].respond_to? :read
31
+ end
32
+
33
+ def request(env)
34
+ req = ::Typhoeus::Request.new env[:url].to_s,
35
+ :method => env[:method],
36
+ :body => env[:body],
37
+ :headers => env[:request_headers],
38
+ :disable_ssl_peer_verification => (env[:ssl] && !env[:ssl].fetch(:verify, true))
39
+
40
+ configure_ssl req, env
41
+ configure_proxy req, env
42
+ configure_timeout req, env
43
+ configure_socket req, env
44
+
45
+ req.on_complete do |resp|
46
+ if resp.timed_out?
47
+ if parallel?(env)
48
+ # TODO: error callback in async mode
49
+ else
50
+ raise Faraday::Error::TimeoutError, "request timed out"
51
+ end
52
+ end
53
+
54
+ save_response(env, resp.code, resp.body) do |response_headers|
55
+ response_headers.parse resp.headers
56
+ end
57
+ # in async mode, :response is initialized at this point
58
+ env[:response].finish(env) if parallel?(env)
59
+ end
60
+
61
+ req
62
+ end
63
+
64
+ def configure_ssl(req, env)
65
+ ssl = env[:ssl]
66
+
67
+ req.ssl_version = ssl[:version] if ssl[:version]
68
+ req.ssl_cert = ssl[:client_cert_file] if ssl[:client_cert_file]
69
+ req.ssl_key = ssl[:client_key_file] if ssl[:client_key_file]
70
+ req.ssl_cacert = ssl[:ca_file] if ssl[:ca_file]
71
+ req.ssl_capath = ssl[:ca_path] if ssl[:ca_path]
72
+ end
73
+
74
+ def configure_proxy(req, env)
75
+ proxy = request_options(env)[:proxy]
76
+ return unless proxy
77
+
78
+ req.proxy = "#{proxy[:uri].host}:#{proxy[:uri].port}"
79
+
80
+ if proxy[:username] && proxy[:password]
81
+ req.proxy_username = proxy[:username]
82
+ req.proxy_password = proxy[:password]
83
+ end
84
+ end
85
+
86
+ def configure_timeout(req, env)
87
+ env_req = request_options(env)
88
+ req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
89
+ req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
90
+ end
91
+
92
+ def configure_socket(req, env)
93
+ if bind = request_options(env)[:bind]
94
+ req.interface = bind[:host]
95
+ end
96
+ end
97
+
98
+ def request_options(env)
99
+ env[:request]
100
+ end
101
+
102
+ def parallel?(env)
103
+ !!env[:parallel_manager]
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,184 @@
1
+ module Faraday
2
+ # Possibly going to extend this a bit.
3
+ #
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
9
+ attr_accessor :handlers
10
+
11
+ # Error raised when trying to modify the stack after calling `lock!`
12
+ class StackLocked < RuntimeError; end
13
+
14
+ # borrowed from ActiveSupport::Dependencies::Reference &
15
+ # ActionDispatch::MiddlewareStack::Middleware
16
+ class Handler
17
+ @@constants = Hash.new { |h, k|
18
+ h[k] = k.respond_to?(:constantize) ? k.constantize : Object.const_get(k)
19
+ }
20
+
21
+ attr_reader :name, :args
22
+
23
+ def initialize(klass, *args, &block)
24
+ @name = klass.to_s
25
+ @@constants[@name] = klass if klass.respond_to?(:name)
26
+ @args, @block = args, block
27
+ end
28
+
29
+ def klass() @@constants[@name] end
30
+ def inspect() @name end
31
+
32
+ def ==(other)
33
+ if other.is_a? Handler
34
+ self.name == other.name
35
+ elsif other.respond_to? :name
36
+ klass == other
37
+ else
38
+ @name == other.to_s
39
+ end
40
+ end
41
+
42
+ def build(app)
43
+ klass.new(app, *@args, &@block)
44
+ end
45
+
46
+ def adapter?
47
+ klass.respond_to?(:adapter?) && klass.adapter?
48
+ end
49
+ end
50
+
51
+ def initialize(handlers = [])
52
+ @handlers = handlers
53
+ if block_given?
54
+ build(&Proc.new)
55
+ elsif @handlers.empty?
56
+ # default stack, if nothing else is configured
57
+ self.request :url_encoded
58
+ self.adapter Faraday.default_adapter
59
+ end
60
+ end
61
+
62
+ def build(options = {})
63
+ raise_if_locked
64
+ clear unless options[:keep]
65
+ yield self if block_given?
66
+ end
67
+
68
+ def clear
69
+ @handlers.clear
70
+ end
71
+
72
+ def [](idx)
73
+ @handlers[idx]
74
+ end
75
+
76
+ def ==(other)
77
+ other.is_a?(self.class) && @handlers == other.handlers
78
+ end
79
+
80
+ def dup
81
+ self.class.new(@handlers.dup)
82
+ end
83
+
84
+ def to_app(inner_app)
85
+ # last added handler is the deepest and thus closest to the inner app
86
+ @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) }
87
+ end
88
+
89
+ # Locks the middleware stack to ensure no further modifications are possible.
90
+ def lock!
91
+ @handlers.freeze
92
+ end
93
+
94
+ def locked?
95
+ @handlers.frozen?
96
+ end
97
+
98
+ def use(klass, *args, &block)
99
+ if klass.is_a? Symbol
100
+ use_symbol(Faraday::Middleware, klass, *args, &block)
101
+ else
102
+ raise_if_locked
103
+ @handlers << self.class::Handler.new(klass, *args, &block)
104
+ end
105
+ end
106
+
107
+ def request(key, *args, &block)
108
+ use_symbol(Faraday::Request, key, *args, &block)
109
+ end
110
+
111
+ def response(key, *args, &block)
112
+ use_symbol(Faraday::Response, key, *args, &block)
113
+ end
114
+
115
+ def adapter(key=nil, *args, &block)
116
+ if [key, *args, block].none?
117
+ find_adapter
118
+ else
119
+ use_symbol(Faraday::Adapter, key, *args, &block)
120
+ end
121
+ end
122
+
123
+ def has_adapter?
124
+ !!find_adapter
125
+ end
126
+
127
+ ## methods to push onto the various positions in the stack:
128
+
129
+ def insert(index, *args, &block)
130
+ raise_if_locked
131
+ index = assert_index(index)
132
+ handler = self.class::Handler.new(*args, &block)
133
+ @handlers.insert(index, handler)
134
+ end
135
+
136
+ alias_method :insert_before, :insert
137
+
138
+ def insert_after(index, *args, &block)
139
+ index = assert_index(index)
140
+ insert(index + 1, *args, &block)
141
+ end
142
+
143
+ def swap(index, *args, &block)
144
+ raise_if_locked
145
+ index = assert_index(index)
146
+ @handlers.delete_at(index)
147
+ insert(index, *args, &block)
148
+ end
149
+
150
+ def delete(handler)
151
+ raise_if_locked
152
+ @handlers.delete(handler)
153
+ end
154
+
155
+ def adapter=(adapter_args)
156
+ clear_adapters
157
+ adapter(*adapter_args)
158
+ end
159
+
160
+ private
161
+
162
+ def raise_if_locked
163
+ raise StackLocked, "can't modify middleware stack after making a request" if locked?
164
+ end
165
+
166
+ def use_symbol(mod, key, *args, &block)
167
+ use(mod.lookup_middleware(key), *args, &block)
168
+ end
169
+
170
+ def assert_index(index)
171
+ idx = index.is_a?(Integer) ? index : @handlers.index(index)
172
+ raise "No such handler: #{index.inspect}" unless idx
173
+ idx
174
+ end
175
+
176
+ def find_adapter
177
+ @handlers.detect{|h| h.adapter?}
178
+ end
179
+
180
+ def clear_adapters
181
+ @handlers.delete_if{|h| h.adapter?}
182
+ end
183
+ end
184
+ end