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,40 @@
1
+ module Faraday
2
+ module Error
3
+ class ClientError < StandardError
4
+ attr_reader :response
5
+
6
+ def initialize(ex, response = nil)
7
+ @wrapped_exception = nil
8
+ @response = response
9
+
10
+ if ex.respond_to?(:backtrace)
11
+ super(ex.message)
12
+ @wrapped_exception = ex
13
+ elsif ex.respond_to?(:each_key)
14
+ super("the server responded with status #{ex[:status]}")
15
+ @response = ex
16
+ else
17
+ super(ex.to_s)
18
+ end
19
+ end
20
+
21
+ def backtrace
22
+ if @wrapped_exception
23
+ @wrapped_exception.backtrace
24
+ else
25
+ super
26
+ end
27
+ end
28
+
29
+ def inspect
30
+ %(#<#{self.class}>)
31
+ end
32
+ end
33
+
34
+ class ConnectionFailed < ClientError; end
35
+ class ResourceNotFound < ClientError; end
36
+ class ParsingError < ClientError; end
37
+ class TimeoutError < ClientError; end
38
+ class MissingDependency < StandardError; end
39
+ end
40
+ end
@@ -0,0 +1,41 @@
1
+ module Faraday
2
+ class Middleware
3
+ extend MiddlewareRegistry
4
+
5
+ class << self
6
+ attr_accessor :load_error
7
+ private :load_error=
8
+ end
9
+
10
+ self.load_error = nil
11
+
12
+ # Executes a block which should try to require and reference dependent libraries
13
+ def self.dependency(lib = nil)
14
+ lib ? require(lib) : yield
15
+ rescue LoadError, NameError => error
16
+ self.load_error = error
17
+ end
18
+
19
+ def self.new(*)
20
+ raise "missing dependency for #{self}: #{load_error.message}" unless loaded?
21
+ super
22
+ end
23
+
24
+ def self.loaded?
25
+ load_error.nil?
26
+ end
27
+
28
+ def self.inherited(subclass)
29
+ super
30
+ subclass.send(:load_error=, self.load_error)
31
+ end
32
+
33
+ def self.adapter?
34
+ false
35
+ end
36
+
37
+ def initialize(app = nil)
38
+ @app = app
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,101 @@
1
+ module Faraday
2
+ # Used to setup urls, params, headers, and the request body in a sane manner.
3
+ #
4
+ # @connection.post do |req|
5
+ # req.url 'http://localhost', 'a' => '1' # 'http://localhost?a=1'
6
+ # req.headers['b'] = '2' # Header
7
+ # req.params['c'] = '3' # GET Param
8
+ # req['b'] = '2' # also Header
9
+ # req.body = 'abc'
10
+ # end
11
+ #
12
+ class Request < Struct.new(:method, :path, :params, :headers, :body, :options)
13
+ extend AutoloadHelper
14
+ extend MiddlewareRegistry
15
+
16
+ autoload_all 'faraday/request',
17
+ :UrlEncoded => 'url_encoded',
18
+ :Multipart => 'multipart',
19
+ :Retry => 'retry',
20
+ :Timeout => 'timeout',
21
+ :Authorization => 'authorization',
22
+ :BasicAuthentication => 'basic_authentication',
23
+ :TokenAuthentication => 'token_authentication'
24
+
25
+ register_middleware \
26
+ :url_encoded => :UrlEncoded,
27
+ :multipart => :Multipart,
28
+ :retry => :Retry,
29
+ :authorization => :Authorization,
30
+ :basic_auth => :BasicAuthentication,
31
+ :token_auth => :TokenAuthentication
32
+
33
+ def self.create(request_method)
34
+ new(request_method).tap do |request|
35
+ yield request if block_given?
36
+ end
37
+ end
38
+
39
+ # Public: Replace params, preserving the existing hash type
40
+ def params=(hash)
41
+ if params then params.replace hash
42
+ else super
43
+ end
44
+ end
45
+
46
+ # Public: Replace request headers, preserving the existing hash type
47
+ def headers=(hash)
48
+ if headers then headers.replace hash
49
+ else super
50
+ end
51
+ end
52
+
53
+ def url(path, params = nil)
54
+ if path.respond_to? :query
55
+ if query = path.query
56
+ path = path.dup
57
+ path.query = nil
58
+ end
59
+ else
60
+ path, query = path.split('?', 2)
61
+ end
62
+ self.path = path
63
+ self.params.merge_query query
64
+ self.params.update(params) if params
65
+ end
66
+
67
+ def [](key)
68
+ headers[key]
69
+ end
70
+
71
+ def []=(key, value)
72
+ headers[key] = value
73
+ end
74
+
75
+ # ENV Keys
76
+ # :method - a symbolized request method (:get, :post)
77
+ # :body - the request body that will eventually be converted to a string.
78
+ # :url - URI instance for the current request.
79
+ # :status - HTTP response status code
80
+ # :request_headers - hash of HTTP Headers to be sent to the server
81
+ # :response_headers - Hash of HTTP headers from the server
82
+ # :parallel_manager - sent if the connection is in parallel mode
83
+ # :request - Hash of options for configuring the request.
84
+ # :timeout - open/read timeout Integer in seconds
85
+ # :open_timeout - read timeout Integer in seconds
86
+ # :proxy - Hash of proxy options
87
+ # :uri - Proxy Server URI
88
+ # :user - Proxy server username
89
+ # :password - Proxy server password
90
+ # :ssl - Hash of options for configuring SSL requests.
91
+ def to_env(connection)
92
+ { :method => method,
93
+ :body => body,
94
+ :url => connection.build_exclusive_url(path, params),
95
+ :request_headers => headers,
96
+ :parallel_manager => connection.parallel_manager,
97
+ :request => options,
98
+ :ssl => connection.ssl}
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,40 @@
1
+ module Faraday
2
+ class Request::Authorization < Faraday::Middleware
3
+ KEY = "Authorization".freeze
4
+
5
+ # Public
6
+ def self.header(type, token)
7
+ case token
8
+ when String, Symbol then "#{type} #{token}"
9
+ when Hash then build_hash(type.to_s, token)
10
+ else
11
+ raise ArgumentError, "Can't build an Authorization #{type} header from #{token.inspect}"
12
+ end
13
+ end
14
+
15
+ # Internal
16
+ def self.build_hash(type, hash)
17
+ offset = KEY.size + type.size + 3
18
+ comma = ",\n#{' ' * offset}"
19
+ values = []
20
+ hash.each do |key, value|
21
+ values << "#{key}=#{value.to_s.inspect}"
22
+ end
23
+ "#{type} #{values * comma}"
24
+ end
25
+
26
+ def initialize(app, type, token)
27
+ @header_value = self.class.header(type, token)
28
+ super(app)
29
+ end
30
+
31
+ # Public
32
+ def call(env)
33
+ unless env[:request_headers][KEY]
34
+ env[:request_headers][KEY] = @header_value
35
+ end
36
+ @app.call(env)
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,13 @@
1
+ require 'base64'
2
+
3
+ module Faraday
4
+ class Request::BasicAuthentication < Request::Authorization
5
+ # Public
6
+ def self.header(login, pass)
7
+ value = Base64.encode64([login, pass].join(':'))
8
+ value.gsub!("\n", '')
9
+ super(:Basic, value)
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,62 @@
1
+ module Faraday
2
+ class Request::Multipart < Request::UrlEncoded
3
+ self.mime_type = 'multipart/form-data'.freeze
4
+ DEFAULT_BOUNDARY = "-----------RubyMultipartPost".freeze
5
+
6
+ def call(env)
7
+ match_content_type(env) do |params|
8
+ env[:request] ||= {}
9
+ env[:request][:boundary] ||= DEFAULT_BOUNDARY
10
+ env[:request_headers][CONTENT_TYPE] += ";boundary=#{env[:request][:boundary]}"
11
+ env[:body] = create_multipart(env, params)
12
+ end
13
+ @app.call env
14
+ end
15
+
16
+ def process_request?(env)
17
+ type = request_type(env)
18
+ env[:body].respond_to?(:each_key) and !env[:body].empty? and (
19
+ (type.empty? and has_multipart?(env[:body])) or
20
+ type == self.class.mime_type
21
+ )
22
+ end
23
+
24
+ def has_multipart?(obj)
25
+ # string is an enum in 1.8, returning list of itself
26
+ if obj.respond_to?(:each) && !obj.is_a?(String)
27
+ (obj.respond_to?(:values) ? obj.values : obj).each do |val|
28
+ return true if (val.respond_to?(:content_type) || has_multipart?(val))
29
+ end
30
+ end
31
+ false
32
+ end
33
+
34
+ def create_multipart(env, params)
35
+ boundary = env[:request][:boundary]
36
+ parts = process_params(params) do |key, value|
37
+ Faraday::Parts::Part.new(boundary, key, value)
38
+ end
39
+ parts << Faraday::Parts::EpiloguePart.new(boundary)
40
+
41
+ body = Faraday::CompositeReadIO.new(parts)
42
+ env[:request_headers]['Content-Length'] = body.length.to_s
43
+ return body
44
+ end
45
+
46
+ def process_params(params, prefix = nil, pieces = nil, &block)
47
+ params.inject(pieces || []) do |all, (key, value)|
48
+ key = "#{prefix}[#{key}]" if prefix
49
+
50
+ case value
51
+ when Array
52
+ values = value.inject([]) { |a,v| a << [nil, v] }
53
+ process_params(values, key, all, &block)
54
+ when Hash
55
+ process_params(value, key, all, &block)
56
+ else
57
+ all << block.call(key, value)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,67 @@
1
+ module Faraday
2
+ # Catches exceptions and retries each request a limited number of times.
3
+ #
4
+ # By default, it retries 2 times and handles only timeout exceptions. It can
5
+ # be configured with an arbitrary number of retries, a list of exceptions to
6
+ # handle an a retry interval.
7
+ #
8
+ # Examples
9
+ #
10
+ # Faraday.new do |conn|
11
+ # conn.request :retry, max: 2, interval: 0.05,
12
+ # exceptions: [CustomException, 'Timeout::Error']
13
+ # conn.adapter ...
14
+ # end
15
+ class Request::Retry < Faraday::Middleware
16
+ # Public: Initialize middleware
17
+ #
18
+ # Options:
19
+ # max - Maximum number of retries (default: 2).
20
+ # interval - Pause in seconds between retries (default: 0).
21
+ # exceptions - The list of exceptions to handle. Exceptions can be
22
+ # given as Class, Module, or String. (default:
23
+ # [Errno::ETIMEDOUT, Timeout::Error, Error::TimeoutError])
24
+ def initialize(app, options = {})
25
+ super(app)
26
+ @retries, options = options, {} if options.is_a? Integer
27
+ @retries ||= options.fetch(:max, 2).to_i
28
+ @sleep = options.fetch(:interval, 0).to_f
29
+ to_handle = options.fetch(:exceptions) {
30
+ [Errno::ETIMEDOUT, 'Timeout::Error', Error::TimeoutError]
31
+ }
32
+ @errmatch = build_exception_matcher Array(to_handle)
33
+ end
34
+
35
+ def call(env)
36
+ retries = @retries
37
+ begin
38
+ @app.call(env)
39
+ rescue @errmatch
40
+ if retries > 0
41
+ retries -= 1
42
+ sleep @sleep if @sleep > 0
43
+ retry
44
+ end
45
+ raise
46
+ end
47
+ end
48
+
49
+ # Private: construct an exception matcher object.
50
+ #
51
+ # An exception matcher for the rescue clause can usually be any object that
52
+ # responds to `===`, but for Ruby 1.8 it has to be a Class or Module.
53
+ def build_exception_matcher(exceptions)
54
+ matcher = Module.new
55
+ (class << matcher; self; end).class_eval do
56
+ define_method(:===) do |error|
57
+ exceptions.any? do |ex|
58
+ if ex.is_a? Module then error.is_a? ex
59
+ else error.class.to_s == ex.to_s
60
+ end
61
+ end
62
+ end
63
+ end
64
+ matcher
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,15 @@
1
+ module Faraday
2
+ class Request::TokenAuthentication < Request::Authorization
3
+ # Public
4
+ def self.header(token, options = nil)
5
+ options ||= {}
6
+ options[:token] = token
7
+ super :Token, options
8
+ end
9
+
10
+ def initialize(app, token, options = nil)
11
+ super(app, token, options)
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,35 @@
1
+ module Faraday
2
+ class Request::UrlEncoded < Faraday::Middleware
3
+ CONTENT_TYPE = 'Content-Type'.freeze
4
+
5
+ class << self
6
+ attr_accessor :mime_type
7
+ end
8
+ self.mime_type = 'application/x-www-form-urlencoded'.freeze
9
+
10
+ def call(env)
11
+ match_content_type(env) do |data|
12
+ env[:body] = Faraday::Utils.build_nested_query data
13
+ end
14
+ @app.call env
15
+ end
16
+
17
+ def match_content_type(env)
18
+ if process_request?(env)
19
+ env[:request_headers][CONTENT_TYPE] ||= self.class.mime_type
20
+ yield env[:body] unless env[:body].respond_to?(:to_str)
21
+ end
22
+ end
23
+
24
+ def process_request?(env)
25
+ type = request_type(env)
26
+ env[:body] and (type.empty? or type == self.class.mime_type)
27
+ end
28
+
29
+ def request_type(env)
30
+ type = env[:request_headers][CONTENT_TYPE].to_s
31
+ type = type.split(';', 2).first if type.index(';')
32
+ type
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,99 @@
1
+ require 'forwardable'
2
+
3
+ module Faraday
4
+ class Response
5
+ # Used for simple response middleware.
6
+ class Middleware < Faraday::Middleware
7
+ def call(env)
8
+ @app.call(env).on_complete do |environment|
9
+ on_complete(environment)
10
+ end
11
+ end
12
+
13
+ # Override this to modify the environment after the response has finished.
14
+ # Calls the `parse` method if defined
15
+ def on_complete(env)
16
+ if respond_to? :parse
17
+ env[:body] = parse(env[:body]) unless [204,304].index env[:status]
18
+ end
19
+ end
20
+ end
21
+
22
+ extend Forwardable
23
+ extend AutoloadHelper
24
+ extend MiddlewareRegistry
25
+
26
+ autoload_all 'faraday/response',
27
+ :RaiseError => 'raise_error',
28
+ :Logger => 'logger'
29
+
30
+ register_middleware \
31
+ :raise_error => :RaiseError,
32
+ :logger => :Logger
33
+
34
+ def initialize(env = nil)
35
+ @env = env
36
+ @on_complete_callbacks = []
37
+ end
38
+
39
+ attr_reader :env
40
+ alias_method :to_hash, :env
41
+
42
+ def status
43
+ finished? ? env[:status] : nil
44
+ end
45
+
46
+ def headers
47
+ finished? ? env[:response_headers] : {}
48
+ end
49
+ def_delegator :headers, :[]
50
+
51
+ def body
52
+ finished? ? env[:body] : nil
53
+ end
54
+
55
+ def finished?
56
+ !!env
57
+ end
58
+
59
+ def on_complete
60
+ if not finished?
61
+ @on_complete_callbacks << Proc.new
62
+ else
63
+ yield env
64
+ end
65
+ return self
66
+ end
67
+
68
+ def finish(env)
69
+ raise "response already finished" if finished?
70
+ @env = env
71
+ @on_complete_callbacks.each { |callback| callback.call(env) }
72
+ return self
73
+ end
74
+
75
+ def success?
76
+ (200..299).include?(status)
77
+ end
78
+
79
+ # because @on_complete_callbacks cannot be marshalled
80
+ def marshal_dump
81
+ !finished? ? nil : {
82
+ :status => @env[:status], :body => @env[:body],
83
+ :response_headers => @env[:response_headers]
84
+ }
85
+ end
86
+
87
+ def marshal_load(env)
88
+ @env = env
89
+ end
90
+
91
+ # Expand the env with more properties, without overriding existing ones.
92
+ # Useful for applying request params after restoring a marshalled Response.
93
+ def apply_request(request_env)
94
+ raise "response didn't finish yet" unless finished?
95
+ @env = request_env.merge @env
96
+ return self
97
+ end
98
+ end
99
+ end