faraday_middleware_safeyaml 0.12.pre.safeyaml
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.
- checksums.yaml +7 -0
- data/LICENSE.md +20 -0
- data/README.md +58 -0
- data/lib/faraday_middleware.rb +48 -0
- data/lib/faraday_middleware/addressable_patch.rb +20 -0
- data/lib/faraday_middleware/backwards_compatibility.rb +15 -0
- data/lib/faraday_middleware/gzip.rb +64 -0
- data/lib/faraday_middleware/instrumentation.rb +30 -0
- data/lib/faraday_middleware/rack_compatible.rb +86 -0
- data/lib/faraday_middleware/request/encode_json.rb +53 -0
- data/lib/faraday_middleware/request/method_override.rb +51 -0
- data/lib/faraday_middleware/request/oauth.rb +87 -0
- data/lib/faraday_middleware/request/oauth2.rb +85 -0
- data/lib/faraday_middleware/response/caching.rb +102 -0
- data/lib/faraday_middleware/response/chunked.rb +29 -0
- data/lib/faraday_middleware/response/follow_redirects.rb +134 -0
- data/lib/faraday_middleware/response/mashify.rb +37 -0
- data/lib/faraday_middleware/response/parse_dates.rb +39 -0
- data/lib/faraday_middleware/response/parse_json.rb +50 -0
- data/lib/faraday_middleware/response/parse_marshal.rb +13 -0
- data/lib/faraday_middleware/response/parse_xml.rb +15 -0
- data/lib/faraday_middleware/response/parse_yaml.rb +38 -0
- data/lib/faraday_middleware/response/rashify.rb +15 -0
- data/lib/faraday_middleware/response_middleware.rb +112 -0
- data/lib/faraday_middleware/version.rb +3 -0
- metadata +105 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module FaradayMiddleware
|
5
|
+
# Public: Uses the simple_oauth library to sign requests according the
|
6
|
+
# OAuth protocol.
|
7
|
+
#
|
8
|
+
# The options for this middleware are forwarded to SimpleOAuth::Header:
|
9
|
+
# :consumer_key, :consumer_secret, :token, :token_secret. All these
|
10
|
+
# parameters are optional.
|
11
|
+
#
|
12
|
+
# The signature is added to the "Authorization" HTTP request header. If the
|
13
|
+
# value for this header already exists, it is not overriden.
|
14
|
+
#
|
15
|
+
# If no Content-Type header is specified, this middleware assumes that
|
16
|
+
# request body parameters should be included while signing the request.
|
17
|
+
# Otherwise, it only includes them if the Content-Type is
|
18
|
+
# "application/x-www-form-urlencoded", as per OAuth 1.0.
|
19
|
+
#
|
20
|
+
# For better performance while signing requests, this middleware should be
|
21
|
+
# positioned before UrlEncoded middleware on the stack, but after any other
|
22
|
+
# body-encoding middleware (such as EncodeJson).
|
23
|
+
class OAuth < Faraday::Middleware
|
24
|
+
dependency 'simple_oauth'
|
25
|
+
|
26
|
+
AUTH_HEADER = 'Authorization'.freeze
|
27
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
28
|
+
TYPE_URLENCODED = 'application/x-www-form-urlencoded'.freeze
|
29
|
+
|
30
|
+
extend Forwardable
|
31
|
+
parser_method = :parse_nested_query
|
32
|
+
parser_module = ::Faraday::Utils.respond_to?(parser_method) ? 'Faraday::Utils' : 'Rack::Utils'
|
33
|
+
def_delegator parser_module, parser_method
|
34
|
+
|
35
|
+
def initialize(app, options)
|
36
|
+
super(app)
|
37
|
+
@options = options
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(env)
|
41
|
+
env[:request_headers][AUTH_HEADER] ||= oauth_header(env).to_s if sign_request?(env)
|
42
|
+
@app.call(env)
|
43
|
+
end
|
44
|
+
|
45
|
+
def oauth_header(env)
|
46
|
+
SimpleOAuth::Header.new env[:method],
|
47
|
+
env[:url].to_s,
|
48
|
+
signature_params(body_params(env)),
|
49
|
+
oauth_options(env)
|
50
|
+
end
|
51
|
+
|
52
|
+
def sign_request?(env)
|
53
|
+
!!env[:request].fetch(:oauth, true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def oauth_options(env)
|
57
|
+
if extra = env[:request][:oauth] and extra.is_a? Hash and !extra.empty?
|
58
|
+
@options.merge extra
|
59
|
+
else
|
60
|
+
@options
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def body_params(env)
|
65
|
+
if include_body_params?(env)
|
66
|
+
if env[:body].respond_to?(:to_str)
|
67
|
+
parse_nested_query env[:body]
|
68
|
+
else
|
69
|
+
env[:body]
|
70
|
+
end
|
71
|
+
end || {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def include_body_params?(env)
|
75
|
+
# see RFC 5849, section 3.4.1.3.1 for details
|
76
|
+
!(type = env[:request_headers][CONTENT_TYPE]) or type == TYPE_URLENCODED
|
77
|
+
end
|
78
|
+
|
79
|
+
def signature_params(params)
|
80
|
+
params.empty? ? params :
|
81
|
+
params.reject {|k,v| v.respond_to?(:content_type) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# deprecated alias
|
87
|
+
Faraday::Request::OAuth = FaradayMiddleware::OAuth
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module FaradayMiddleware
|
5
|
+
# Public: A simple middleware that adds an access token to each request.
|
6
|
+
#
|
7
|
+
# By default, the token is added as both "access_token" query parameter and the
|
8
|
+
# "Authorization" HTTP request header. It can alternatively be added exclusively
|
9
|
+
# as a bearer token "Authorization" header by specifying a "token_type" option
|
10
|
+
# of "bearer". However, an explicit "access_token" parameter or "Authorization"
|
11
|
+
# header for the current request are not overriden.
|
12
|
+
#
|
13
|
+
# Examples
|
14
|
+
#
|
15
|
+
# # configure default token:
|
16
|
+
# OAuth2.new(app, 'abc123')
|
17
|
+
#
|
18
|
+
# # configure query parameter name:
|
19
|
+
# OAuth2.new(app, 'abc123', :param_name => 'my_oauth_token')
|
20
|
+
#
|
21
|
+
# # use bearer token authorization header only
|
22
|
+
# OAuth2.new(app, 'abc123', :token_type => 'bearer')
|
23
|
+
#
|
24
|
+
# # default token value is optional:
|
25
|
+
# OAuth2.new(app, :param_name => 'my_oauth_token')
|
26
|
+
class OAuth2 < Faraday::Middleware
|
27
|
+
|
28
|
+
PARAM_NAME = 'access_token'.freeze
|
29
|
+
TOKEN_TYPE = 'param'.freeze
|
30
|
+
AUTH_HEADER = 'Authorization'.freeze
|
31
|
+
|
32
|
+
attr_reader :param_name, :token_type
|
33
|
+
|
34
|
+
extend Forwardable
|
35
|
+
def_delegators :'Faraday::Utils', :parse_query, :build_query
|
36
|
+
|
37
|
+
def call(env)
|
38
|
+
params = { param_name => @token }.update query_params(env[:url])
|
39
|
+
token = params[param_name]
|
40
|
+
|
41
|
+
if token.respond_to?(:empty?) && !token.empty?
|
42
|
+
case @token_type.downcase
|
43
|
+
when 'param'
|
44
|
+
env[:url].query = build_query params
|
45
|
+
env[:request_headers][AUTH_HEADER] ||= %(Token token="#{token}")
|
46
|
+
when 'bearer'
|
47
|
+
env[:request_headers][AUTH_HEADER] ||= %(Bearer #{token})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@app.call env
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(app, token = nil, options = {})
|
55
|
+
super(app)
|
56
|
+
options, token = token, nil if token.is_a? Hash
|
57
|
+
@token = token && token.to_s
|
58
|
+
@param_name = options.fetch(:param_name, PARAM_NAME).to_s
|
59
|
+
@token_type = options.fetch(:token_type, TOKEN_TYPE).to_s
|
60
|
+
|
61
|
+
if @token_type == 'param' && @param_name.empty?
|
62
|
+
raise ArgumentError, ":param_name can't be blank"
|
63
|
+
end
|
64
|
+
|
65
|
+
if options[:token_type].nil?
|
66
|
+
warn "\nWarning: FaradayMiddleware::OAuth2 initialized with default "\
|
67
|
+
"token_type - token will be added as both a query string parameter "\
|
68
|
+
"and an Authorization header. In the next major release, tokens will "\
|
69
|
+
"be added exclusively as an Authorization header by default. Please "\
|
70
|
+
"visit https://github.com/lostisland/faraday_middleware/wiki for more information."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def query_params(url)
|
75
|
+
if url.query.nil? or url.query.empty?
|
76
|
+
{}
|
77
|
+
else
|
78
|
+
parse_query url.query
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# deprecated alias
|
85
|
+
Faraday::Request::OAuth2 = FaradayMiddleware::OAuth2
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'forwardable'
|
3
|
+
# fixes normalizing query strings:
|
4
|
+
require 'faraday_middleware/addressable_patch' if defined? ::Addressable::URI
|
5
|
+
|
6
|
+
module FaradayMiddleware
|
7
|
+
# Public: Caches GET responses and pulls subsequent ones from the cache.
|
8
|
+
class Caching < Faraday::Middleware
|
9
|
+
attr_reader :cache
|
10
|
+
# Internal: List of status codes that can be cached:
|
11
|
+
# * 200 - 'OK'
|
12
|
+
# * 203 - 'Non-Authoritative Information'
|
13
|
+
# * 300 - 'Multiple Choices'
|
14
|
+
# * 301 - 'Moved Permanently'
|
15
|
+
# * 302 - 'Found'
|
16
|
+
# * 404 - 'Not Found'
|
17
|
+
# * 410 - 'Gone'
|
18
|
+
CACHEABLE_STATUS_CODES = [200, 203, 300, 301, 302, 404, 410]
|
19
|
+
|
20
|
+
extend Forwardable
|
21
|
+
def_delegators :'Faraday::Utils', :parse_query, :build_query
|
22
|
+
|
23
|
+
# Public: initialize the middleware.
|
24
|
+
#
|
25
|
+
# cache - An object that responds to read, write and fetch (default: nil).
|
26
|
+
# options - An options Hash (default: {}):
|
27
|
+
# :ignore_params - String name or Array names of query params
|
28
|
+
# that should be ignored when forming the cache
|
29
|
+
# key (default: []).
|
30
|
+
#
|
31
|
+
# Yields if no cache is given. The block should return a cache object.
|
32
|
+
def initialize(app, cache = nil, options = {})
|
33
|
+
super(app)
|
34
|
+
options, cache = cache, nil if cache.is_a? Hash and block_given?
|
35
|
+
@cache = cache || yield
|
36
|
+
@options = options
|
37
|
+
end
|
38
|
+
|
39
|
+
def call(env)
|
40
|
+
if :get == env[:method]
|
41
|
+
if env[:parallel_manager]
|
42
|
+
# callback mode
|
43
|
+
cache_on_complete(env)
|
44
|
+
else
|
45
|
+
# synchronous mode
|
46
|
+
key = cache_key(env)
|
47
|
+
unless response = cache.read(key) and response
|
48
|
+
response = @app.call(env)
|
49
|
+
|
50
|
+
if CACHEABLE_STATUS_CODES.include?(response.status)
|
51
|
+
cache.write(key, response)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
finalize_response(response, env)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
@app.call(env)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def cache_key(env)
|
62
|
+
url = env[:url].dup
|
63
|
+
if url.query && params_to_ignore.any?
|
64
|
+
params = parse_query url.query
|
65
|
+
params.reject! {|k,| params_to_ignore.include? k }
|
66
|
+
url.query = params.any? ? build_query(params) : nil
|
67
|
+
end
|
68
|
+
url.normalize!
|
69
|
+
url.request_uri
|
70
|
+
end
|
71
|
+
|
72
|
+
def params_to_ignore
|
73
|
+
@params_to_ignore ||= Array(@options[:ignore_params]).map { |p| p.to_s }
|
74
|
+
end
|
75
|
+
|
76
|
+
def cache_on_complete(env)
|
77
|
+
key = cache_key(env)
|
78
|
+
if cached_response = cache.read(key)
|
79
|
+
finalize_response(cached_response, env)
|
80
|
+
else
|
81
|
+
# response.status is nil at this point, any checks need to be done inside on_complete block
|
82
|
+
@app.call(env).on_complete do |response_env|
|
83
|
+
if CACHEABLE_STATUS_CODES.include?(response_env.status)
|
84
|
+
cache.write(key, response_env.response)
|
85
|
+
end
|
86
|
+
response_env
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def finalize_response(response, env)
|
92
|
+
response = response.dup if response.frozen?
|
93
|
+
env[:response] = response
|
94
|
+
unless env[:response_headers]
|
95
|
+
env.update response.env
|
96
|
+
# FIXME: omg hax
|
97
|
+
response.instance_variable_set('@env', env)
|
98
|
+
end
|
99
|
+
response
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'faraday_middleware/response_middleware'
|
2
|
+
|
3
|
+
module FaradayMiddleware
|
4
|
+
# Public: Parse a Transfer-Encoding: Chunked response to just the original data
|
5
|
+
class Chunked < FaradayMiddleware::ResponseMiddleware
|
6
|
+
TRANSFER_ENCODING = 'transfer-encoding'.freeze
|
7
|
+
|
8
|
+
define_parser do |raw_body|
|
9
|
+
decoded_body = []
|
10
|
+
until raw_body.empty?
|
11
|
+
chunk_len, raw_body = raw_body.split("\r\n", 2)
|
12
|
+
chunk_len = chunk_len.split(';',2).first.hex
|
13
|
+
break if chunk_len == 0
|
14
|
+
decoded_body << raw_body[0, chunk_len]
|
15
|
+
# The 2 is to strip the extra CRLF at the end of the chunk
|
16
|
+
raw_body = raw_body[chunk_len + 2, raw_body.length - chunk_len - 2]
|
17
|
+
end
|
18
|
+
decoded_body.join('')
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_response?(env)
|
22
|
+
super and chunked_encoding?(env[:response_headers])
|
23
|
+
end
|
24
|
+
|
25
|
+
def chunked_encoding?(headers)
|
26
|
+
encoding = headers[TRANSFER_ENCODING] and encoding.split(',').include?('chunked')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module FaradayMiddleware
|
5
|
+
# Public: Exception thrown when the maximum amount of requests is exceeded.
|
6
|
+
class RedirectLimitReached < Faraday::Error::ClientError
|
7
|
+
attr_reader :response
|
8
|
+
|
9
|
+
def initialize(response)
|
10
|
+
super "too many redirects; last one to: #{response['location']}"
|
11
|
+
@response = response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Public: Follow HTTP 301, 302, 303, 307, and 308 redirects.
|
16
|
+
#
|
17
|
+
# For HTTP 301, 302, and 303, the original GET, POST, PUT, DELETE, or PATCH
|
18
|
+
# request gets converted into a GET. With `:standards_compliant => true`,
|
19
|
+
# however, the HTTP method after 301/302 remains unchanged. This allows you
|
20
|
+
# to opt into HTTP/1.1 compliance and act unlike the major web browsers.
|
21
|
+
#
|
22
|
+
# This middleware currently only works with synchronous requests; i.e. it
|
23
|
+
# doesn't support parallelism.
|
24
|
+
#
|
25
|
+
# If you wish to persist cookies across redirects, you could use
|
26
|
+
# the faraday-cookie_jar gem:
|
27
|
+
#
|
28
|
+
# Faraday.new(:url => url) do |faraday|
|
29
|
+
# faraday.use FaradayMiddleware::FollowRedirects
|
30
|
+
# faraday.use :cookie_jar
|
31
|
+
# faraday.adapter Faraday.default_adapter
|
32
|
+
# end
|
33
|
+
class FollowRedirects < Faraday::Middleware
|
34
|
+
# HTTP methods for which 30x redirects can be followed
|
35
|
+
ALLOWED_METHODS = Set.new [:head, :options, :get, :post, :put, :patch, :delete]
|
36
|
+
# HTTP redirect status codes that this middleware implements
|
37
|
+
REDIRECT_CODES = Set.new [301, 302, 303, 307, 308]
|
38
|
+
# Keys in env hash which will get cleared between requests
|
39
|
+
ENV_TO_CLEAR = Set.new [:status, :response, :response_headers]
|
40
|
+
|
41
|
+
# Default value for max redirects followed
|
42
|
+
FOLLOW_LIMIT = 3
|
43
|
+
|
44
|
+
# Regex that matches characters that need to be escaped in URLs, sans
|
45
|
+
# the "%" character which we assume already represents an escaped sequence.
|
46
|
+
URI_UNSAFE = /[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]%]/
|
47
|
+
|
48
|
+
# Public: Initialize the middleware.
|
49
|
+
#
|
50
|
+
# options - An options Hash (default: {}):
|
51
|
+
# :limit - A Numeric redirect limit (default: 3)
|
52
|
+
# :standards_compliant - A Boolean indicating whether to respect
|
53
|
+
# the HTTP spec when following 301/302
|
54
|
+
# (default: false)
|
55
|
+
# :callback - A callable that will be called on redirects
|
56
|
+
# with the old and new envs
|
57
|
+
def initialize(app, options = {})
|
58
|
+
super(app)
|
59
|
+
@options = options
|
60
|
+
|
61
|
+
@convert_to_get = Set.new [303]
|
62
|
+
@convert_to_get << 301 << 302 unless standards_compliant?
|
63
|
+
end
|
64
|
+
|
65
|
+
def call(env)
|
66
|
+
perform_with_redirection(env, follow_limit)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def convert_to_get?(response)
|
72
|
+
![:head, :options].include?(response.env[:method]) &&
|
73
|
+
@convert_to_get.include?(response.status)
|
74
|
+
end
|
75
|
+
|
76
|
+
def perform_with_redirection(env, follows)
|
77
|
+
request_body = env[:body]
|
78
|
+
response = @app.call(env)
|
79
|
+
|
80
|
+
response.on_complete do |response_env|
|
81
|
+
if follow_redirect?(response_env, response)
|
82
|
+
raise RedirectLimitReached, response if follows.zero?
|
83
|
+
new_request_env = update_env(response_env.dup, request_body, response)
|
84
|
+
callback.call(response_env, new_request_env) if callback
|
85
|
+
response = perform_with_redirection(new_request_env, follows - 1)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
response
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_env(env, request_body, response)
|
92
|
+
env[:url] += safe_escape(response['location'] || '')
|
93
|
+
|
94
|
+
if convert_to_get?(response)
|
95
|
+
env[:method] = :get
|
96
|
+
env[:body] = nil
|
97
|
+
else
|
98
|
+
env[:body] = request_body
|
99
|
+
end
|
100
|
+
|
101
|
+
ENV_TO_CLEAR.each {|key| env.delete key }
|
102
|
+
|
103
|
+
env
|
104
|
+
end
|
105
|
+
|
106
|
+
def follow_redirect?(env, response)
|
107
|
+
ALLOWED_METHODS.include? env[:method] and
|
108
|
+
REDIRECT_CODES.include? response.status
|
109
|
+
end
|
110
|
+
|
111
|
+
def follow_limit
|
112
|
+
@options.fetch(:limit, FOLLOW_LIMIT)
|
113
|
+
end
|
114
|
+
|
115
|
+
def standards_compliant?
|
116
|
+
@options.fetch(:standards_compliant, false)
|
117
|
+
end
|
118
|
+
|
119
|
+
def callback
|
120
|
+
@options[:callback]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Internal: escapes unsafe characters from an URL which might be a path
|
124
|
+
# component only or a fully qualified URI so that it can be joined onto an
|
125
|
+
# URI:HTTP using the `+` operator. Doesn't escape "%" characters so to not
|
126
|
+
# risk double-escaping.
|
127
|
+
def safe_escape(uri)
|
128
|
+
uri = uri.split('#')[0] # we want to remove the fragment if present
|
129
|
+
uri.to_s.gsub(URI_UNSAFE) { |match|
|
130
|
+
'%' + match.unpack('H2' * match.bytesize).join('%').upcase
|
131
|
+
}
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module FaradayMiddleware
|
4
|
+
# Public: Converts parsed response bodies to a Hashie::Mash if they were of
|
5
|
+
# Hash or Array type.
|
6
|
+
class Mashify < Faraday::Response::Middleware
|
7
|
+
attr_accessor :mash_class
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :mash_class
|
11
|
+
end
|
12
|
+
|
13
|
+
dependency do
|
14
|
+
require 'hashie/mash'
|
15
|
+
self.mash_class = ::Hashie::Mash
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(app = nil, options = {})
|
19
|
+
super(app)
|
20
|
+
self.mash_class = options[:mash_class] || self.class.mash_class
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(body)
|
24
|
+
case body
|
25
|
+
when Hash
|
26
|
+
mash_class.new(body)
|
27
|
+
when Array
|
28
|
+
body.map { |item| parse(item) }
|
29
|
+
else
|
30
|
+
body
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# deprecated alias
|
37
|
+
Faraday::Response::Mashify = FaradayMiddleware::Mashify
|