faraday-stack 0.1.0
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.
- data/README.md +61 -0
- data/lib/faraday-stack.rb +1 -0
- data/lib/faraday_stack/addressable_patch.rb +18 -0
- data/lib/faraday_stack/caching.rb +58 -0
- data/lib/faraday_stack/follow_redirects.rb +43 -0
- data/lib/faraday_stack/instrumentation.rb +26 -0
- data/lib/faraday_stack/rack_compatible.rb +76 -0
- data/lib/faraday_stack/response_html.rb +12 -0
- data/lib/faraday_stack/response_json.rb +32 -0
- data/lib/faraday_stack/response_middleware.rb +64 -0
- data/lib/faraday_stack/response_xml.rb +12 -0
- data/lib/faraday_stack.rb +43 -0
- data/test/caching_test.rb +120 -0
- data/test/follow_redirects_test.rb +33 -0
- data/test/response_middleware_test.rb +72 -0
- data/test/test_helper.rb +2 -0
- metadata +73 -0
data/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Über Stack
|
|
2
|
+
|
|
3
|
+
[Faraday][] is an HTTP client lib that provides a common interface over many adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.
|
|
4
|
+
|
|
5
|
+
*“Faraday Stack”* is an add-on library that implements several middleware (such as JSON and XML parsers) and helps you build an awesome stack that covers most of your API-consuming needs.
|
|
6
|
+
|
|
7
|
+
Boring example:
|
|
8
|
+
|
|
9
|
+
require 'faraday_stack'
|
|
10
|
+
|
|
11
|
+
response = FaradayStack.get 'http://google.com'
|
|
12
|
+
|
|
13
|
+
response.headers['content-type'] #=> "text/html; charset=UTF-8"
|
|
14
|
+
response.headers['location'] #=> "http://www.google.com/"
|
|
15
|
+
puts response.body
|
|
16
|
+
|
|
17
|
+
Awesome example:
|
|
18
|
+
|
|
19
|
+
conn = FaradayStack.build 'http://github.com/api/v2'
|
|
20
|
+
|
|
21
|
+
# JSON resource
|
|
22
|
+
resp = conn.get 'json/repos/show/mislav/faraday-stack'
|
|
23
|
+
resp.body
|
|
24
|
+
#=> {"repository"=>{"language"=>"Ruby", "fork"=>false, ...}}
|
|
25
|
+
|
|
26
|
+
# XML resource
|
|
27
|
+
resp = conn.get 'xml/repos/show/mislav/faraday-stack'
|
|
28
|
+
resp.body.class
|
|
29
|
+
#=> Nokogiri::XML::Document
|
|
30
|
+
|
|
31
|
+
# 404
|
|
32
|
+
conn.get 'zomg/wrong/url'
|
|
33
|
+
#=> raises Faraday::Error::ResourceNotFound
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
* parses JSON, XML & HTML
|
|
38
|
+
* raises exceptions on 4xx, 5xx responses
|
|
39
|
+
* follows redirects
|
|
40
|
+
|
|
41
|
+
Optional features:
|
|
42
|
+
|
|
43
|
+
* encode POST/PUT bodies as JSON:
|
|
44
|
+
|
|
45
|
+
conn.post(path, payload, :content_type => 'application/json')
|
|
46
|
+
|
|
47
|
+
* add `Instrumentation` middleware to instrument requests with ActiveSupport
|
|
48
|
+
|
|
49
|
+
conn.builder.insert_after Faraday::Response::RaiseError, FaradayStack::Instrumentation
|
|
50
|
+
|
|
51
|
+
* add `Caching` middleware to have GET responses cached
|
|
52
|
+
|
|
53
|
+
conn.builder.insert_before FaradayStack::ResponseJSON, FaradayStack::Caching do
|
|
54
|
+
ActiveSupport::Cache::FileStore.new 'tmp/cache',
|
|
55
|
+
:namespace => 'faraday', :expires_in => 3600
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
To see how the default stack is built, see "[faraday_stack.rb][source]".
|
|
59
|
+
|
|
60
|
+
[faraday]: https://github.com/technoweenie/faraday
|
|
61
|
+
[source]: https://github.com/mislav/faraday-stack/blob/master/lib/faraday_stack.rb
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'faraday_stack'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'addressable/uri'
|
|
2
|
+
|
|
3
|
+
# fix `normalized_query` by sorting query key-value pairs
|
|
4
|
+
# (rejected: https://github.com/sporkmonger/addressable/issues/28)
|
|
5
|
+
class Addressable::URI
|
|
6
|
+
class << self
|
|
7
|
+
alias old_normalize_component normalize_component
|
|
8
|
+
|
|
9
|
+
def normalize_component(component, character_class = CharacterClasses::RESERVED + CharacterClasses::UNRESERVED)
|
|
10
|
+
normalized = old_normalize_component(component, character_class)
|
|
11
|
+
if character_class == Addressable::URI::CharacterClasses::QUERY
|
|
12
|
+
pairs = normalized.split('&').sort_by { |pair| pair[0, pair.index('=') || pair.length] }
|
|
13
|
+
normalized = pairs.join('&')
|
|
14
|
+
end
|
|
15
|
+
normalized
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module FaradayStack
|
|
2
|
+
class Caching < Faraday::Middleware
|
|
3
|
+
attr_reader :cache
|
|
4
|
+
|
|
5
|
+
def initialize(app, cache = nil, options = {})
|
|
6
|
+
super(app)
|
|
7
|
+
@cache = cache || Proc.new.call
|
|
8
|
+
@options = options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
if :get == env[:method]
|
|
13
|
+
if env[:parallel_manager]
|
|
14
|
+
# callback mode
|
|
15
|
+
cache_on_complete(env)
|
|
16
|
+
else
|
|
17
|
+
# synchronous mode
|
|
18
|
+
response = cache.fetch(cache_key(env)) { @app.call(env) }
|
|
19
|
+
finalize_response(response, env)
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
@app.call(env)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def cache_key(env)
|
|
27
|
+
url = env[:url]
|
|
28
|
+
if params_to_strip.any?
|
|
29
|
+
url = url.dup
|
|
30
|
+
url.query_values = url.query_values.reject { |k,| params_to_strip.include? k }
|
|
31
|
+
url.normalize!
|
|
32
|
+
else
|
|
33
|
+
url = url.normalize
|
|
34
|
+
end
|
|
35
|
+
url.request_uri
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def params_to_strip
|
|
39
|
+
@params_to_strip ||= Array(@options[:strip_params]).map { |p| p.to_s }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cache_on_complete(env)
|
|
43
|
+
key = cache_key(env)
|
|
44
|
+
if cached_response = cache.read(key)
|
|
45
|
+
finalize_response(cached_response, env)
|
|
46
|
+
else
|
|
47
|
+
response = @app.call(env)
|
|
48
|
+
response.on_complete { cache.write(key, response) }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def finalize_response(response, env)
|
|
53
|
+
response = env[:response] = response.dup if response.frozen?
|
|
54
|
+
response.apply_request env unless response.env[:method]
|
|
55
|
+
response
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module FaradayStack
|
|
2
|
+
class RedirectLimitReached < Faraday::Error::ClientError
|
|
3
|
+
attr_reader :response
|
|
4
|
+
|
|
5
|
+
def initialize(response)
|
|
6
|
+
super "too many redirects; last one to: #{response['location']}"
|
|
7
|
+
@response = response
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class FollowRedirects < Faraday::Middleware
|
|
12
|
+
# TODO: 307
|
|
13
|
+
REDIRECTS = [301, 302, 303]
|
|
14
|
+
# default value for max redirects followed
|
|
15
|
+
FOLLOW_LIMIT = 3
|
|
16
|
+
|
|
17
|
+
def initialize(app, options = {})
|
|
18
|
+
super(app)
|
|
19
|
+
@options = options
|
|
20
|
+
@follow_limit = options[:limit] || FOLLOW_LIMIT
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(env)
|
|
24
|
+
process_response(@app.call(env), @follow_limit)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def process_response(response, follows)
|
|
28
|
+
response.on_complete do |env|
|
|
29
|
+
if redirect? response
|
|
30
|
+
raise RedirectLimitReached, response if follows.zero?
|
|
31
|
+
env[:url] += response['location']
|
|
32
|
+
env[:method] = :get
|
|
33
|
+
response = process_response(@app.call(env), follows - 1)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
response
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def redirect?(response)
|
|
40
|
+
REDIRECTS.include? response.status
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module FaradayStack
|
|
2
|
+
# Measures request time only in synchronous request mode.
|
|
3
|
+
# Sample subscriber:
|
|
4
|
+
#
|
|
5
|
+
# ActiveSupport::Notifications.subscribe('request.faraday') do |name, start_time, end_time, _, env|
|
|
6
|
+
# url = env[:url]
|
|
7
|
+
# http_method = env[:method].to_s.upcase
|
|
8
|
+
# duration = end_time - start_time
|
|
9
|
+
# $stderr.puts '[%s] %s %s (%.3f s)' % [url.host, http_method, url.request_uri, duration]
|
|
10
|
+
# end
|
|
11
|
+
class Instrumentation < Faraday::Middleware
|
|
12
|
+
dependency 'active_support/notifications'
|
|
13
|
+
|
|
14
|
+
def initialize(app, options = {})
|
|
15
|
+
super(app)
|
|
16
|
+
@options = options
|
|
17
|
+
@name = options[:name] || 'request.faraday'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call(env)
|
|
21
|
+
ActiveSupport::Notifications.instrument(@name, env) do
|
|
22
|
+
@app.call(env)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
|
|
3
|
+
module FaradayStack
|
|
4
|
+
# Wraps a handler originally written for Rack to make it compatible with Faraday.
|
|
5
|
+
#
|
|
6
|
+
# Experimental. Only handles changes in request headers.
|
|
7
|
+
class RackCompatible
|
|
8
|
+
def initialize(app, rack_handler, *args)
|
|
9
|
+
# tiny middleware that decomposes a Faraday::Response to standard Rack
|
|
10
|
+
# array: [status, headers, body]
|
|
11
|
+
compatible_app = lambda do |env|
|
|
12
|
+
restore_env(env)
|
|
13
|
+
response = app.call(env)
|
|
14
|
+
[response.status, response.headers, Array(response.body)]
|
|
15
|
+
end
|
|
16
|
+
@rack = rack_handler.new(compatible_app, *args)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call(env)
|
|
20
|
+
prepare_env(env)
|
|
21
|
+
rack_response = @rack.call(env)
|
|
22
|
+
finalize_response(env, rack_response)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
NonPrefixedHeaders = %w[CONTENT_LENGTH CONTENT_TYPE]
|
|
26
|
+
|
|
27
|
+
# faraday to rack-compatible
|
|
28
|
+
def prepare_env(env)
|
|
29
|
+
env[:request_headers].each do |name, value|
|
|
30
|
+
name = name.upcase.tr('-', '_')
|
|
31
|
+
name = "HTTP_#{name}" unless NonPrefixedHeaders.include? name
|
|
32
|
+
env[name] = value
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
url = env[:url]
|
|
36
|
+
env['rack.url_scheme'] = url.scheme
|
|
37
|
+
env['PATH_INFO'] = url.path
|
|
38
|
+
env['SERVER_PORT'] = url.inferred_port
|
|
39
|
+
env['QUERY_STRING'] = url.query
|
|
40
|
+
env['REQUEST_METHOD'] = env[:method].to_s.upcase
|
|
41
|
+
|
|
42
|
+
env['rack.errors'] ||= StringIO.new
|
|
43
|
+
|
|
44
|
+
env
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# rack to faraday-compatible
|
|
48
|
+
def restore_env(env)
|
|
49
|
+
headers = env[:request_headers]
|
|
50
|
+
headers.clear
|
|
51
|
+
|
|
52
|
+
env.each do |name, value|
|
|
53
|
+
next unless String === name
|
|
54
|
+
if NonPrefixedHeaders.include? name or name.index('HTTP_') == 0
|
|
55
|
+
name = name.sub(/^HTTP_/).downcase.tr('_', '-')
|
|
56
|
+
headers[name] = value
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
env[:method] = env['REQUEST_METHOD'].downcase.to_sym
|
|
61
|
+
env
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def finalize_response(env, rack_response)
|
|
65
|
+
status, headers, body = rack_response
|
|
66
|
+
body = body.inject('') { |str, part| str << part }
|
|
67
|
+
headers = Faraday::Utils::Headers.new(headers) unless Faraday::Utils::Headers === headers
|
|
68
|
+
|
|
69
|
+
response_env = { :status => status, :body => body, :response_headers => headers }
|
|
70
|
+
|
|
71
|
+
env[:response] ||= Faraday::Response.new({})
|
|
72
|
+
env[:response].env.update(response_env)
|
|
73
|
+
env[:response]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module FaradayStack
|
|
2
|
+
class ResponseJSON < ResponseMiddleware
|
|
3
|
+
adapter_name = nil
|
|
4
|
+
|
|
5
|
+
# loads the JSON decoder either from yajl-ruby or activesupport
|
|
6
|
+
dependency do
|
|
7
|
+
require 'yajl'
|
|
8
|
+
adapter_name = Yajl::Parser.name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
dependency do
|
|
12
|
+
require 'active_support/json/decoding'
|
|
13
|
+
adapter_name = ActiveSupport::JSON.name
|
|
14
|
+
end unless loaded?
|
|
15
|
+
|
|
16
|
+
# defines a parser block depending on which adapter has loaded
|
|
17
|
+
case adapter_name
|
|
18
|
+
when 'Yajl::Parser'
|
|
19
|
+
define_parser do |body|
|
|
20
|
+
Yajl::Parser.parse(body)
|
|
21
|
+
end
|
|
22
|
+
when 'ActiveSupport::JSON'
|
|
23
|
+
define_parser do |body|
|
|
24
|
+
unless body.nil? or body.empty?
|
|
25
|
+
result = ActiveSupport::JSON.decode(body)
|
|
26
|
+
raise ActiveSupport::JSON.backend::ParseError if String === result
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module FaradayStack
|
|
2
|
+
# A base class for middleware that parses responses
|
|
3
|
+
class ResponseMiddleware < Faraday::Response::Middleware
|
|
4
|
+
CONTENT_TYPE = 'Content-Type'.freeze
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :parser
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Stores a block that receives the body and should return a parsed result
|
|
11
|
+
def self.define_parser(&block)
|
|
12
|
+
@parser = block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.inherited(subclass)
|
|
16
|
+
super
|
|
17
|
+
subclass.load_error = self.load_error
|
|
18
|
+
subclass.parser = self.parser
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(app = nil, options = {})
|
|
22
|
+
super(app)
|
|
23
|
+
@options = options
|
|
24
|
+
@content_types = Array(options[:content_type])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Override this to modify the environment after the response has finished.
|
|
28
|
+
def on_complete(env)
|
|
29
|
+
if process_response_type?(response_type(env)) and parse_response?(env)
|
|
30
|
+
env[:body] = parse(env[:body])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Parses the response body and returns the result.
|
|
35
|
+
# Instead of overriding this method, consider using `define_parser`
|
|
36
|
+
def parse(body)
|
|
37
|
+
if self.class.parser
|
|
38
|
+
begin
|
|
39
|
+
self.class.parser.call(body)
|
|
40
|
+
rescue
|
|
41
|
+
raise Faraday::Error::ParsingError, $!
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
body
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def response_type(env)
|
|
49
|
+
type = env[:response_headers][CONTENT_TYPE].to_s
|
|
50
|
+
type = type.split(';', 2).first if type.index(';')
|
|
51
|
+
type
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def process_response_type?(type)
|
|
55
|
+
@content_types.empty? or @content_types.any? { |pattern|
|
|
56
|
+
Regexp === pattern ? type =~ pattern : type == pattern
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def parse_response?(env)
|
|
61
|
+
env[:body].respond_to? :to_str
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'faraday'
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
require 'faraday_stack/addressable_patch'
|
|
5
|
+
|
|
6
|
+
module FaradayStack
|
|
7
|
+
extend Faraday::AutoloadHelper
|
|
8
|
+
|
|
9
|
+
autoload_all 'faraday_stack',
|
|
10
|
+
:ResponseMiddleware => 'response_middleware',
|
|
11
|
+
:ResponseJSON => 'response_json',
|
|
12
|
+
:ResponseXML => 'response_xml',
|
|
13
|
+
:ResponseHTML => 'response_html',
|
|
14
|
+
:Instrumentation => 'instrumentation',
|
|
15
|
+
:Caching => 'caching',
|
|
16
|
+
:FollowRedirects => 'follow_redirects',
|
|
17
|
+
:RackCompatible => 'rack_compatible'
|
|
18
|
+
|
|
19
|
+
# THE ÜBER STACK
|
|
20
|
+
def self.default_connection
|
|
21
|
+
@default_connection ||= self.build
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
extend Forwardable
|
|
26
|
+
attr_writer :default_connection
|
|
27
|
+
def_delegators :default_connection, :get, :post, :put, :head, :delete
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.build(url = nil, options = {})
|
|
31
|
+
Faraday::Connection.new(url, options) do |builder|
|
|
32
|
+
builder.request :url_encoded
|
|
33
|
+
builder.request :json
|
|
34
|
+
yield builder if block_given?
|
|
35
|
+
builder.use ResponseXML, :content_type => /[+\/]xml$/
|
|
36
|
+
builder.use ResponseHTML, :content_type => 'text/html'
|
|
37
|
+
builder.use ResponseJSON, :content_type => 'application/json'
|
|
38
|
+
builder.use FollowRedirects
|
|
39
|
+
builder.response :raise_error
|
|
40
|
+
builder.adapter Faraday.default_adapter
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'forwardable'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'rack/cache'
|
|
5
|
+
|
|
6
|
+
class CachingTest < Test::Unit::TestCase
|
|
7
|
+
class TestCache < Hash
|
|
8
|
+
def read(key)
|
|
9
|
+
if cached = self[key]
|
|
10
|
+
Marshal.load(cached)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def write(key, data)
|
|
15
|
+
self[key] = Marshal.dump(data)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fetch(key)
|
|
19
|
+
read(key) || yield.tap { |data| write(key, data) }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def setup
|
|
24
|
+
@cache = TestCache.new
|
|
25
|
+
|
|
26
|
+
request_count = 0
|
|
27
|
+
response = lambda { |env|
|
|
28
|
+
[200, {'Content-Type' => 'text/plain'}, "request:#{request_count+=1}"]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@conn = Faraday.new do |b|
|
|
32
|
+
b.use FaradayStack::Caching, @cache
|
|
33
|
+
b.adapter :test do |stub|
|
|
34
|
+
stub.get('/', &response)
|
|
35
|
+
stub.get('/?foo=bar', &response)
|
|
36
|
+
stub.post('/', &response)
|
|
37
|
+
stub.get('/other', &response)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
extend Forwardable
|
|
43
|
+
def_delegators :@conn, :get, :post
|
|
44
|
+
|
|
45
|
+
def test_cache_get
|
|
46
|
+
assert_equal 'request:1', get('/').body
|
|
47
|
+
assert_equal 'request:1', get('/').body
|
|
48
|
+
assert_equal 'request:2', get('/other').body
|
|
49
|
+
assert_equal 'request:2', get('/other').body
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_response_has_request_params
|
|
53
|
+
get('/') # make cache
|
|
54
|
+
response = get('/')
|
|
55
|
+
assert_equal :get, response.env[:method]
|
|
56
|
+
assert_equal '/', response.env[:url].to_s
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_cache_query_params
|
|
60
|
+
assert_equal 'request:1', get('/').body
|
|
61
|
+
assert_equal 'request:2', get('/?foo=bar').body
|
|
62
|
+
assert_equal 'request:2', get('/?foo=bar').body
|
|
63
|
+
assert_equal 'request:1', get('/').body
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_doesnt_cache_post
|
|
67
|
+
assert_equal 'request:1', post('/').body
|
|
68
|
+
assert_equal 'request:2', post('/').body
|
|
69
|
+
assert_equal 'request:3', post('/').body
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# RackCompatible + Rack::Cache
|
|
74
|
+
class HttpCachingTest < Test::Unit::TestCase
|
|
75
|
+
include FileUtils
|
|
76
|
+
|
|
77
|
+
CACHE_DIR = File.expand_path('../cache', __FILE__)
|
|
78
|
+
|
|
79
|
+
def setup
|
|
80
|
+
rm_r CACHE_DIR if File.exists? CACHE_DIR
|
|
81
|
+
|
|
82
|
+
request_count = 0
|
|
83
|
+
response = lambda { |env|
|
|
84
|
+
[200, { 'Content-Type' => 'text/plain',
|
|
85
|
+
'Cache-Control' => 'public, max-age=900',
|
|
86
|
+
},
|
|
87
|
+
"request:#{request_count+=1}"]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@conn = Faraday.new do |b|
|
|
91
|
+
b.use FaradayStack::RackCompatible, Rack::Cache::Context,
|
|
92
|
+
:metastore => "file:#{CACHE_DIR}/rack/meta",
|
|
93
|
+
:entitystore => "file:#{CACHE_DIR}/rack/body",
|
|
94
|
+
:verbose => true
|
|
95
|
+
|
|
96
|
+
b.adapter :test do |stub|
|
|
97
|
+
stub.get('/', &response)
|
|
98
|
+
stub.post('/', &response)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
extend Forwardable
|
|
104
|
+
def_delegators :@conn, :get, :post
|
|
105
|
+
|
|
106
|
+
def test_cache_get
|
|
107
|
+
assert_equal 'request:1', get('/').body
|
|
108
|
+
response = get('/')
|
|
109
|
+
assert_equal 'request:1', response.body
|
|
110
|
+
assert_equal 'text/plain', response['content-type']
|
|
111
|
+
|
|
112
|
+
assert_equal 'request:2', post('/').body
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_doesnt_cache_post
|
|
116
|
+
assert_equal 'request:1', get('/').body
|
|
117
|
+
assert_equal 'request:2', post('/').body
|
|
118
|
+
assert_equal 'request:3', post('/').body
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'forwardable'
|
|
3
|
+
|
|
4
|
+
class FollowRedirectsTest < Test::Unit::TestCase
|
|
5
|
+
def setup
|
|
6
|
+
@conn = Faraday.new do |b|
|
|
7
|
+
b.use FaradayStack::FollowRedirects
|
|
8
|
+
b.adapter :test do |stub|
|
|
9
|
+
stub.get('/') { [301, {'Location' => '/found'}, ''] }
|
|
10
|
+
stub.post('/create') { [302, {'Location' => '/'}, ''] }
|
|
11
|
+
stub.get('/found') { [200, {'Content-Type' => 'text/plain'}, 'fin'] }
|
|
12
|
+
stub.get('/loop') { [302, {'Location' => '/loop'}, ''] }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
extend Forwardable
|
|
18
|
+
def_delegators :@conn, :get, :post
|
|
19
|
+
|
|
20
|
+
def test_redirected
|
|
21
|
+
assert_equal 'fin', get('/').body
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_redirected_twice
|
|
25
|
+
assert_equal 'fin', post('/create').body
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_redirect_limit
|
|
29
|
+
assert_raises FaradayStack::RedirectLimitReached do
|
|
30
|
+
get('/loop')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class ResponseMiddlewareTest < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@json_handler = FaradayStack::ResponseJSON
|
|
6
|
+
@conn = Faraday.new do |b|
|
|
7
|
+
b.use @json_handler
|
|
8
|
+
b.adapter :test do |stub|
|
|
9
|
+
stub.get('json') { [200, {'Content-Type' => 'application/json; charset=utf-8'}, "[1,2,3]"] }
|
|
10
|
+
stub.get('blank') { [200, {'Content-Type' => 'application/json'}, ''] }
|
|
11
|
+
stub.get('nil') { [200, {'Content-Type' => 'application/json'}, nil] }
|
|
12
|
+
stub.get('bad_json') { [200, {'Content-Type' => 'application/json'}, '<body></body>']}
|
|
13
|
+
stub.get('non_json') { [200, {'Content-Type' => 'text/html'}, '<body></body>']}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process_only(*types)
|
|
19
|
+
@conn.builder.swap @json_handler, @json_handler, :content_type => types
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_uses_json_to_parse_json_content
|
|
23
|
+
response = @conn.get('json')
|
|
24
|
+
assert response.success?
|
|
25
|
+
assert_equal [1,2,3], response.body
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_uses_json_to_parse_json_content_conditional
|
|
29
|
+
process_only('application/json')
|
|
30
|
+
response = @conn.get('json')
|
|
31
|
+
assert response.success?
|
|
32
|
+
assert_equal [1,2,3], response.body
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_uses_json_to_parse_json_content_conditional_with_regexp
|
|
36
|
+
process_only(%r{/(x-)?json$})
|
|
37
|
+
response = @conn.get('json')
|
|
38
|
+
assert response.success?
|
|
39
|
+
assert_equal [1,2,3], response.body
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_uses_json_to_skip_blank_content
|
|
43
|
+
response = @conn.get('blank')
|
|
44
|
+
assert response.success?
|
|
45
|
+
assert_nil response.body
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_uses_json_to_skip_nil_content
|
|
49
|
+
response = @conn.get('nil')
|
|
50
|
+
assert response.success?
|
|
51
|
+
assert_nil response.body
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_uses_json_to_raise_Faraday_Error_Parsing_with_no_json_content
|
|
55
|
+
assert_raises Faraday::Error::ParsingError do
|
|
56
|
+
@conn.get('bad_json')
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test_non_json_response
|
|
61
|
+
assert_raises Faraday::Error::ParsingError do
|
|
62
|
+
@conn.get('non_json')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def test_non_json_response_conditional
|
|
67
|
+
process_only('application/json')
|
|
68
|
+
response = @conn.get('non_json')
|
|
69
|
+
assert_equal 'text/html', response.headers['Content-Type']
|
|
70
|
+
assert_equal '<body></body>', response.body
|
|
71
|
+
end
|
|
72
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: faraday-stack
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Mislav Marohnić
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2011-04-01 00:00:00.000000000 +02:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: faraday
|
|
17
|
+
requirement: &2164745380 !ruby/object:Gem::Requirement
|
|
18
|
+
none: false
|
|
19
|
+
requirements:
|
|
20
|
+
- - ~>
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '0.6'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: *2164745380
|
|
26
|
+
description:
|
|
27
|
+
email: mislav.marohnic@gmail.com
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- lib/faraday-stack.rb
|
|
33
|
+
- lib/faraday_stack/addressable_patch.rb
|
|
34
|
+
- lib/faraday_stack/caching.rb
|
|
35
|
+
- lib/faraday_stack/follow_redirects.rb
|
|
36
|
+
- lib/faraday_stack/instrumentation.rb
|
|
37
|
+
- lib/faraday_stack/rack_compatible.rb
|
|
38
|
+
- lib/faraday_stack/response_html.rb
|
|
39
|
+
- lib/faraday_stack/response_json.rb
|
|
40
|
+
- lib/faraday_stack/response_middleware.rb
|
|
41
|
+
- lib/faraday_stack/response_xml.rb
|
|
42
|
+
- lib/faraday_stack.rb
|
|
43
|
+
- test/caching_test.rb
|
|
44
|
+
- test/follow_redirects_test.rb
|
|
45
|
+
- test/response_middleware_test.rb
|
|
46
|
+
- test/test_helper.rb
|
|
47
|
+
- README.md
|
|
48
|
+
has_rdoc: false
|
|
49
|
+
homepage: https://github.com/mislav/faraday-stack
|
|
50
|
+
licenses: []
|
|
51
|
+
post_install_message:
|
|
52
|
+
rdoc_options: []
|
|
53
|
+
require_paths:
|
|
54
|
+
- lib
|
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
56
|
+
none: false
|
|
57
|
+
requirements:
|
|
58
|
+
- - ! '>='
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
|
+
none: false
|
|
63
|
+
requirements:
|
|
64
|
+
- - ! '>='
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubyforge_project:
|
|
69
|
+
rubygems_version: 1.5.3
|
|
70
|
+
signing_key:
|
|
71
|
+
specification_version: 3
|
|
72
|
+
summary: Great Faraday stack for consuming all kinds of APIs
|
|
73
|
+
test_files: []
|