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,468 @@
1
+ require 'cgi'
2
+ require 'set'
3
+ require 'forwardable'
4
+ require 'uri'
5
+
6
+ Faraday.require_libs 'builder', 'request', 'response', 'utils'
7
+
8
+ module Faraday
9
+ # Public: Connection objects manage the default properties and the middleware
10
+ # stack for fulfilling an HTTP request.
11
+ #
12
+ # Examples
13
+ #
14
+ # conn = Faraday::Connection.new 'http://sushi.com'
15
+ #
16
+ # # GET http://sushi.com/nigiri
17
+ # conn.get 'nigiri'
18
+ # # => #<Faraday::Response>
19
+ #
20
+ class Connection
21
+ # A Set of allowed HTTP verbs.
22
+ METHODS = Set.new [:get, :post, :put, :delete, :head, :patch, :options]
23
+
24
+ # A Set of HTTP verbs that typically send a body. If no body is set for
25
+ # these requests, the Content-Length header is set to 0.
26
+ METHODS_WITH_BODIES = Set.new [:post, :put, :patch, :options]
27
+
28
+ # Public: Returns a Hash of URI query unencoded key/value pairs.
29
+ attr_reader :params
30
+
31
+ # Public: Returns a Hash of unencoded HTTP header key/value pairs.
32
+ attr_reader :headers
33
+
34
+ # Public: Returns a URI with the prefix used for all requests from this
35
+ # Connection. This includes a default host name, scheme, port, and path.
36
+ attr_reader :url_prefix
37
+
38
+ # Public: Returns the Faraday::Builder for this Connection.
39
+ attr_reader :builder
40
+
41
+ # Public: Returns a Hash of the request options.
42
+ attr_reader :options
43
+
44
+ # Public: Returns a Hash of the SSL options.
45
+ attr_reader :ssl
46
+
47
+ # Public: Returns the parallel manager for this Connection.
48
+ attr_reader :parallel_manager
49
+
50
+ # Public: Sets the default parallel manager for this connection.
51
+ attr_writer :default_parallel_manager
52
+
53
+ # Public: Initializes a new Faraday::Connection.
54
+ #
55
+ # url - The optional String base URL to use as a prefix for all
56
+ # requests. Can also be the options Hash.
57
+ # options - The optional Hash used to configure this Faraday::Connection.
58
+ # Any of these values will be set on every request made, unless
59
+ # overridden for a specific request.
60
+ # :url - String base URL.
61
+ # :params - Hash of URI query unencoded key/value pairs.
62
+ # :headers - Hash of unencoded HTTP header key/value pairs.
63
+ # :request - Hash of request options.
64
+ # :ssl - Hash of SSL options.
65
+ # :proxy - Hash of Proxy options.
66
+ def initialize(url = nil, options = {})
67
+ if url.is_a?(Hash)
68
+ options = url
69
+ url = options[:url]
70
+ end
71
+ @headers = Utils::Headers.new
72
+ @params = Utils::ParamsHash.new
73
+ @options = options[:request] || {}
74
+ @ssl = options[:ssl] || {}
75
+ adapter = options[:adapter]
76
+
77
+ @parallel_manager = nil
78
+ @default_parallel_manager = options[:parallel_manager]
79
+
80
+ @builder = options[:builder] || begin
81
+ # pass an empty block to Builder so it doesn't assume default middleware
82
+ block = block_given?? Proc.new {|b| } : nil
83
+ Builder.new(&block)
84
+ end
85
+
86
+ self.url_prefix = url || 'http:/'
87
+
88
+ @params.update options[:params] if options[:params]
89
+ @headers.update options[:headers] if options[:headers]
90
+
91
+ @proxy = nil
92
+ proxy(options.fetch(:proxy) { ENV['http_proxy'] })
93
+
94
+ yield self if block_given?
95
+
96
+ if adapter
97
+ self.adapter = adapter
98
+ end
99
+ end
100
+
101
+ # Public: Sets the Hash of URI query unencoded key/value pairs.
102
+ def params=(hash)
103
+ @params.replace hash
104
+ end
105
+
106
+ # Public: Sets the Hash of unencoded HTTP header key/value pairs.
107
+ def headers=(hash)
108
+ @headers.replace hash
109
+ end
110
+
111
+ extend Forwardable
112
+
113
+ def_delegators :builder, :build, :use, :request, :response, :adapter, :has_adapter?,
114
+ :adapter=
115
+
116
+ # The "rack app" wrapped in middleware. All requests are sent here.
117
+ #
118
+ # The builder is responsible for creating the app object. After this,
119
+ # the builder gets locked to ensure no further modifications are made
120
+ # to the middleware stack.
121
+ #
122
+ # Returns an object that responds to `call` and returns a Response.
123
+ def app
124
+ @app ||= begin
125
+ builder.lock!
126
+ builder.to_app(lambda { |env|
127
+ # the inner app that creates and returns the Response object
128
+ response = Response.new
129
+ response.finish(env) unless env[:parallel_manager]
130
+ env[:response] = response
131
+ })
132
+ end
133
+ end
134
+
135
+ # Public: Makes an HTTP request without a body.
136
+ #
137
+ # url - The optional String base URL to use as a prefix for all
138
+ # requests. Can also be the options Hash.
139
+ # params - Hash of URI query unencoded key/value pairs.
140
+ # headers - Hash of unencoded HTTP header key/value pairs.
141
+ #
142
+ # Examples
143
+ #
144
+ # conn.get '/items', {:page => 1}, :accept => 'application/json'
145
+ # conn.head '/items/1'
146
+ #
147
+ # # ElasticSearch example sending a body with GET.
148
+ # conn.get '/twitter/tweet/_search' do |req|
149
+ # req.headers[:content_type] = 'application/json'
150
+ # req.params[:routing] = 'kimchy'
151
+ # req.body = JSON.generate(:query => {...})
152
+ # end
153
+ #
154
+ # Yields a Faraday::Response for further request customizations.
155
+ # Returns a Faraday::Response.
156
+ #
157
+ # Signature
158
+ #
159
+ # <verb>(url = nil, params = nil, headers = nil)
160
+ #
161
+ # verb - An HTTP verb: get, head, or delete.
162
+ %w[get head delete].each do |method|
163
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
164
+ def #{method}(url = nil, params = nil, headers = nil)
165
+ run_request(:#{method}, url, nil, headers) { |request|
166
+ request.params.update(params) if params
167
+ yield request if block_given?
168
+ }
169
+ end
170
+ RUBY
171
+ end
172
+
173
+ # Public: Makes an HTTP request with a body.
174
+ #
175
+ # url - The optional String base URL to use as a prefix for all
176
+ # requests. Can also be the options Hash.
177
+ # body - The String body for the request.
178
+ # headers - Hash of unencoded HTTP header key/value pairs.
179
+ #
180
+ # Examples
181
+ #
182
+ # conn.post '/items', data, :content_type => 'application/json'
183
+ #
184
+ # # Simple ElasticSearch indexing sample.
185
+ # conn.post '/twitter/tweet' do |req|
186
+ # req.headers[:content_type] = 'application/json'
187
+ # req.params[:routing] = 'kimchy'
188
+ # req.body = JSON.generate(:user => 'kimchy', ...)
189
+ # end
190
+ #
191
+ # Yields a Faraday::Response for further request customizations.
192
+ # Returns a Faraday::Response.
193
+ #
194
+ # Signature
195
+ #
196
+ # <verb>(url = nil, body = nil, headers = nil)
197
+ #
198
+ # verb - An HTTP verb: post, put, or patch.
199
+ %w[post put patch].each do |method|
200
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
201
+ def #{method}(url = nil, body = nil, headers = nil, &block)
202
+ run_request(:#{method}, url, body, headers, &block)
203
+ end
204
+ RUBY
205
+ end
206
+
207
+ # Public: Sets up the Authorization header with these credentials, encoded
208
+ # with base64.
209
+ #
210
+ # login - The authentication login.
211
+ # pass - The authentication password.
212
+ #
213
+ # Examples
214
+ #
215
+ # conn.basic_auth 'Aladdin', 'open sesame'
216
+ # conn.headers['Authorization']
217
+ # # => "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
218
+ #
219
+ # Returns nothing.
220
+ def basic_auth(login, pass)
221
+ headers[Faraday::Request::Authorization::KEY] =
222
+ Faraday::Request::BasicAuthentication.header(login, pass)
223
+ end
224
+
225
+ # Public: Sets up the Authorization header with the given token.
226
+ #
227
+ # token - The String token.
228
+ # options - Optional Hash of extra token options.
229
+ #
230
+ # Examples
231
+ #
232
+ # conn.token_auth 'abcdef', :foo => 'bar'
233
+ # conn.headers['Authorization']
234
+ # # => "Token token=\"abcdef\",
235
+ # foo=\"bar\""
236
+ #
237
+ # Returns nothing.
238
+ def token_auth(token, options = nil)
239
+ headers[Faraday::Request::Authorization::KEY] =
240
+ Faraday::Request::TokenAuthentication.header(token, options)
241
+ end
242
+
243
+ # Public: Sets up a custom Authorization header.
244
+ #
245
+ # type - The String authorization type.
246
+ # token - The String or Hash token. A String value is taken literally, and
247
+ # a Hash is encoded into comma separated key/value pairs.
248
+ #
249
+ # Examples
250
+ #
251
+ # conn.authorization :Bearer, 'mF_9.B5f-4.1JqM'
252
+ # conn.headers['Authorization']
253
+ # # => "Bearer mF_9.B5f-4.1JqM"
254
+ #
255
+ # conn.authorization :Token, :token => 'abcdef', :foo => 'bar'
256
+ # conn.headers['Authorization']
257
+ # # => "Token token=\"abcdef\",
258
+ # foo=\"bar\""
259
+ #
260
+ # Returns nothing.
261
+ def authorization(type, token)
262
+ headers[Faraday::Request::Authorization::KEY] =
263
+ Faraday::Request::Authorization.header(type, token)
264
+ end
265
+
266
+ # Internal: Traverse the middleware stack in search of a
267
+ # parallel-capable adapter.
268
+ #
269
+ # Yields in case of not found.
270
+ #
271
+ # Returns a parallel manager or nil if not found.
272
+ def default_parallel_manager
273
+ @default_parallel_manager ||= begin
274
+ handler = @builder.handlers.detect do |h|
275
+ h.klass.respond_to?(:supports_parallel?) and h.klass.supports_parallel?
276
+ end
277
+
278
+ if handler then handler.klass.setup_parallel_manager
279
+ elsif block_given? then yield
280
+ end
281
+ end
282
+ end
283
+
284
+ # Public: Determine if this Faraday::Connection can make parallel requests.
285
+ #
286
+ # Returns true or false.
287
+ def in_parallel?
288
+ !!@parallel_manager
289
+ end
290
+
291
+ # Public: Sets up the parallel manager to make a set of requests.
292
+ #
293
+ # manager - The parallel manager that this Connection's Adapter uses.
294
+ #
295
+ # Yields a block to execute multiple requests.
296
+ # Returns nothing.
297
+ def in_parallel(manager = nil)
298
+ @parallel_manager = manager || default_parallel_manager {
299
+ warn "Warning: `in_parallel` called but no parallel-capable adapter on Faraday stack"
300
+ warn caller[2,10].join("\n")
301
+ nil
302
+ }
303
+ yield
304
+ @parallel_manager && @parallel_manager.run
305
+ ensure
306
+ @parallel_manager = nil
307
+ end
308
+
309
+ # Public: Gets or Sets the Hash proxy options.
310
+ def proxy(arg = nil)
311
+ return @proxy if arg.nil?
312
+
313
+ @proxy = if arg.is_a? Hash
314
+ uri = arg.fetch(:uri) { raise ArgumentError, "no :uri option" }
315
+ arg.merge :uri => self.class.URI(uri)
316
+ else
317
+ {:uri => self.class.URI(arg)}
318
+ end
319
+ end
320
+
321
+ # Public: Normalize URI() behavior across Ruby versions
322
+ #
323
+ # url - A String or URI.
324
+ #
325
+ # Returns a parsed URI.
326
+ def self.URI(url)
327
+ if url.respond_to?(:host)
328
+ url
329
+ elsif url.respond_to?(:to_str)
330
+ Kernel.URI(url)
331
+ else
332
+ raise ArgumentError, "bad argument (expected URI object or URI string)"
333
+ end
334
+ end
335
+
336
+ def_delegators :url_prefix, :scheme, :scheme=, :host, :host=, :port, :port=
337
+ def_delegator :url_prefix, :path, :path_prefix
338
+
339
+ # Public: Parses the giving url with URI and stores the individual
340
+ # components in this connection. These components serve as defaults for
341
+ # requests made by this connection.
342
+ #
343
+ # url - A String or URI.
344
+ #
345
+ # Examples
346
+ #
347
+ # conn = Faraday::Connection.new { ... }
348
+ # conn.url_prefix = "https://sushi.com/api"
349
+ # conn.scheme # => https
350
+ # conn.path_prefix # => "/api"
351
+ #
352
+ # conn.get("nigiri?page=2") # accesses https://sushi.com/api/nigiri
353
+ #
354
+ # Returns the parsed URI from teh given input..
355
+ def url_prefix=(url)
356
+ uri = @url_prefix = self.class.URI(url)
357
+ self.path_prefix = uri.path
358
+
359
+ params.merge_query(uri.query)
360
+ uri.query = nil
361
+
362
+ if uri.user && uri.password
363
+ basic_auth(Utils.unescape(uri.user), Utils.unescape(uri.password))
364
+ uri.user = uri.password = nil
365
+ end
366
+
367
+ uri
368
+ end
369
+
370
+ # Public: Sets the path prefix and ensures that it always has a leading
371
+ # but no trailing slash.
372
+ #
373
+ # value - A String.
374
+ #
375
+ # Returns the new String path prefix.
376
+ def path_prefix=(value)
377
+ url_prefix.path = if value
378
+ value = value.chomp '/'
379
+ value = '/' + value unless value[0,1] == '/'
380
+ value
381
+ end
382
+ end
383
+
384
+ # Builds and runs the Faraday::Request.
385
+ #
386
+ # method - The Symbol HTTP method.
387
+ # url - The String or URI to access.
388
+ # body - The String body
389
+ # headers - Hash of unencoded HTTP header key/value pairs.
390
+ #
391
+ # Returns a Faraday::Response.
392
+ def run_request(method, url, body, headers)
393
+ if !METHODS.include?(method)
394
+ raise ArgumentError, "unknown http method: #{method}"
395
+ end
396
+
397
+ request = build_request(method) do |req|
398
+ req.url(url) if url
399
+ req.headers.update(headers) if headers
400
+ req.body = body if body
401
+ yield req if block_given?
402
+ end
403
+
404
+ env = request.to_env(self)
405
+ self.app.call(env)
406
+ end
407
+
408
+ # Creates and configures the request object.
409
+ #
410
+ # Returns the new Request.
411
+ def build_request(method)
412
+ Request.create(method) do |req|
413
+ req.params = self.params.dup
414
+ req.headers = self.headers.dup
415
+ req.options = self.options.merge(:proxy => self.proxy)
416
+ yield req if block_given?
417
+ end
418
+ end
419
+
420
+ # Takes a relative url for a request and combines it with the defaults
421
+ # set on the connection instance.
422
+ #
423
+ # conn = Faraday::Connection.new { ... }
424
+ # conn.url_prefix = "https://sushi.com/api?token=abc"
425
+ # conn.scheme # => https
426
+ # conn.path_prefix # => "/api"
427
+ #
428
+ # conn.build_url("nigiri?page=2") # => https://sushi.com/api/nigiri?token=abc&page=2
429
+ # conn.build_url("nigiri", :page => 2) # => https://sushi.com/api/nigiri?token=abc&page=2
430
+ #
431
+ def build_url(url, extra_params = nil)
432
+ uri = build_exclusive_url(url)
433
+
434
+ query_values = self.params.dup.merge_query(uri.query)
435
+ query_values.update extra_params if extra_params
436
+ uri.query = query_values.empty? ? nil : query_values.to_query
437
+
438
+ uri
439
+ end
440
+
441
+ # Internal: Build an absolute URL based on url_prefix.
442
+ #
443
+ # url - A String or URI-like object
444
+ # params - A Faraday::Utils::ParamsHash to replace the query values
445
+ # of the resulting url (default: nil).
446
+ #
447
+ # Returns the resulting URI instance.
448
+ def build_exclusive_url(url, params = nil)
449
+ url = nil if url.respond_to?(:empty?) and url.empty?
450
+ base = url_prefix
451
+ if url and base.path and base.path !~ /\/$/
452
+ base = base.dup
453
+ base.path = base.path + '/' # ensure trailing slash
454
+ end
455
+ uri = url ? base + url : base
456
+ uri.query = params.to_query if params
457
+ uri.query = nil if uri.query and uri.query.empty?
458
+ uri
459
+ end
460
+
461
+ # Internal: Creates a duplicate of this Faraday::Connection.
462
+ #
463
+ # Returns a Faraday::Connection.
464
+ def dup
465
+ self.class.new(build_url(''), :headers => headers.dup, :params => params.dup, :builder => builder.dup, :ssl => ssl.dup)
466
+ end
467
+ end
468
+ end