rack-client 0.1.1 → 0.3.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/History.txt +2 -2
- data/README.textile +2 -2
- data/Rakefile +11 -5
- data/demo/demo_spec.rb +3 -3
- data/lib/rack/client.rb +29 -25
- data/lib/rack/client/adapter.rb +6 -0
- data/lib/rack/client/adapter/base.rb +57 -0
- data/lib/rack/client/adapter/simple.rb +49 -0
- data/lib/rack/client/auth/abstract/challenge.rb +53 -0
- data/lib/rack/client/auth/basic.rb +57 -0
- data/lib/rack/client/auth/digest/challenge.rb +38 -0
- data/lib/rack/client/auth/digest/md5.rb +78 -0
- data/lib/rack/client/auth/digest/params.rb +10 -0
- data/lib/rack/client/body.rb +12 -0
- data/lib/rack/client/cache.rb +19 -0
- data/lib/rack/client/cache/cachecontrol.rb +195 -0
- data/lib/rack/client/cache/context.rb +95 -0
- data/lib/rack/client/cache/entitystore.rb +77 -0
- data/lib/rack/client/cache/key.rb +51 -0
- data/lib/rack/client/cache/metastore.rb +133 -0
- data/lib/rack/client/cache/options.rb +147 -0
- data/lib/rack/client/cache/request.rb +46 -0
- data/lib/rack/client/cache/response.rb +62 -0
- data/lib/rack/client/cache/storage.rb +43 -0
- data/lib/rack/client/cookie_jar.rb +17 -0
- data/lib/rack/client/cookie_jar/context.rb +59 -0
- data/lib/rack/client/cookie_jar/cookie.rb +59 -0
- data/lib/rack/client/cookie_jar/cookiestore.rb +36 -0
- data/lib/rack/client/cookie_jar/options.rb +43 -0
- data/lib/rack/client/cookie_jar/request.rb +15 -0
- data/lib/rack/client/cookie_jar/response.rb +16 -0
- data/lib/rack/client/cookie_jar/storage.rb +34 -0
- data/lib/rack/client/dual_band.rb +13 -0
- data/lib/rack/client/follow_redirects.rb +47 -20
- data/lib/rack/client/handler.rb +10 -0
- data/lib/rack/client/handler/em-http.rb +66 -0
- data/lib/rack/client/handler/excon.rb +50 -0
- data/lib/rack/client/handler/net_http.rb +85 -0
- data/lib/rack/client/handler/typhoeus.rb +62 -0
- data/lib/rack/client/headers.rb +49 -0
- data/lib/rack/client/parser.rb +18 -0
- data/lib/rack/client/parser/base.rb +25 -0
- data/lib/rack/client/parser/body_collection.rb +50 -0
- data/lib/rack/client/parser/context.rb +15 -0
- data/lib/rack/client/parser/json.rb +54 -0
- data/lib/rack/client/parser/middleware.rb +8 -0
- data/lib/rack/client/parser/request.rb +21 -0
- data/lib/rack/client/parser/response.rb +19 -0
- data/lib/rack/client/parser/yaml.rb +52 -0
- data/lib/rack/client/response.rb +9 -0
- data/lib/rack/client/version.rb +5 -0
- data/spec/apps/example.org.ru +47 -3
- data/spec/auth/basic_spec.rb +69 -0
- data/spec/auth/digest/md5_spec.rb +69 -0
- data/spec/cache_spec.rb +40 -0
- data/spec/cookie_jar_spec.rb +37 -0
- data/spec/endpoint_spec.rb +4 -13
- data/spec/follow_redirect_spec.rb +27 -0
- data/spec/handler/async_api_spec.rb +69 -0
- data/spec/handler/em_http_spec.rb +22 -0
- data/spec/handler/excon_spec.rb +7 -0
- data/spec/handler/net_http_spec.rb +8 -0
- data/spec/handler/sync_api_spec.rb +55 -0
- data/spec/handler/typhoeus_spec.rb +22 -0
- data/spec/middleware_helper.rb +37 -0
- data/spec/middleware_spec.rb +48 -5
- data/spec/parser/json_spec.rb +22 -0
- data/spec/parser/yaml_spec.rb +22 -0
- data/spec/server_helper.rb +72 -0
- data/spec/spec_helper.rb +17 -3
- metadata +86 -31
- data/lib/rack/client/auth.rb +0 -13
- data/lib/rack/client/http.rb +0 -77
- data/spec/auth_spec.rb +0 -22
- data/spec/core_spec.rb +0 -123
- data/spec/redirect_spec.rb +0 -12
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
module Client
|
6
|
+
module Handler
|
7
|
+
class NetHTTP
|
8
|
+
include Rack::Client::DualBand
|
9
|
+
|
10
|
+
class << self
|
11
|
+
extend Forwardable
|
12
|
+
def_delegator :new, :call
|
13
|
+
end
|
14
|
+
|
15
|
+
def sync_call(env)
|
16
|
+
request = Rack::Request.new(env)
|
17
|
+
|
18
|
+
connection_for(request).request(net_request_for(request), body_for(request)) do |net_response|
|
19
|
+
return parse(net_response).finish
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def async_call(env)
|
24
|
+
request = Rack::Request.new(env)
|
25
|
+
|
26
|
+
connection_for(request).request(net_request_for(request), body_for(request)) do |net_response|
|
27
|
+
yield parse(net_response).finish
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def connection_for(request)
|
32
|
+
connection = Net::HTTP.new(request.host, request.port)
|
33
|
+
|
34
|
+
if request.scheme == 'https'
|
35
|
+
connection.use_ssl = true
|
36
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
37
|
+
end
|
38
|
+
|
39
|
+
connection.start
|
40
|
+
connection
|
41
|
+
end
|
42
|
+
|
43
|
+
def net_request_for(request)
|
44
|
+
klass = case request.request_method
|
45
|
+
when 'DELETE' then Net::HTTP::Delete
|
46
|
+
when 'GET' then Net::HTTP::Get
|
47
|
+
when 'HEAD' then Net::HTTP::Head
|
48
|
+
when 'POST' then Net::HTTP::Post
|
49
|
+
when 'PUT' then Net::HTTP::Put
|
50
|
+
end
|
51
|
+
|
52
|
+
klass.new(request.path, Headers.from(request.env).to_http)
|
53
|
+
end
|
54
|
+
|
55
|
+
def body_for(request)
|
56
|
+
case request.body
|
57
|
+
when StringIO then request.body.string
|
58
|
+
when IO then request.body.read
|
59
|
+
when Array then request.body.to_s
|
60
|
+
when String then request.body
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse(net_response)
|
65
|
+
body = (net_response.body.nil? || net_response.body.empty?) ? [] : StringIO.new(net_response.body)
|
66
|
+
Response.new(net_response.code.to_i, parse_headers(net_response), body)
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_headers(net_response)
|
70
|
+
headers = Headers.new
|
71
|
+
|
72
|
+
net_response.each do |(k,v)|
|
73
|
+
headers.update(k => v)
|
74
|
+
end
|
75
|
+
|
76
|
+
headers.to_http
|
77
|
+
end
|
78
|
+
|
79
|
+
def connections
|
80
|
+
@connections ||= {}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Client
|
5
|
+
module Handler
|
6
|
+
class Typhoeus
|
7
|
+
include Rack::Client::DualBand
|
8
|
+
|
9
|
+
def initialize(url, hydra = Typhoeus::Hydra.new)
|
10
|
+
@uri, @hydra = URI.parse(url), hydra
|
11
|
+
end
|
12
|
+
|
13
|
+
def async_call(env)
|
14
|
+
rack_request = Rack::Request.new(env)
|
15
|
+
|
16
|
+
typhoeus_request = request_for(rack_request)
|
17
|
+
|
18
|
+
typhoeus_request.on_complete do |response|
|
19
|
+
yield parse(response).finish
|
20
|
+
end
|
21
|
+
|
22
|
+
@hydra.queue typhoeus_request
|
23
|
+
end
|
24
|
+
|
25
|
+
def sync_call(env)
|
26
|
+
rack_request = Rack::Request.new(env)
|
27
|
+
|
28
|
+
parse(process(rack_request)).finish
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse(typhoeus_response)
|
32
|
+
body = (typhoeus_response.body.nil? || typhoeus_response.body.empty?) ? [] : StringIO.new(typhoeus_response.body)
|
33
|
+
Response.new(typhoeus_response.code, Headers.new(typhoeus_response.headers_hash).to_http, body)
|
34
|
+
end
|
35
|
+
|
36
|
+
def request_for(rack_request)
|
37
|
+
::Typhoeus::Request.new((@uri + rack_request.path).to_s, params_for(rack_request))
|
38
|
+
end
|
39
|
+
|
40
|
+
def process(rack_request)
|
41
|
+
::Typhoeus::Request.run((@uri + rack_request.path).to_s, params_for(rack_request))
|
42
|
+
end
|
43
|
+
|
44
|
+
def params_for(rack_request)
|
45
|
+
{
|
46
|
+
:method => rack_request.request_method.downcase.to_sym,
|
47
|
+
:headers => Headers.from(rack_request.env).to_http,
|
48
|
+
:params => {}
|
49
|
+
}.merge(body_params_for(rack_request))
|
50
|
+
end
|
51
|
+
|
52
|
+
def body_params_for(rack_request)
|
53
|
+
unless %w[ HEAD GET ].include? rack_request.request_method
|
54
|
+
{:body => rack_request.body.string}
|
55
|
+
else
|
56
|
+
{}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rack
|
2
|
+
module Client
|
3
|
+
class Headers < Hash
|
4
|
+
def self.from(env)
|
5
|
+
new env.reject {|(header,_)| header !~ %r'^HTTP_' unless %w( CONTENT_TYPE CONTENT_LENGTH ).include?(header) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(headers = {})
|
9
|
+
super
|
10
|
+
merge!(headers)
|
11
|
+
end
|
12
|
+
|
13
|
+
def clean(header)
|
14
|
+
header.gsub(/HTTP_/, '').gsub('_', '-').gsub(/(\w+)/) do |matches|
|
15
|
+
matches.downcase.sub(/^./) do |char|
|
16
|
+
char.upcase
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_http
|
22
|
+
self.inject({}) {|h,(header,value)| h.update(clean(header) => value) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_env
|
26
|
+
self.inject({}) {|h,(header,value)| h.update((rackish?(header) ? header : rackify(header)) => value) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def rackish?(header)
|
30
|
+
case header
|
31
|
+
when 'CONTENT_TYPE', 'CONTENT_LENGTH' then true
|
32
|
+
when /^rack[-.]/ then true
|
33
|
+
when /^HTTP_/ then true
|
34
|
+
else false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def rackify(original)
|
39
|
+
header = original.upcase.gsub('-', '_')
|
40
|
+
|
41
|
+
case header
|
42
|
+
when 'CONTENT_TYPE', 'CONTENT_LENGTH' then header
|
43
|
+
when /^HTTP_/ then header
|
44
|
+
else "HTTP_#{header}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rack
|
2
|
+
module Client
|
3
|
+
module Parser
|
4
|
+
autoload :JSON, 'rack/client/parser/json'
|
5
|
+
autoload :YAML, 'rack/client/parser/yaml'
|
6
|
+
|
7
|
+
def self.new(app, &b)
|
8
|
+
Context.new(app, &b)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'rack/client/parser/base'
|
15
|
+
require 'rack/client/parser/body_collection'
|
16
|
+
require 'rack/client/parser/context'
|
17
|
+
require 'rack/client/parser/request'
|
18
|
+
require 'rack/client/parser/response'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rack
|
2
|
+
module Client
|
3
|
+
module Parser
|
4
|
+
class Base
|
5
|
+
CONTENT_TYPE = %r'^([^/]+)/([^;]+)\s?(?:;\s?(.*))?$'
|
6
|
+
|
7
|
+
@@type_table ||= Hash.new {|h,k| h[k] = Hash.new {|hh,kk| hh[kk] = {} } }
|
8
|
+
|
9
|
+
def self.content_type(type, subtype, *parameters)
|
10
|
+
type_table[type][subtype][parameters] = self
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.type_table
|
14
|
+
@@type_table
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.lookup(content_type)
|
18
|
+
type, subtype, *parameters = content_type.scan(CONTENT_TYPE).first
|
19
|
+
|
20
|
+
type_table[type][subtype][parameters.compact]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Rack
|
2
|
+
module Client
|
3
|
+
class BodyCollection
|
4
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
|
5
|
+
|
6
|
+
def initialize(&load_with_proc)
|
7
|
+
raise ArgumentException, 'BodyCollection must be initialized with a block' unless block_given?
|
8
|
+
|
9
|
+
@loaded = false
|
10
|
+
@finished = false
|
11
|
+
@load_with_proc = load_with_proc
|
12
|
+
@callbacks = []
|
13
|
+
@array = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&block)
|
17
|
+
@callbacks << block
|
18
|
+
@array.each {|a| yield(*a) }
|
19
|
+
|
20
|
+
lazy_load unless finished?
|
21
|
+
ensure
|
22
|
+
@callbacks.delete(block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def lazy_load
|
26
|
+
until finished?
|
27
|
+
@load_with_proc[self]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def <<(value)
|
32
|
+
@array << value
|
33
|
+
@callbacks.each {|cb| cb[value] }
|
34
|
+
end
|
35
|
+
|
36
|
+
def finished?
|
37
|
+
@finished
|
38
|
+
end
|
39
|
+
|
40
|
+
def finish
|
41
|
+
@finished = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(sym, *a, &b)
|
45
|
+
lazy_load
|
46
|
+
@array.send(sym, *a, &b)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Client
|
5
|
+
module Parser
|
6
|
+
class JSON < Parser::Base
|
7
|
+
|
8
|
+
content_type 'application', 'json'
|
9
|
+
|
10
|
+
def encode(input)
|
11
|
+
output = StringIO.new
|
12
|
+
|
13
|
+
input.each do |object|
|
14
|
+
::JSON.dump(object, output)
|
15
|
+
end
|
16
|
+
|
17
|
+
output
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode(body)
|
21
|
+
BodyCollection.new do |collection|
|
22
|
+
begin
|
23
|
+
data = if body.respond_to? :read
|
24
|
+
body.read
|
25
|
+
elsif body.respond_to? :to_path
|
26
|
+
File.read(body.to_path)
|
27
|
+
else
|
28
|
+
io = StringIO.new
|
29
|
+
|
30
|
+
body.each do |part|
|
31
|
+
io << part
|
32
|
+
end
|
33
|
+
|
34
|
+
io.rewind
|
35
|
+
io.read
|
36
|
+
end
|
37
|
+
|
38
|
+
case result = ::JSON.parse(data)
|
39
|
+
when Array then result.each {|object| collection << object }
|
40
|
+
else collection << result
|
41
|
+
end
|
42
|
+
|
43
|
+
collection.finish
|
44
|
+
ensure
|
45
|
+
body.close if body.respond_to? :close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
Yaml = YAML
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Rack
|
2
|
+
module Client
|
3
|
+
module Parser
|
4
|
+
class Request < Rack::Request
|
5
|
+
def initialize(*)
|
6
|
+
super
|
7
|
+
|
8
|
+
if @env['rack-client.body_collection'] && content_type
|
9
|
+
parse_input(@env['rack-client.body_collection'])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse_input(collection)
|
14
|
+
if parser = Base.lookup(content_type)
|
15
|
+
@env['rack.input'] = parser.new.encode(collection)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Rack
|
2
|
+
module Client
|
3
|
+
module Parser
|
4
|
+
class Response < Rack::Client::Response
|
5
|
+
def finish(*)
|
6
|
+
super
|
7
|
+
ensure
|
8
|
+
parse_body_as(headers['Content-Type']) if headers['Content-Type']
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse_body_as(content_type)
|
12
|
+
if parser = Base.lookup(content_type)
|
13
|
+
headers['rack-client.body_collection'] = parser.new.decode(body)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module Client
|
5
|
+
module Parser
|
6
|
+
class YAML < Parser::Base
|
7
|
+
|
8
|
+
content_type 'application', 'x-yaml'
|
9
|
+
|
10
|
+
def encode(input)
|
11
|
+
output = StringIO.new
|
12
|
+
|
13
|
+
input.each do |object|
|
14
|
+
::YAML.dump(object, output)
|
15
|
+
end
|
16
|
+
|
17
|
+
output
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode(body)
|
21
|
+
BodyCollection.new do |collection|
|
22
|
+
begin
|
23
|
+
io = if body.respond_to? :to_path
|
24
|
+
File.open(body.to_path, 'r')
|
25
|
+
else
|
26
|
+
io = StringIO.new
|
27
|
+
|
28
|
+
body.each do |part|
|
29
|
+
io << part
|
30
|
+
end
|
31
|
+
|
32
|
+
io.rewind
|
33
|
+
io
|
34
|
+
end
|
35
|
+
|
36
|
+
::YAML.load_documents(io) do |object|
|
37
|
+
collection << object
|
38
|
+
end
|
39
|
+
|
40
|
+
collection.finish
|
41
|
+
ensure
|
42
|
+
io.close if io.respond_to? :close
|
43
|
+
body.close if body.respond_to? :close
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Yaml = YAML
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|