shin-faraday 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ module Faraday
2
+ module Adapter
3
+ class Patron < Middleware
4
+ begin
5
+ require 'patron'
6
+ rescue LoadError, NameError => e
7
+ self.load_error = e
8
+ end
9
+
10
+ def call(env)
11
+ process_body_for_request(env)
12
+
13
+ sess = ::Patron::Session.new
14
+ args = [env[:method], env[:url].to_s, env[:request_headers]]
15
+ if Faraday::Connection::METHODS_WITH_BODIES.include?(env[:method])
16
+ args.insert(2, env[:body].to_s)
17
+ end
18
+ resp = sess.send *args
19
+
20
+ env.update \
21
+ :status => resp.status,
22
+ :response_headers => resp.headers.
23
+ inject({}) { |memo, (k, v)| memo.update(k.downcase => v) },
24
+ :body => resp.body
25
+ env[:response].finish(env)
26
+
27
+ @app.call env
28
+ rescue Errno::ECONNREFUSED
29
+ raise Error::ConnectionFailed, "connection refused"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,117 @@
1
+ module Faraday
2
+ module 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 < Middleware
15
+ attr_accessor :stubs
16
+
17
+ def self.loaded?() false end
18
+
19
+ class Stubs
20
+ def initialize
21
+ # {:get => [Stub, Stub]}
22
+ @stack = {}
23
+ yield self if block_given?
24
+ end
25
+
26
+ def empty?
27
+ @stack.empty?
28
+ end
29
+
30
+ def match(request_method, path, body)
31
+ return false if !@stack.key?(request_method)
32
+ stub = @stack[request_method].detect { |stub| stub.matches?(path, body) }
33
+ @stack[request_method].delete stub
34
+ end
35
+
36
+ def get(path, &block)
37
+ new_stub(:get, path, &block)
38
+ end
39
+
40
+ def head(path, &block)
41
+ new_stub(:head, path, &block)
42
+ end
43
+
44
+ def post(path, body=nil, &block)
45
+ new_stub(:post, path, body, &block)
46
+ end
47
+
48
+ def put(path, body=nil, &block)
49
+ new_stub(:put, path, body, &block)
50
+ end
51
+
52
+ def delete(path, &block)
53
+ new_stub(:delete, path, &block)
54
+ end
55
+
56
+ def new_stub(request_method, path, body=nil, &block)
57
+ (@stack[request_method] ||= []) << Stub.new(path, body, block)
58
+ end
59
+
60
+ # Raises an error if any of the stubbed calls have not been made.
61
+ def verify_stubbed_calls
62
+ failed_stubs = []
63
+ @stack.each do |method, stubs|
64
+ unless stubs.size == 0
65
+ failed_stubs.concat(stubs.map {|stub|
66
+ "Expected #{method} #{stub}."
67
+ })
68
+ end
69
+ end
70
+ raise failed_stubs.join(" ") unless failed_stubs.size == 0
71
+ end
72
+ end
73
+
74
+ class Stub < Struct.new(:path, :body, :block)
75
+ def matches?(request_path, request_body)
76
+ request_path == path && (body.to_s.size.zero? || request_body == body)
77
+ end
78
+
79
+ def to_s
80
+ "#{path} #{body}"
81
+ end
82
+ end
83
+
84
+ def initialize(app, stubs=nil, &block)
85
+ super(app)
86
+ @stubs = stubs || Stubs.new
87
+ configure(&block) if block
88
+ end
89
+
90
+ def configure
91
+ yield stubs
92
+ end
93
+
94
+ def request_uri(url)
95
+ (url.path != "" ? url.path : "/") +
96
+ (url.query ? "?#{sort_query_params(url.query)}" : "")
97
+ end
98
+
99
+ def sort_query_params(query)
100
+ query.split('&').sort.join('&')
101
+ end
102
+
103
+ def call(env)
104
+ if stub = stubs.match(env[:method], request_uri(env[:url]), env[:body])
105
+ status, headers, body = stub.block.call(env)
106
+ env.update \
107
+ :status => status,
108
+ :response_headers => headers,
109
+ :body => body
110
+ else
111
+ raise "no stubbed request for #{env[:method]} #{request_uri(env[:url])} #{env[:body]}"
112
+ end
113
+ @app.call(env)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,65 @@
1
+ module Faraday
2
+ module Adapter
3
+ class Typhoeus < Middleware
4
+ self.supports_parallel_requests = true
5
+
6
+ def self.setup_parallel_manager(options = {})
7
+ options.empty? ? ::Typhoeus::Hydra.hydra : ::Typhoeus::Hydra.new(options)
8
+ end
9
+
10
+ begin
11
+ require 'typhoeus'
12
+ rescue LoadError, NameError => e
13
+ self.load_error = e
14
+ end
15
+
16
+ def call(env)
17
+ process_body_for_request(env)
18
+
19
+ hydra = env[:parallel_manager] || self.class.setup_parallel_manager
20
+ req = ::Typhoeus::Request.new env[:url].to_s,
21
+ :method => env[:method],
22
+ :body => env[:body],
23
+ :headers => env[:request_headers],
24
+ :disable_ssl_peer_verification => (env[:ssl][:verify] == false)
25
+
26
+ env_req = env[:request]
27
+ req.timeout = req.connect_timeout = (env_req[:timeout] * 1000) if env_req[:timeout]
28
+ req.connect_timeout = (env_req[:open_timeout] * 1000) if env_req[:open_timeout]
29
+
30
+ req.on_complete do |resp|
31
+ env.update \
32
+ :status => resp.code,
33
+ :response_headers => parse_response_headers(resp.headers),
34
+ :body => resp.body
35
+ env[:response].finish(env)
36
+ end
37
+
38
+ hydra.queue req
39
+
40
+ if !env[:parallel_manager]
41
+ hydra.run
42
+ end
43
+
44
+ @app.call env
45
+ rescue Errno::ECONNREFUSED
46
+ raise Error::ConnectionFailed, "connection refused"
47
+ end
48
+
49
+ def in_parallel(options = {})
50
+ @hydra = ::Typhoeus::Hydra.new(options)
51
+ yield
52
+ @hydra.run
53
+ @hydra = nil
54
+ end
55
+
56
+ def parse_response_headers(header_string)
57
+ return {} unless header_string && !header_string.empty?
58
+ Hash[*header_string.split(/\r\n/).
59
+ tap { |a| a.shift }. # drop the HTTP status line
60
+ map! { |h| h.split(/:\s+/,2) }. # split key and value
61
+ map! { |(k, v)| [k.downcase, v] }.flatten!]
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,82 @@
1
+ module Faraday
2
+ # Possibly going to extend this a bit.
3
+ #
4
+ # Faraday::Connection.new(:url => 'http://sushi.com') do |b|
5
+ # b.request :yajl # Faraday::Request::Yajl
6
+ # b.adapter :logger # Faraday::Adapter::Logger
7
+ # b.response :yajl # Faraday::Response::Yajl
8
+ # end
9
+ class Builder
10
+ attr_accessor :handlers
11
+
12
+ def self.create(&block)
13
+ Builder.new(&block)
14
+ end
15
+
16
+ def self.inner_app
17
+ lambda do |env|
18
+ env[:parallel_manager] ? env[:response] : env[:response].finish(env)
19
+ end
20
+ end
21
+
22
+ def initialize(handlers = [], &block)
23
+ @handlers = handlers
24
+ build(&block) if block_given?
25
+ end
26
+
27
+ def build(options = {}, &block)
28
+ inner = @handlers.shift
29
+ if !options[:keep]
30
+ @handlers.clear
31
+ end
32
+ block.call(self)
33
+ run(inner || self.class.inner_app)
34
+ end
35
+
36
+ def [](index)
37
+ # @handlers are stored in reverse order
38
+ @handlers[-(index+1)]
39
+ end
40
+
41
+ def run(app)
42
+ @handlers.unshift app
43
+ end
44
+
45
+ def to_app
46
+ if @handlers.empty?
47
+ build { |b| b.adapter Faraday.default_adapter }
48
+ end
49
+
50
+ inner_app = @handlers.first
51
+ @handlers[1..-1].inject(inner_app) { |app, middleware| middleware.call(app) }
52
+ end
53
+
54
+ def use(klass, *args, &block)
55
+ run(lambda { |app| klass.new(app, *args, &block) })
56
+ end
57
+
58
+ def request(key, *args, &block)
59
+ use_symbol(Faraday::Request, key, *args, &block)
60
+ end
61
+
62
+ def response(key, *args, &block)
63
+ use_symbol(Faraday::Response, key, *args, &block)
64
+ end
65
+
66
+ def adapter(key, *args, &block)
67
+ use_symbol(Faraday::Adapter, key, *args, &block)
68
+ end
69
+
70
+ def use_symbol(mod, key, *args, &block)
71
+ use(mod.lookup_module(key), *args, &block)
72
+ end
73
+
74
+ def ==(other)
75
+ other.is_a?(self.class) && @handlers == other.handlers
76
+ end
77
+
78
+ def dup
79
+ self.class.new(@handlers.dup)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,263 @@
1
+ require 'addressable/uri'
2
+ require 'set'
3
+ require 'base64'
4
+
5
+ module Faraday
6
+ class Connection
7
+ include Addressable, Rack::Utils
8
+
9
+ HEADERS = Hash.new do |h, k|
10
+ if k.respond_to?(:to_str)
11
+ k
12
+ else
13
+ k.to_s.split('_'). # :user_agent => %w(user agent)
14
+ each { |w| w.capitalize! }. # => %w(User Agent)
15
+ join('-') # => "User-Agent"
16
+ end
17
+ end
18
+ HEADERS.update \
19
+ :etag => "ETag"
20
+ HEADERS.values.each { |v| v.freeze }
21
+
22
+ METHODS = Set.new [:get, :post, :put, :delete, :head]
23
+ METHODS_WITH_BODIES = Set.new [:post, :put]
24
+
25
+ attr_accessor :host, :port, :scheme, :params, :headers, :parallel_manager
26
+ attr_reader :path_prefix, :builder, :options, :ssl
27
+
28
+ # :url
29
+ # :params
30
+ # :headers
31
+ # :request
32
+ # :ssl
33
+ def initialize(url = nil, options = {}, &block)
34
+ if url.is_a?(Hash)
35
+ options = url
36
+ url = options[:url]
37
+ end
38
+ @headers = HeaderHash.new
39
+ @params = {}
40
+ @options = options[:request] || {}
41
+ @ssl = options[:ssl] || {}
42
+ @parallel_manager = options[:parallel]
43
+ self.url_prefix = url if url
44
+ proxy(options[:proxy])
45
+ merge_params @params, options[:params] if options[:params]
46
+ merge_headers @headers, options[:headers] if options[:headers]
47
+
48
+ if block
49
+ @builder = Builder.new
50
+ @builder.build { block.call(self) }
51
+ else
52
+ @builder = options[:builder] || Builder.new
53
+ end
54
+ end
55
+
56
+ def use(klass, *args, &block)
57
+ @builder.use(klass, *args, &block)
58
+ end
59
+
60
+ def request(key, *args, &block)
61
+ @builder.request(key, *args, &block)
62
+ end
63
+
64
+ def response(key, *args, &block)
65
+ @builder.response(key, *args, &block)
66
+ end
67
+
68
+ def adapter(key, *args, &block)
69
+ @builder.adapter(key, *args, &block)
70
+ end
71
+
72
+ def build(options = {}, &block)
73
+ @builder.build(options, &block)
74
+ end
75
+
76
+ def get(url = nil, headers = nil, &block)
77
+ run_request(:get, url, nil, headers, &block)
78
+ end
79
+
80
+ def post(url = nil, body = nil, headers = nil, &block)
81
+ run_request(:post, url, body, headers, &block)
82
+ end
83
+
84
+ def put(url = nil, body = nil, headers = nil, &block)
85
+ run_request(:put, url, body, headers, &block)
86
+ end
87
+
88
+ def head(url = nil, headers = nil, &block)
89
+ run_request(:head, url, nil, headers, &block)
90
+ end
91
+
92
+ def delete(url = nil, headers = nil, &block)
93
+ run_request(:delete, url, nil, headers, &block)
94
+ end
95
+
96
+ def basic_auth(login, pass)
97
+ @headers['authorization'] = "Basic #{Base64.encode64("#{login}:#{pass}").strip}"
98
+ end
99
+
100
+ def token_auth(token, options = {})
101
+ values = ["token=#{token.to_s.inspect}"]
102
+ options.each do |key, value|
103
+ values << "#{key}=#{value.to_s.inspect}"
104
+ end
105
+ # 21 = "Authorization: Token ".size
106
+ comma = ",\n#{' ' * 21}"
107
+ @headers['authorization'] = "Token #{values * comma}"
108
+ end
109
+
110
+ def in_parallel?
111
+ !!@parallel_manager
112
+ end
113
+
114
+ def in_parallel(manager)
115
+ @parallel_manager = manager
116
+ yield
117
+ @parallel_manager && @parallel_manager.run
118
+ ensure
119
+ @parallel_manager = nil
120
+ end
121
+
122
+ def proxy(arg = nil)
123
+ return @proxy if arg.nil?
124
+
125
+ @proxy =
126
+ case arg
127
+ when String then {:uri => proxy_arg_to_uri(arg)}
128
+ when URI then {:uri => arg}
129
+ when Hash then arg
130
+ if arg[:uri] = proxy_arg_to_uri(arg[:uri])
131
+ arg
132
+ else
133
+ raise ArgumentError, "no :uri option."
134
+ end
135
+ end
136
+ end
137
+
138
+ # Parses the giving url with Addressable::URI and stores the individual
139
+ # components in this connection. These components serve as defaults for
140
+ # requests made by this connection.
141
+ #
142
+ # conn = Faraday::Connection.new { ... }
143
+ # conn.url_prefix = "https://sushi.com/api"
144
+ # conn.scheme # => https
145
+ # conn.path_prefix # => "/api"
146
+ #
147
+ # conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
148
+ #
149
+ def url_prefix=(url)
150
+ uri = URI.parse(url)
151
+ self.scheme = uri.scheme
152
+ self.host = uri.host
153
+ self.port = uri.port
154
+ self.path_prefix = uri.path
155
+ if uri.query && !uri.query.empty?
156
+ merge_params @params, parse_query(uri.query)
157
+ end
158
+ if uri.user && uri.password
159
+ basic_auth(uri.user, uri.password)
160
+ end
161
+ end
162
+
163
+ # Ensures that the path prefix always has a leading / and no trailing /
164
+ def path_prefix=(value)
165
+ if value
166
+ value.chomp! "/"
167
+ value.replace "/#{value}" if value !~ /^\//
168
+ end
169
+ @path_prefix = value
170
+ end
171
+
172
+ # return the assembled Rack application for this instance.
173
+ def to_app
174
+ @builder.to_app
175
+ end
176
+
177
+ def run_request(method, url, body, headers)
178
+ if !METHODS.include?(method)
179
+ raise ArgumentError, "unknown http method: #{method}"
180
+ end
181
+
182
+ Request.run(self, method) do |req|
183
+ req.url(url) if url
184
+ req.headers.update(headers) if headers
185
+ req.body = body if body
186
+ yield req if block_given?
187
+ end
188
+ end
189
+
190
+ # Takes a relative url for a request and combines it with the defaults
191
+ # set on the connection instance.
192
+ #
193
+ # conn = Faraday::Connection.new { ... }
194
+ # conn.url_prefix = "https://sushi.com/api?token=abc"
195
+ # conn.scheme # => https
196
+ # conn.path_prefix # => "/api"
197
+ #
198
+ # conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
199
+ # conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
200
+ #
201
+ def build_url(url, params = nil)
202
+ uri = URI.parse(url.to_s)
203
+ if @path_prefix && uri.path !~ /^\//
204
+ uri.path = "#{@path_prefix.size > 1 ? @path_prefix : nil}/#{uri.path}"
205
+ end
206
+ uri.host ||= @host
207
+ uri.port ||= @port
208
+ uri.scheme ||= @scheme
209
+ replace_query(uri, params)
210
+ uri
211
+ end
212
+
213
+ def dup
214
+ self.class.new(build_url(''), :headers => headers.dup, :params => params.dup, :builder => builder.dup)
215
+ end
216
+
217
+ def replace_query(uri, params)
218
+ url_params = @params.dup
219
+ if uri.query && !uri.query.empty?
220
+ merge_params(url_params, parse_query(uri.query))
221
+ end
222
+ if params && !params.empty?
223
+ merge_params(url_params, params)
224
+ end
225
+ uri.query = url_params.empty? ? nil : build_query(url_params)
226
+ uri
227
+ end
228
+
229
+ # turns param keys into strings
230
+ def merge_params(existing_params, new_params)
231
+ new_params.each do |key, value|
232
+ existing_params[key.to_s] = value
233
+ end
234
+ end
235
+
236
+ # turns headers keys and values into strings. Look up symbol keys in the
237
+ # the HEADERS hash.
238
+ #
239
+ # h = merge_headers(HeaderHash.new, :content_type => 'text/plain')
240
+ # h['Content-Type'] # = 'text/plain'
241
+ #
242
+ def merge_headers(existing_headers, new_headers)
243
+ new_headers.each do |key, value|
244
+ existing_headers[HEADERS[key]] = value.to_s
245
+ end
246
+ end
247
+
248
+ # Be sure to URI escape '+' symbols to %2B. Otherwise, they get interpreted
249
+ # as spaces.
250
+ def escape(s)
251
+ s.to_s.gsub(/([^a-zA-Z0-9_.-]+)/n) do
252
+ '%' << $1.unpack('H2'*bytesize($1)).join('%').tap { |c| c.upcase! }
253
+ end
254
+ end
255
+
256
+ def proxy_arg_to_uri(arg)
257
+ case arg
258
+ when String then URI.parse(arg)
259
+ when URI then arg
260
+ end
261
+ end
262
+ end
263
+ end