jeffersongirao_faraday-stack 0.1.5
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 +70 -0
- data/Rakefile +10 -0
- data/lib/faraday-stack.rb +1 -0
- data/lib/faraday_stack.rb +52 -0
- data/lib/faraday_stack/addressable_patch.rb +20 -0
- data/lib/faraday_stack/caching.rb +55 -0
- data/lib/faraday_stack/follow_redirects.rb +42 -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 +50 -0
- data/lib/faraday_stack/response_middleware.rb +64 -0
- data/lib/faraday_stack/response_xml.rb +12 -0
- data/test/caching_test.rb +120 -0
- data/test/factory_test.rb +28 -0
- data/test/follow_redirects_test.rb +38 -0
- data/test/response_middleware_test.rb +93 -0
- data/test/test_helper.rb +2 -0
- metadata +82 -0
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
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
|
+
To see how the default stack is built, see "[faraday_stack.rb][source]".
|
42
|
+
|
43
|
+
### Optional features:
|
44
|
+
|
45
|
+
* encode POST/PUT bodies as JSON:
|
46
|
+
|
47
|
+
conn.post(path, payload, :content_type => 'application/json')
|
48
|
+
|
49
|
+
* add `Instrumentation` middleware to instrument requests with ActiveSupport
|
50
|
+
|
51
|
+
conn.builder.insert_after Faraday::Response::RaiseError, FaradayStack::Instrumentation
|
52
|
+
|
53
|
+
* add `Caching` middleware to have GET responses cached
|
54
|
+
|
55
|
+
conn.builder.insert_before FaradayStack::ResponseJSON, FaradayStack::Caching do
|
56
|
+
ActiveSupport::Cache::FileStore.new 'tmp/cache',
|
57
|
+
:namespace => 'faraday', :expires_in => 3600
|
58
|
+
end
|
59
|
+
|
60
|
+
* mount [Rack::Cache][] through `RackCompatible` middleware for HTTP caching of responses
|
61
|
+
|
62
|
+
conn.builder.insert_after FaradayStack::FollowRedirects, FaradayStack::RackCompatible,
|
63
|
+
Rack::Cache::Context,
|
64
|
+
:metastore => "file:/var/cache/rack/meta",
|
65
|
+
:entitystore => "file:/var/cache/rack/body"
|
66
|
+
|
67
|
+
|
68
|
+
[faraday]: https://github.com/technoweenie/faraday
|
69
|
+
[source]: https://github.com/mislav/faraday-stack/blob/master/lib/faraday_stack.rb
|
70
|
+
[rack::cache]: http://rtomayko.github.com/rack-cache/
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'faraday_stack'
|
@@ -0,0 +1,52 @@
|
|
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
|
+
klass = nil
|
32
|
+
if url.is_a?(Hash) then options = url.dup
|
33
|
+
elsif url.is_a?(Class) then klass = url
|
34
|
+
else options = options.merge(:url => url)
|
35
|
+
end
|
36
|
+
|
37
|
+
klass ||= options.delete(:class) || Faraday::Connection
|
38
|
+
|
39
|
+
klass.new(options) do |builder|
|
40
|
+
builder.request :url_encoded
|
41
|
+
builder.request :json
|
42
|
+
yield builder if block_given?
|
43
|
+
builder.use ResponseXML, :content_type => /[+\/]xml$/
|
44
|
+
builder.use ResponseHTML, :content_type => 'text/html'
|
45
|
+
builder.use ResponseJSON, :content_type => /[+\/]json$/
|
46
|
+
builder.use ResponseJSON::MimeTypeFix, :content_type => /text\/(plain|javascript)/
|
47
|
+
builder.use FollowRedirects
|
48
|
+
builder.response :raise_error
|
49
|
+
builder.adapter Faraday.default_adapter
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'addressable/uri'
|
2
|
+
|
3
|
+
# feature-detect the bug
|
4
|
+
unless Addressable::URI.parse('/?a=1&b=2') === '/?b=2&a=1'
|
5
|
+
# fix `normalized_query` by sorting query key-value pairs
|
6
|
+
# (rejected: https://github.com/sporkmonger/addressable/issues/28)
|
7
|
+
class Addressable::URI
|
8
|
+
alias normalized_query_without_ordering_fix normalized_query
|
9
|
+
|
10
|
+
def normalized_query
|
11
|
+
fresh = @normalized_query.nil?
|
12
|
+
query = normalized_query_without_ordering_fix
|
13
|
+
if query && fresh
|
14
|
+
@normalized_query = query.split('&', -1).sort_by {|q| q[0..(q.index('=')||-1)] }.join('&')
|
15
|
+
else
|
16
|
+
query
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,55 @@
|
|
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].dup
|
28
|
+
if url.query && params_to_strip.any?
|
29
|
+
url.query_values = url.query_values.reject { |k,| params_to_strip.include? k }
|
30
|
+
end
|
31
|
+
url.normalize!
|
32
|
+
url.request_uri
|
33
|
+
end
|
34
|
+
|
35
|
+
def params_to_strip
|
36
|
+
@params_to_strip ||= Array(@options[:strip_params]).map { |p| p.to_s }
|
37
|
+
end
|
38
|
+
|
39
|
+
def cache_on_complete(env)
|
40
|
+
key = cache_key(env)
|
41
|
+
if cached_response = cache.read(key)
|
42
|
+
finalize_response(cached_response, env)
|
43
|
+
else
|
44
|
+
response = @app.call(env)
|
45
|
+
response.on_complete { cache.write(key, response) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def finalize_response(response, env)
|
50
|
+
response = env[:response] = response.dup if response.frozen?
|
51
|
+
response.apply_request env unless response.env[:method]
|
52
|
+
response
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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
|
+
REDIRECTS = [301, 302, 303, 307]
|
13
|
+
# default value for max redirects followed
|
14
|
+
FOLLOW_LIMIT = 3
|
15
|
+
|
16
|
+
def initialize(app, options = {})
|
17
|
+
super(app)
|
18
|
+
@options = options
|
19
|
+
@follow_limit = options[:limit] || FOLLOW_LIMIT
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
process_response(@app.call(env), @follow_limit)
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_response(response, follows)
|
27
|
+
response.on_complete do |env|
|
28
|
+
if redirect? response
|
29
|
+
raise RedirectLimitReached, response if follows.zero?
|
30
|
+
env[:url] += response['location']
|
31
|
+
env[:method] = :get
|
32
|
+
response = process_response(@app.call(env), follows - 1)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
def redirect?(response)
|
39
|
+
REDIRECTS.include? response.status
|
40
|
+
end
|
41
|
+
end
|
42
|
+
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,50 @@
|
|
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
|
+
|
32
|
+
# writes the correct MIME-type if the payload looks like JSON
|
33
|
+
class MimeTypeFix < ResponseMiddleware
|
34
|
+
def on_complete(env)
|
35
|
+
if process_response_type?(response_type(env)) and looks_like_json?(env)
|
36
|
+
old_type = env[:response_headers][CONTENT_TYPE]
|
37
|
+
new_type = 'application/json'
|
38
|
+
new_type << ';' << old_type.split(';', 2).last if old_type.index(';')
|
39
|
+
env[:response_headers][CONTENT_TYPE] = new_type
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
BRACKETS = %w- [ { -
|
44
|
+
|
45
|
+
def looks_like_json?(env)
|
46
|
+
parse_response?(env) and BRACKETS.include? env[:body][0,1]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
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,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('/', :user_agent => 'test').body
|
108
|
+
response = get('/', :user_agent => 'test')
|
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,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
class FactoryTest < Test::Unit::TestCase
|
5
|
+
extend Forwardable
|
6
|
+
def_delegator FaradayStack, :build
|
7
|
+
|
8
|
+
class CustomConnection < Faraday::Connection
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_default_connection
|
12
|
+
assert_instance_of Faraday::Connection, FaradayStack.default_connection
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_build_subclass
|
16
|
+
assert_instance_of CustomConnection, build(CustomConnection)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_build_url
|
20
|
+
conn = FaradayStack.build('http://example.com')
|
21
|
+
assert_equal 'example.com', conn.host
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_build_url_in_options
|
25
|
+
conn = FaradayStack.build(:url => 'http://example.com')
|
26
|
+
assert_equal 'example.com', conn.host
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
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
|
+
stub.get('/temp') { [307, {'Location' => '/found'}, ''] }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
extend Forwardable
|
19
|
+
def_delegators :@conn, :get, :post
|
20
|
+
|
21
|
+
def test_redirected
|
22
|
+
assert_equal 'fin', get('/').body
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_redirected_twice
|
26
|
+
assert_equal 'fin', post('/create').body
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_redirected_by_temp
|
30
|
+
assert_equal 'fin', get('/temp').body
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_redirect_limit
|
34
|
+
assert_raises FaradayStack::RedirectLimitReached do
|
35
|
+
get('/loop')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,93 @@
|
|
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('bad_mime') { [200, {'Content-Type' => 'text/javascript; charset=utf-8'}, "[1,2,3]"] }
|
11
|
+
stub.get('js') { [200, {'Content-Type' => 'text/javascript'}, "alert('hello')"] }
|
12
|
+
stub.get('blank') { [200, {'Content-Type' => 'application/json'}, ''] }
|
13
|
+
stub.get('nil') { [200, {'Content-Type' => 'application/json'}, nil] }
|
14
|
+
stub.get('bad_json') { [200, {'Content-Type' => 'application/json'}, '<body></body>']}
|
15
|
+
stub.get('non_json') { [200, {'Content-Type' => 'text/html'}, '<body></body>']}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def process_only(*types)
|
21
|
+
@conn.builder.swap @json_handler, @json_handler, :content_type => types
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_mime_type_fix(*types)
|
25
|
+
@conn.builder.insert_after @json_handler, FaradayStack::ResponseJSON::MimeTypeFix, :content_type => types
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_uses_json_to_parse_json_content
|
29
|
+
response = @conn.get('json')
|
30
|
+
assert response.success?
|
31
|
+
assert_equal [1,2,3], response.body
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_uses_json_to_parse_json_content_conditional
|
35
|
+
process_only('application/json')
|
36
|
+
response = @conn.get('json')
|
37
|
+
assert response.success?
|
38
|
+
assert_equal [1,2,3], response.body
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_uses_json_to_parse_json_content_conditional_with_regexp
|
42
|
+
process_only(%r{/(x-)?json$})
|
43
|
+
response = @conn.get('json')
|
44
|
+
assert response.success?
|
45
|
+
assert_equal [1,2,3], response.body
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_uses_json_to_skip_blank_content
|
49
|
+
response = @conn.get('blank')
|
50
|
+
assert response.success?
|
51
|
+
assert_nil response.body
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_uses_json_to_skip_nil_content
|
55
|
+
response = @conn.get('nil')
|
56
|
+
assert response.success?
|
57
|
+
assert_nil response.body
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_uses_json_to_raise_Faraday_Error_Parsing_with_no_json_content
|
61
|
+
assert_raises Faraday::Error::ParsingError do
|
62
|
+
@conn.get('bad_json')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_non_json_response
|
67
|
+
assert_raises Faraday::Error::ParsingError do
|
68
|
+
@conn.get('non_json')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_non_json_response_conditional
|
73
|
+
process_only('application/json')
|
74
|
+
response = @conn.get('non_json')
|
75
|
+
assert_equal 'text/html', response.headers['Content-Type']
|
76
|
+
assert_equal '<body></body>', response.body
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_mime_type_fix
|
80
|
+
process_only('application/json')
|
81
|
+
with_mime_type_fix
|
82
|
+
response = @conn.get('bad_mime')
|
83
|
+
assert_equal 'application/json; charset=utf-8', response.headers['Content-Type']
|
84
|
+
assert_equal [1,2,3], response.body
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_mime_type_fix_conditional
|
88
|
+
process_only('application/json')
|
89
|
+
with_mime_type_fix
|
90
|
+
response = @conn.get('js')
|
91
|
+
assert_equal 'text/javascript', response.headers['Content-Type']
|
92
|
+
end
|
93
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jeffersongirao_faraday-stack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.5
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mislav Marohnić
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-01-15 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: faraday
|
16
|
+
requirement: &2157616900 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.6'
|
22
|
+
- - <
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '0.8'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: *2157616900
|
28
|
+
description:
|
29
|
+
email: mislav.marohnic@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- Rakefile
|
35
|
+
- lib/faraday-stack.rb
|
36
|
+
- lib/faraday_stack/addressable_patch.rb
|
37
|
+
- lib/faraday_stack/caching.rb
|
38
|
+
- lib/faraday_stack/follow_redirects.rb
|
39
|
+
- lib/faraday_stack/instrumentation.rb
|
40
|
+
- lib/faraday_stack/rack_compatible.rb
|
41
|
+
- lib/faraday_stack/response_html.rb
|
42
|
+
- lib/faraday_stack/response_json.rb
|
43
|
+
- lib/faraday_stack/response_middleware.rb
|
44
|
+
- lib/faraday_stack/response_xml.rb
|
45
|
+
- lib/faraday_stack.rb
|
46
|
+
- test/caching_test.rb
|
47
|
+
- test/factory_test.rb
|
48
|
+
- test/follow_redirects_test.rb
|
49
|
+
- test/response_middleware_test.rb
|
50
|
+
- test/test_helper.rb
|
51
|
+
- README.md
|
52
|
+
homepage: https://github.com/jeffersongirao/faraday-stack
|
53
|
+
licenses: []
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
hash: -1051454488374554944
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
hash: -1051454488374554944
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project: jeffersongirao_faraday-stack
|
78
|
+
rubygems_version: 1.8.10
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: A Temp fork of faraday-stack
|
82
|
+
test_files: []
|