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