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