faraday-stack 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|