weary 1.0.1 → 1.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/.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
|