weary 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +9 -4
- data/Gemfile.lock +36 -20
- data/README.md +11 -3
- data/lib/weary/adapter.rb +25 -1
- data/lib/weary/adapters/excon.rb +36 -0
- data/lib/weary/adapters/net_http.rb +29 -47
- data/lib/weary/adapters/typhoeus.rb +32 -0
- data/lib/weary/client.rb +2 -29
- data/lib/weary/deferred.rb +1 -1
- data/lib/weary/env.rb +6 -1
- data/lib/weary/middleware/timeout.rb +22 -0
- data/lib/weary/request.rb +14 -23
- data/lib/weary/requestable.rb +69 -0
- data/lib/weary/resource.rb +35 -32
- data/lib/weary/response.rb +6 -2
- data/lib/weary/route.rb +6 -1
- data/lib/weary/version.rb +1 -1
- data/spec/spec_helper.rb +2 -1
- data/spec/support/shared_examples_for_a_rack_app.rb +12 -0
- data/spec/support/shared_examples_for_a_requestable.rb +77 -0
- data/spec/support/shared_examples_for_an_adapter.rb +16 -0
- data/spec/weary/adapter_spec.rb +8 -15
- data/spec/weary/adapters/excon_spec.rb +86 -0
- data/spec/weary/adapters/nethttp_spec.rb +63 -54
- data/spec/weary/adapters/typhoeus_spec.rb +68 -0
- data/spec/weary/client_spec.rb +4 -0
- data/spec/weary/deferred_spec.rb +1 -1
- data/spec/weary/middleware/basic_auth_spec.rb +2 -2
- data/spec/weary/middleware/timeout_spec.rb +24 -0
- data/spec/weary/request_spec.rb +6 -42
- data/spec/weary/requestable_spec.rb +9 -0
- data/spec/weary/resource_spec.rb +6 -18
- data/spec/weary/response_spec.rb +10 -0
- data/weary.gemspec +2 -2
- metadata +56 -15
data/lib/weary/request.rb
CHANGED
@@ -2,6 +2,7 @@ require 'addressable/uri'
|
|
2
2
|
require 'future'
|
3
3
|
require 'rack'
|
4
4
|
|
5
|
+
require 'weary/requestable'
|
5
6
|
require 'weary/env'
|
6
7
|
require 'weary/adapter'
|
7
8
|
|
@@ -15,12 +16,13 @@ module Weary
|
|
15
16
|
# Rack application. Because the Request performs so much manipulation on
|
16
17
|
# the Rack env, you can attach middleware to it to act on its mutated env.
|
17
18
|
class Request
|
19
|
+
include Weary::Requestable
|
20
|
+
|
18
21
|
attr_reader :uri
|
19
22
|
|
20
23
|
def initialize(url, method='GET')
|
21
24
|
self.uri = url
|
22
25
|
self.method = method
|
23
|
-
@middlewares = []
|
24
26
|
yield self if block_given?
|
25
27
|
end
|
26
28
|
|
@@ -38,7 +40,7 @@ module Weary
|
|
38
40
|
# Returns an Array of three items; a Rack tuple.
|
39
41
|
def call(environment)
|
40
42
|
app = adapter.new
|
41
|
-
middlewares = @middlewares
|
43
|
+
middlewares = @middlewares || []
|
42
44
|
stack = Rack::Builder.new do
|
43
45
|
middlewares.each do |middleware|
|
44
46
|
klass, *args = middleware
|
@@ -64,20 +66,11 @@ module Weary
|
|
64
66
|
@method = verb.to_s.upcase
|
65
67
|
end
|
66
68
|
|
67
|
-
def headers(hash=nil)
|
68
|
-
@headers = hash unless hash.nil?
|
69
|
-
@headers ||= {}
|
70
|
-
end
|
71
|
-
|
72
|
-
def user_agent(agent)
|
73
|
-
headers.update 'User-Agent' => agent
|
74
|
-
end
|
75
|
-
|
76
69
|
def params(parameters=nil)
|
77
70
|
if !parameters.nil?
|
78
71
|
if ["POST", "PUT"].include? method
|
79
72
|
@body = query_params_from_hash(parameters)
|
80
|
-
body
|
73
|
+
body stringio_encode(@body)
|
81
74
|
use Weary::Middleware::ContentType
|
82
75
|
else
|
83
76
|
uri.query_values = parameters
|
@@ -89,18 +82,13 @@ module Weary
|
|
89
82
|
|
90
83
|
def json(parameters)
|
91
84
|
json = MultiJson.encode(parameters)
|
92
|
-
body
|
85
|
+
body stringio_encode(json)
|
93
86
|
json
|
94
87
|
end
|
95
88
|
|
96
89
|
def body(io=nil)
|
97
90
|
@attachment = io unless io.nil?
|
98
|
-
@attachment ||=
|
99
|
-
end
|
100
|
-
|
101
|
-
def adapter(connection=nil)
|
102
|
-
@connection = connection unless connection.nil?
|
103
|
-
@connection ||= Weary::Adapter::NetHttp
|
91
|
+
@attachment ||= stringio_encode("")
|
104
92
|
end
|
105
93
|
|
106
94
|
def basic_auth(*credentials)
|
@@ -133,10 +121,6 @@ module Weary
|
|
133
121
|
end
|
134
122
|
end
|
135
123
|
|
136
|
-
def use(middleware, *args, &block)
|
137
|
-
@middlewares << [middleware, args.compact, block]
|
138
|
-
end
|
139
|
-
|
140
124
|
private
|
141
125
|
|
142
126
|
def query_params_from_hash(hash)
|
@@ -153,5 +137,12 @@ module Weary
|
|
153
137
|
'rack.run_once' => false }
|
154
138
|
end
|
155
139
|
|
140
|
+
def stringio_encode(content)
|
141
|
+
io = StringIO.new(content)
|
142
|
+
io.binmode
|
143
|
+
io.set_encoding "ASCII-8BIT" if io.respond_to? :set_encoding
|
144
|
+
io
|
145
|
+
end
|
146
|
+
|
156
147
|
end
|
157
148
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Weary
|
2
|
+
module Requestable
|
3
|
+
|
4
|
+
# An accessor to set a Weary::Adapter to use to forward the connection.
|
5
|
+
# When a request is made, it will be passed along to this adapter to
|
6
|
+
# get the eventual Response. Defaults to Weary::Adapter::NetHttp.
|
7
|
+
#
|
8
|
+
# connection - An optional Weary::Adapter.
|
9
|
+
#
|
10
|
+
# Returns the Weary::Adapter.
|
11
|
+
def adapter(connection=nil)
|
12
|
+
@connection = connection unless connection.nil?
|
13
|
+
@connection ||= Weary::Adapter::NetHttp
|
14
|
+
end
|
15
|
+
|
16
|
+
# An accessor to set HTTP request headers.
|
17
|
+
#
|
18
|
+
# hash - An optional Hash of key/value pairs that are sent as HTTP
|
19
|
+
# request headers when a resource's request is performed.
|
20
|
+
#
|
21
|
+
# Returns a Hash of the headers.
|
22
|
+
def headers(hash=nil)
|
23
|
+
@headers = hash unless hash.nil?
|
24
|
+
@headers ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Send a Rack middleware to be used by the Request.
|
28
|
+
#
|
29
|
+
# middleware - An object that implements the rack middleware interface.
|
30
|
+
# args - Zero or more optional arguments to send to the middleware.
|
31
|
+
# block - An optional block to send to the middleware.
|
32
|
+
#
|
33
|
+
# Returns an Array of middlewares.
|
34
|
+
def use(middleware, *args, &block)
|
35
|
+
@middlewares ||= []
|
36
|
+
@middlewares << [middleware, args.compact, block]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Convenience method to set a User Agent Header
|
40
|
+
#
|
41
|
+
# agent - A user agent String. See Weary::USER_AGENTS for some help
|
42
|
+
#
|
43
|
+
# Returns the updated headers Hash.
|
44
|
+
def user_agent(agent)
|
45
|
+
headers.update 'User-Agent' => agent
|
46
|
+
end
|
47
|
+
|
48
|
+
# Should the Request use one or more Rack::Middleware?
|
49
|
+
def has_middleware?
|
50
|
+
!@middlewares.nil? && !@middlewares.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Pass Requestable values on to another Requestable object
|
54
|
+
# (including Middleware).
|
55
|
+
#
|
56
|
+
# requestable - Another Requestable object.
|
57
|
+
#
|
58
|
+
# Returns the Requestable object.
|
59
|
+
def pass_values_onto_requestable(requestable)
|
60
|
+
requestable.headers self.headers
|
61
|
+
requestable.adapter self.adapter
|
62
|
+
if has_middleware?
|
63
|
+
@middlewares.each {|middleware| requestable.use *middleware }
|
64
|
+
end
|
65
|
+
requestable
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
data/lib/weary/resource.rb
CHANGED
@@ -7,6 +7,8 @@ module Weary
|
|
7
7
|
# retrieve the resource and some constraints on the parameters necessary
|
8
8
|
# to complete the request.
|
9
9
|
class Resource
|
10
|
+
include Weary::Requestable
|
11
|
+
|
10
12
|
UnmetRequirementsError = Class.new(StandardError)
|
11
13
|
|
12
14
|
attr_reader :method
|
@@ -49,24 +51,6 @@ module Weary
|
|
49
51
|
@defaults ||= {}
|
50
52
|
end
|
51
53
|
|
52
|
-
# An accessor to set HTTP request headers.
|
53
|
-
def headers(hash=nil)
|
54
|
-
@headers = hash unless hash.nil?
|
55
|
-
@headers ||= {}
|
56
|
-
end
|
57
|
-
|
58
|
-
# Set up a Rack::Middleware to be used by the Request (which is
|
59
|
-
# Rack-friendly).
|
60
|
-
def use(middleware, *args, &block)
|
61
|
-
@middlewares ||= []
|
62
|
-
@middlewares << [middleware, args.compact, block]
|
63
|
-
end
|
64
|
-
|
65
|
-
# Convenience method to set a User Agent Header
|
66
|
-
def user_agent(agent)
|
67
|
-
headers.update 'User-Agent' => agent
|
68
|
-
end
|
69
|
-
|
70
54
|
# Tell the Resource to anticipate Basic Authentication. Optionally,
|
71
55
|
# tell the Resource what parameters to use as credentials.
|
72
56
|
#
|
@@ -96,7 +80,7 @@ module Weary
|
|
96
80
|
|
97
81
|
# The keys expected as parameters to the Request.
|
98
82
|
def expected_params
|
99
|
-
defaults.keys
|
83
|
+
(defaults.keys | optional | required).map(&:to_s).uniq
|
100
84
|
end
|
101
85
|
|
102
86
|
# Does the Resource expect this parameter to be used to make the Request?
|
@@ -122,29 +106,48 @@ module Weary
|
|
122
106
|
# Raises a Weary::Resource::UnmetRequirementsError if the requirements
|
123
107
|
# are not met.
|
124
108
|
def request(params={})
|
125
|
-
params
|
126
|
-
params.update(defaults)
|
109
|
+
normalize_parameters params
|
127
110
|
raise UnmetRequirementsError, "Required parameters: #{requirements}" \
|
128
111
|
unless meets_requirements? params
|
129
|
-
credentials = pull_credentials params
|
130
|
-
|
131
|
-
request =
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
112
|
+
credentials = pull_credentials params
|
113
|
+
pairs = pull_url_pairs params
|
114
|
+
request = construct_request expand_url(pairs), params, credentials
|
115
|
+
yield request if block_given?
|
116
|
+
request
|
117
|
+
end
|
118
|
+
alias build request
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# Private: Build the Request object with the given Resource parameters.
|
123
|
+
def construct_request(uri, params, credentials=[])
|
124
|
+
Weary::Request.new uri, @method do |r|
|
125
|
+
pass_values_onto_requestable(r)
|
136
126
|
if !expected_params.empty?
|
137
127
|
r.params params.reject {|k,v| !expects? k }
|
138
128
|
end
|
139
129
|
r.send @authenticates, *credentials if authenticates?
|
140
130
|
end
|
141
|
-
yield request if block_given?
|
142
|
-
request
|
143
131
|
end
|
144
|
-
alias build request
|
145
132
|
|
133
|
+
# Private: For a set of parameters passed in to build a Request, delete
|
134
|
+
# those with no values, and merge them with the defaults.
|
135
|
+
def normalize_parameters(params)
|
136
|
+
params.delete_if {|k,v| v.nil? || v.to_s.empty? }
|
137
|
+
params.update(defaults)
|
138
|
+
params
|
139
|
+
end
|
146
140
|
|
147
|
-
|
141
|
+
# Private: Expand the url template with the passed pairs to get the
|
142
|
+
# final url.
|
143
|
+
def expand_url(pairs)
|
144
|
+
url.expand Hash[pairs]
|
145
|
+
end
|
146
|
+
|
147
|
+
# Private: Separate the parameters needed to construct a url.
|
148
|
+
def pull_url_pairs(params)
|
149
|
+
url.keys.map {|k| [k, params.delete(k) || params.delete(k.to_sym)] }
|
150
|
+
end
|
148
151
|
|
149
152
|
# Private: Separate the credentials for authentication from the other
|
150
153
|
# request parameters.
|
data/lib/weary/response.rb
CHANGED
@@ -52,8 +52,12 @@ module Weary
|
|
52
52
|
|
53
53
|
def parse
|
54
54
|
raise "The response does not contain a body" if body.nil? || body.empty?
|
55
|
-
|
56
|
-
|
55
|
+
if block_given?
|
56
|
+
yield body, content_type
|
57
|
+
else
|
58
|
+
raise "Unable to parse Content-Type: #{content_type}" unless content_type =~ /json($|;.*)/
|
59
|
+
MultiJson.decode body
|
60
|
+
end
|
57
61
|
end
|
58
62
|
end
|
59
63
|
end
|
data/lib/weary/route.rb
CHANGED
@@ -39,7 +39,12 @@ module Weary
|
|
39
39
|
|
40
40
|
def select_by_url(url, set=@resources)
|
41
41
|
set.select do |resource|
|
42
|
-
|
42
|
+
mapping = resource.url.extract(url)
|
43
|
+
if mapping.respond_to?(:empty?) && !mapping.empty?
|
44
|
+
!mapping.values_at(*resource.url.variables).compact.empty?
|
45
|
+
else
|
46
|
+
!mapping.nil?
|
47
|
+
end
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
data/lib/weary/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -18,6 +18,18 @@ shared_examples_for "a Rack application" do
|
|
18
18
|
subject.call(env).length.should be 3
|
19
19
|
end
|
20
20
|
|
21
|
+
it "passes Rack::Lint" do
|
22
|
+
rack_defaults = {
|
23
|
+
'rack.version' => Rack::VERSION,
|
24
|
+
'rack.errors' => $stderr,
|
25
|
+
'rack.multithread' => true,
|
26
|
+
'rack.multiprocess' => false,
|
27
|
+
'rack.run_once' => false
|
28
|
+
}
|
29
|
+
lint = Rack::Lint.new subject
|
30
|
+
expect { lint.call(rack_defaults.update(env)) }.to_not raise_error Rack::Lint::LintError
|
31
|
+
end
|
32
|
+
|
21
33
|
describe "the status" do
|
22
34
|
let(:status) { subject.call(env).first }
|
23
35
|
|
@@ -0,0 +1,77 @@
|
|
1
|
+
shared_examples_for "a Requestable" do
|
2
|
+
|
3
|
+
describe "#adapter" do
|
4
|
+
it "sets a new adapter to set the connection" do
|
5
|
+
klass = Class.new { include Weary::Adapter }
|
6
|
+
subject.adapter(klass)
|
7
|
+
subject.adapter.should be klass
|
8
|
+
end
|
9
|
+
|
10
|
+
it "defaults to the Net::HTTP adapter" do
|
11
|
+
subject.adapter.should be Weary::Adapter::NetHttp
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#headers" do
|
16
|
+
it "prepares headers for the request" do
|
17
|
+
subject.headers 'User-Agent' => 'RSpec'
|
18
|
+
subject.headers.should eql 'User-Agent' => 'RSpec'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#use" do
|
23
|
+
it "adds a middleware to the stack" do
|
24
|
+
subject.use Rack::Runtime, "RSpec"
|
25
|
+
stack = subject.instance_variable_get :@middlewares
|
26
|
+
stack.first.should include(Rack::Runtime, ["RSpec"])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#user_agent" do
|
31
|
+
it "updates the #headers hash with a User-Agent" do
|
32
|
+
subject.user_agent 'RSpec'
|
33
|
+
subject.headers.should eql 'User-Agent' => 'RSpec'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#has_middleware?" do
|
38
|
+
it "is true if the Request is set up to use a Middleware" do
|
39
|
+
require 'rack/lobster'
|
40
|
+
subject.use Rack::Lobster
|
41
|
+
subject.should have_middleware
|
42
|
+
end
|
43
|
+
|
44
|
+
it "is false if no Middleware is attached to this Resource" do
|
45
|
+
subject.should_not have_middleware
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#pass_values_onto_requestable" do
|
50
|
+
it "passes middleware onto another Requestable object" do
|
51
|
+
require 'rack/lobster'
|
52
|
+
klass = Class.new { include Weary::Requestable }
|
53
|
+
requestable = klass.new
|
54
|
+
subject.use Rack::Lobster
|
55
|
+
subject.pass_values_onto_requestable(requestable)
|
56
|
+
requestable.should have_middleware
|
57
|
+
end
|
58
|
+
|
59
|
+
it "passes adapter onto another Requestable" do
|
60
|
+
klass = Class.new { include Weary::Requestable }
|
61
|
+
adapterClass = Class.new { include Weary::Adapter }
|
62
|
+
requestable = klass.new
|
63
|
+
subject.adapter(adapterClass)
|
64
|
+
subject.pass_values_onto_requestable(requestable)
|
65
|
+
requestable.adapter.should eql adapterClass
|
66
|
+
end
|
67
|
+
|
68
|
+
it "passes headers onto another Requestable" do
|
69
|
+
klass = Class.new { include Weary::Requestable }
|
70
|
+
requestable = klass.new
|
71
|
+
subject.user_agent "RSpec"
|
72
|
+
subject.pass_values_onto_requestable(requestable)
|
73
|
+
requestable.headers.should eql "User-Agent" => "RSpec"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
shared_examples_for "an Adapter" do
|
2
|
+
it { should respond_to :call }
|
3
|
+
it { should respond_to :connect }
|
4
|
+
|
5
|
+
describe "#call" do
|
6
|
+
it_behaves_like "a Rack application"
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#connect" do
|
10
|
+
it "returns a Rack::Response" do
|
11
|
+
subject.connect(Rack::Request.new(env)).should be_a Rack::Response
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
end
|
data/spec/weary/adapter_spec.rb
CHANGED
@@ -3,24 +3,17 @@ require 'spec_helper'
|
|
3
3
|
describe Weary::Adapter do
|
4
4
|
subject { Class.new { include Weary::Adapter }.new }
|
5
5
|
let(:env) {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
"PATH_INFO"=>"/api/v2/json/repos/show/mwunsch/weary",
|
10
|
-
"QUERY_STRING"=>nil,
|
11
|
-
"SERVER_NAME"=>"github.com",
|
12
|
-
"SERVER_PORT"=>80,
|
13
|
-
"REQUEST_URI"=>"/api/v2/json/repos/show/mwunsch/weary"
|
14
|
-
}
|
6
|
+
req = Weary::Request.new("http://github.com/api/v2/json/repos/show/mwunsch/weary")
|
7
|
+
req.headers 'User-Agent' => Weary::USER_AGENTS['Lynx 2.8.4rel.1 on Linux']
|
8
|
+
req.env
|
15
9
|
}
|
16
10
|
|
17
|
-
|
18
|
-
it_behaves_like "a Rack application"
|
19
|
-
end
|
11
|
+
it_behaves_like "an Adapter"
|
20
12
|
|
21
|
-
describe "
|
22
|
-
it "
|
23
|
-
subject.
|
13
|
+
describe "#normalize_request_headers" do
|
14
|
+
it "removes the HTTP_ prefix from request headers" do
|
15
|
+
headers = subject.normalize_request_headers(env)
|
16
|
+
headers.should have_key "User-Agent"
|
24
17
|
end
|
25
18
|
end
|
26
19
|
end
|