avdi-faraday 0.8.1

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 (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