shin-faraday 0.4.7

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.
@@ -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