/
connection.rb
320 lines (275 loc) · 9.88 KB
/
connection.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
require 'cgi'
require 'set'
require 'forwardable'
require 'uri'
Faraday.require_libs 'builder', 'request', 'response', 'utils'
module Faraday
class Connection
METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
METHODS_WITH_BODIES = Set.new [:post, :put, :patch, :options]
attr_reader :params, :headers, :url_prefix, :builder, :options, :ssl, :parallel_manager
attr_writer :default_parallel_manager
# Public: Initializes a new Faraday::Connection.
#
# url - URI or String base URL to use as a prefix for all
# requests (optional).
# options - Hash of settings that will be applied to every request made
# from this Connection (default: {}).
# :url - URI or String base URL (default: "http:/").
# :params - Hash of URI query unencoded key/value pairs.
# :headers - Hash of unencoded HTTP header key/value pairs.
# :request - Hash of request options.
# :ssl - Hash of SSL options.
# :proxy - URI, String or Hash of HTTP proxy options
# (default: "http_proxy" environment variable).
# :uri - URI or String
# :user - String (optional)
# :password - String (optional)
def initialize(url = nil, options = {})
if url.is_a?(Hash)
options = url
url = options[:url]
end
@headers = Utils::Headers.new
@params = Utils::ParamsHash.new
@options = options[:request] || {}
@ssl = options[:ssl] || {}
@parallel_manager = nil
@default_parallel_manager = options[:parallel_manager]
@builder = options[:builder] || begin
# pass an empty block to Builder so it doesn't assume default middleware
block = block_given?? Proc.new {|b| } : nil
Builder.new(&block)
end
self.url_prefix = url || 'http:/'
@params.update options[:params] if options[:params]
@headers.update options[:headers] if options[:headers]
@proxy = nil
proxy(options.fetch(:proxy) {
uri = ENV['http_proxy']
if uri && !uri.empty?
uri = 'http://' + uri if uri !~ /^http/i
uri
end
})
yield self if block_given?
@headers[:user_agent] ||= "Faraday v#{VERSION}"
end
# Public: Replace default query parameters.
def params=(hash)
@params.replace hash
end
# Public: Replace default request headers.
def headers=(hash)
@headers.replace hash
end
extend Forwardable
def_delegators :builder, :build, :use, :request, :response, :adapter
# The "rack app" wrapped in middleware. All requests are sent here.
#
# The builder is responsible for creating the app object. After this,
# the builder gets locked to ensure no further modifications are made
# to the middleware stack.
#
# Returns an object that responds to `call` and returns a Response.
def app
@app ||= begin
builder.lock!
builder.to_app(lambda { |env|
# the inner app that creates and returns the Response object
response = Response.new
response.finish(env) unless env[:parallel_manager]
env[:response] = response
})
end
end
# get/head/delete(url, params, headers)
%w[get head delete].each do |method|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(url = nil, params = nil, headers = nil)
run_request(:#{method}, url, nil, headers) { |request|
request.params.update(params) if params
yield request if block_given?
}
end
RUBY
end
# post/put/patch(url, body, headers)
%w[post put patch].each do |method|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(url = nil, body = nil, headers = nil, &block)
run_request(:#{method}, url, body, headers, &block)
end
RUBY
end
def basic_auth(login, pass)
headers[Faraday::Request::Authorization::KEY] =
Faraday::Request::BasicAuthentication.header(login, pass)
end
def token_auth(token, options = nil)
headers[Faraday::Request::Authorization::KEY] =
Faraday::Request::TokenAuthentication.header(token, options)
end
def authorization(type, token)
headers[Faraday::Request::Authorization::KEY] =
Faraday::Request::Authorization.header(type, token)
end
# Internal: Traverse the middleware stack in search of a
# parallel-capable adapter.
#
# Yields in case of not found.
#
# Returns a parallel manager or nil if not found.
def default_parallel_manager
@default_parallel_manager ||= begin
handler = @builder.handlers.detect do |h|
h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
end
if handler then handler.klass.setup_parallel_manager
elsif block_given? then yield
end
end
end
def in_parallel?
!!@parallel_manager
end
def in_parallel(manager = nil)
@parallel_manager = manager || default_parallel_manager {
warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack"
warn caller[2,10].join("\n")
nil
}
yield
@parallel_manager && @parallel_manager.run
ensure
@parallel_manager = nil
end
def proxy(arg = nil)
return @proxy if arg.nil?
@proxy = if arg.is_a? Hash
uri = self.class.URI arg.fetch(:uri) { raise ArgumentError, "missing :uri" }
arg.merge :uri => uri
else
uri = self.class.URI(arg)
{:uri => uri}
end
with_uri_credentials(uri) do |user, password|
@proxy[:user] ||= user
@proxy[:password] ||= password
end
@proxy
end
# normalize URI() behavior across Ruby versions
def self.URI(url)
if url.respond_to?(:host)
url
elsif url.respond_to?(:to_str)
Kernel.URI(url)
else
raise ArgumentError, "bad argument (expected URI object or URI string)"
end
end
def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
def_delegator :url_prefix, :path, :path_prefix
# Parses the giving url with URI and stores the individual
# components in this connection. These components serve as defaults for
# requests made by this connection.
#
# conn = Faraday::Connection.new { ... }
# conn.url_prefix = "https://sushi.com/api"
# conn.scheme # => https
# conn.path_prefix # => "/api"
#
# conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
#
def url_prefix=(url)
uri = @url_prefix = self.class.URI(url)
self.path_prefix = uri.path
params.merge_query(uri.query)
uri.query = nil
with_uri_credentials(uri) do |user, password|
basic_auth user, password
uri.user = uri.password = nil
end
uri
end
# Ensures that the path prefix always has a leading but no trailing slash
def path_prefix=(value)
url_prefix.path = if value
value = value.chomp '/'
value = '/' + value unless value[0,1] == '/'
value
end
end
def run_request(method, url, body, headers)
if !METHODS.include?(method)
raise ArgumentError, "unknown http method: #{method}"
end
request = build_request(method) do |req|
req.url(url) if url
req.headers.update(headers) if headers
req.body = body if body
yield req if block_given?
end
env = request.to_env(self)
self.app.call(env)
end
# Internal: Creates and configures the request object.
#
# Returns the new Request.
def build_request(method)
Request.create(method) do |req|
req.params = self.params.dup
req.headers = self.headers.dup
req.options = self.options.merge(:proxy => self.proxy)
yield req if block_given?
end
end
# Takes a relative url for a request and combines it with the defaults
# set on the connection instance.
#
# conn = Faraday::Connection.new { ... }
# conn.url_prefix = "https://sushi.com/api?token=abc"
# conn.scheme # => https
# conn.path_prefix # => "/api"
#
# conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
# conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
#
def build_url(url, extra_params = nil)
uri = build_exclusive_url(url)
query_values = self.params.dup.merge_query(uri.query)
query_values.update extra_params if extra_params
uri.query = query_values.empty? ? nil : query_values.to_query
uri
end
# Internal: Build an absolute URL based on url_prefix.
#
# url - A String or URI-like object
# params - A Faraday::Utils::ParamsHash to replace the query values
# of the resulting url (default: nil).
#
# Returns the resulting URI instance.
def build_exclusive_url(url, params = nil)
url = nil if url.respond_to?(:empty?) and url.empty?
base = url_prefix
if url and base.path and base.path !~ /\/$/
base = base.dup
base.path = base.path + '/' # ensure trailing slash
end
uri = url ? base + url : base
uri.query = params.to_query if params
uri.query = nil if uri.query and uri.query.empty?
uri
end
def dup
self.class.new(build_url(''), :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
end
# Internal: Yields username and password extracted from a URI if they both exist.
def with_uri_credentials(uri)
if uri.user and uri.password
yield Utils.unescape(uri.user), Utils.unescape(uri.password)
end
end
end
end