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/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -3,17 +3,22 @@ source "http://rubygems.org"
|
|
3
3
|
gemspec
|
4
4
|
|
5
5
|
gem "rake", "~> 0.9.2"
|
6
|
+
gem "excon"
|
6
7
|
|
7
8
|
group :test do
|
8
|
-
gem "rspec", "~> 2.
|
9
|
-
gem "webmock", "~> 1.8.
|
9
|
+
gem "rspec", "~> 2.11.0"
|
10
|
+
gem "webmock", "~> 1.8.10"
|
10
11
|
end
|
11
12
|
|
12
13
|
group :doc do
|
13
|
-
gem "yard", "~> 0.
|
14
|
-
gem "yard-tomdoc", "~> 0.
|
14
|
+
gem "yard", "~> 0.8.2"
|
15
|
+
gem "yard-tomdoc", "~> 0.5.0"
|
15
16
|
end
|
16
17
|
|
17
18
|
platforms :jruby do
|
18
19
|
gem "jruby-openssl"
|
19
20
|
end
|
21
|
+
|
22
|
+
platforms :mri, :jruby do
|
23
|
+
gem "typhoeus"
|
24
|
+
end
|
data/Gemfile.lock
CHANGED
@@ -2,8 +2,8 @@ PATH
|
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
4
|
weary (1.0.1)
|
5
|
-
addressable (~> 2.2
|
6
|
-
multi_json (~> 1.
|
5
|
+
addressable (~> 2.3.2)
|
6
|
+
multi_json (~> 1.3.0)
|
7
7
|
promise (~> 0.3.0)
|
8
8
|
rack (~> 1.4.0)
|
9
9
|
simple_oauth (~> 0.1.5)
|
@@ -11,28 +11,42 @@ PATH
|
|
11
11
|
GEM
|
12
12
|
remote: http://rubygems.org/
|
13
13
|
specs:
|
14
|
-
addressable (2.2
|
14
|
+
addressable (2.3.2)
|
15
|
+
courtier (0.2.0)
|
16
|
+
finder
|
17
|
+
loaded
|
15
18
|
crack (0.3.1)
|
16
19
|
diff-lcs (1.1.3)
|
17
|
-
|
20
|
+
excon (0.16.2)
|
21
|
+
ffi (1.1.5)
|
22
|
+
finder (0.3.0)
|
23
|
+
loaded (0.0.1)
|
24
|
+
mime-types (1.19)
|
25
|
+
multi_json (1.3.6)
|
18
26
|
promise (0.3.0)
|
19
27
|
rack (1.4.1)
|
20
28
|
rake (0.9.2.2)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
rspec-
|
25
|
-
|
26
|
-
|
29
|
+
rc (0.2.0)
|
30
|
+
courtier (= 0.2.0)
|
31
|
+
rspec (2.11.0)
|
32
|
+
rspec-core (~> 2.11.0)
|
33
|
+
rspec-expectations (~> 2.11.0)
|
34
|
+
rspec-mocks (~> 2.11.0)
|
35
|
+
rspec-core (2.11.1)
|
36
|
+
rspec-expectations (2.11.3)
|
27
37
|
diff-lcs (~> 1.1.3)
|
28
|
-
rspec-mocks (2.
|
29
|
-
simple_oauth (0.1.
|
30
|
-
tomparse (0.2.
|
31
|
-
|
38
|
+
rspec-mocks (2.11.2)
|
39
|
+
simple_oauth (0.1.9)
|
40
|
+
tomparse (0.2.1)
|
41
|
+
typhoeus (0.4.2)
|
42
|
+
ffi (~> 1.0)
|
43
|
+
mime-types (~> 1.18)
|
44
|
+
webmock (1.8.10)
|
32
45
|
addressable (>= 2.2.7)
|
33
46
|
crack (>= 0.1.7)
|
34
|
-
yard (0.
|
35
|
-
yard-tomdoc (0.
|
47
|
+
yard (0.8.2.1)
|
48
|
+
yard-tomdoc (0.5.0)
|
49
|
+
rc
|
36
50
|
tomparse
|
37
51
|
yard
|
38
52
|
|
@@ -40,10 +54,12 @@ PLATFORMS
|
|
40
54
|
ruby
|
41
55
|
|
42
56
|
DEPENDENCIES
|
57
|
+
excon
|
43
58
|
jruby-openssl
|
44
59
|
rake (~> 0.9.2)
|
45
|
-
rspec (~> 2.
|
60
|
+
rspec (~> 2.11.0)
|
61
|
+
typhoeus
|
46
62
|
weary!
|
47
|
-
webmock (~> 1.8.
|
48
|
-
yard (~> 0.
|
49
|
-
yard-tomdoc (~> 0.
|
63
|
+
webmock (~> 1.8.10)
|
64
|
+
yard (~> 0.8.2)
|
65
|
+
yard-tomdoc (~> 0.5.0)
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Weary
|
2
2
|
|
3
|
+
[](http://travis-ci.org/mwunsch/weary) [](https://codeclimate.com/github/mwunsch/weary)
|
4
|
+
|
3
5
|
_Weary is a framework and DSL for building clients for (preferably RESTful) web service APIs._
|
4
6
|
|
5
7
|
At its most minimal, Weary is simply some nice syntactic sugar around Net/HTTP.
|
@@ -56,7 +58,7 @@ end
|
|
56
58
|
|
57
59
|
The DSL provides methods for all of the HTTP verbs (See `Weary::Client::REQUEST_METHODS`). When you instantiate this class, the object will have an instance method named "resource" that will return a `Weary::Request` object set up to perform a "GET" request on "http://host.com/path/to/resource".
|
58
60
|
|
59
|
-
You can pass a block these methods for access to the `Weary::Resource`.
|
61
|
+
You can pass a block to these methods for access to the `Weary::Resource`.
|
60
62
|
|
61
63
|
Further methods in the DSL include:
|
62
64
|
|
@@ -113,9 +115,15 @@ The method that the Client defines (in the above example, the `client.update` me
|
|
113
115
|
|
114
116
|
### Weary::Request
|
115
117
|
|
116
|
-
No matter how you get there, you'll end up with a Weary::Request object. Call the `perform` method to actually make the request and get back a `Weary::Response`. That's not entirely true `Weary::Request#perform` is asynchronous and non-blocking. It returns a future and will only block once you call a method on the response. You can optionally pass a block that's executed once the response has returned.
|
118
|
+
No matter how you get there, you'll end up with a Weary::Request object. Call the `perform` method to actually make the request and get back a `Weary::Response`. That's not entirely true... `Weary::Request#perform` is asynchronous and non-blocking. It returns a future and will only block once you call a method on the response. You can optionally pass a block that's executed once the response has returned.
|
119
|
+
|
120
|
+
By default, the request is performed through [Net::HTTP](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/net/http/rdoc/Net/HTTP.html). This is done through `Weary::Adapter::NetHttp`. A `Weary::Adapter` is just a special kind of Rack application. `Request#adapter` allows you to hook up your own. Weary also includes adapters for [Typhoeus](http://typhoeus.github.com/) and [Excon](https://github.com/geemus/excon).
|
121
|
+
|
122
|
+
#### Requestable
|
123
|
+
|
124
|
+
`Client`, `Resource`, and `Request` include a Module named `Requestable`. Using this module, it's easy to cascade certain pieces of configuration down from the stack.
|
117
125
|
|
118
|
-
|
126
|
+
For example, you can call `Client#adapter` to change the adapter for all of the resources of that client. Or you can call `Resource#adapter` to change the adapter for requests built for that resource. OR you can call `Request#adapter` to change the adapter for just that request.
|
119
127
|
|
120
128
|
## Rack
|
121
129
|
|
data/lib/weary/adapter.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'weary/response'
|
2
|
-
|
2
|
+
|
3
3
|
module Weary
|
4
4
|
# An abstract interface. A subclass should be something that actually opens
|
5
5
|
# a socket to make the request, e.g. Net/HTTP, Curb, etc.
|
6
6
|
module Adapter
|
7
|
+
autoload :NetHttp, 'weary/adapters/net_http'
|
8
|
+
autoload :Excon, 'weary/adapters/excon'
|
9
|
+
autoload :Typhoeus, 'weary/adapters/typhoeus'
|
7
10
|
|
8
11
|
def initialize(app=nil)
|
9
12
|
@app = app
|
@@ -19,5 +22,26 @@ module Weary
|
|
19
22
|
def connect(request)
|
20
23
|
Rack::Response.new [""], 501, {"Content-Type" => "text/plain"}
|
21
24
|
end
|
25
|
+
|
26
|
+
# Modify the headers of an Env to be Capitalized strings with dashes (as
|
27
|
+
# opposed to the CGI-like headers needed by Rack).
|
28
|
+
def normalize_request_headers(env)
|
29
|
+
req_headers = env.reject {|k,v| !k.start_with? "HTTP_" }
|
30
|
+
normalized = req_headers.map do |k, v|
|
31
|
+
new_key = k.sub("HTTP_",'').split('_').map(&:capitalize).join('-')
|
32
|
+
[new_key, v] unless UNWANTED_REQUEST_HEADERS.include? new_key
|
33
|
+
end
|
34
|
+
Hash[normalized]
|
35
|
+
end
|
36
|
+
|
37
|
+
# According to the Rack Spec:
|
38
|
+
# > The header must not contain a Status key...
|
39
|
+
def normalize_response(headers)
|
40
|
+
headers.reject {|k,v| k.downcase == 'status' }
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
UNWANTED_REQUEST_HEADERS = []
|
22
46
|
end
|
23
47
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'excon'
|
2
|
+
|
3
|
+
module Weary
|
4
|
+
module Adapter
|
5
|
+
class Excon
|
6
|
+
|
7
|
+
class << self
|
8
|
+
include Weary::Adapter
|
9
|
+
|
10
|
+
def connect(request)
|
11
|
+
connection = ::Excon.new("#{request.scheme}://#{request.host_with_port}")
|
12
|
+
response = connection.request prepare(request)
|
13
|
+
Rack::Response.new response.body, response.status, normalize_response(response.headers)
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepare(request)
|
17
|
+
has_query = !(request.query_string.nil? || request.query_string.empty?)
|
18
|
+
excon_params = { :headers => normalize_request_headers(request.env),
|
19
|
+
:method => request.request_method,
|
20
|
+
:path => request.path,
|
21
|
+
:body => request.body.read }
|
22
|
+
excon_params[:query] if has_query
|
23
|
+
request.body.rewind
|
24
|
+
excon_params
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
include Weary::Adapter
|
29
|
+
|
30
|
+
def connect(rack_request)
|
31
|
+
self.class.connect(rack_request)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -4,65 +4,47 @@ require 'net/https'
|
|
4
4
|
module Weary
|
5
5
|
module Adapter
|
6
6
|
class NetHttp
|
7
|
-
include Weary::Adapter
|
8
|
-
|
9
|
-
def self.call(env)
|
10
|
-
connect(Rack::Request.new(env)).finish
|
11
|
-
end
|
12
7
|
|
13
|
-
|
14
|
-
|
15
|
-
response = connection.request prepare(request)
|
16
|
-
Rack::Response.new response.body, response.code, normalize_response(response.to_hash)
|
17
|
-
end
|
8
|
+
class << self
|
9
|
+
include Weary::Adapter
|
18
10
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
req.body = request.body.read
|
24
|
-
request.body.rewind
|
11
|
+
def connect(request)
|
12
|
+
connection = socket(request)
|
13
|
+
response = connection.request prepare(request)
|
14
|
+
Rack::Response.new response.body || "", response.code, normalize_response(response.to_hash)
|
25
15
|
end
|
26
|
-
req.content_type = request.content_type if request.content_type
|
27
|
-
req.content_length = request.content_length if request.content_length
|
28
|
-
req
|
29
|
-
end
|
30
16
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
17
|
+
def prepare(request)
|
18
|
+
req_class = request_class(request.request_method)
|
19
|
+
net_http_req = req_class.new(request.fullpath, normalize_request_headers(request.env))
|
20
|
+
if net_http_req.request_body_permitted? # What's the best way of passing the body?
|
21
|
+
net_http_req.body = request.body.read
|
22
|
+
request.body.rewind
|
23
|
+
end
|
24
|
+
net_http_req.content_type = request.content_type if request.content_type
|
25
|
+
net_http_req.content_length = request.content_length if request.content_length
|
26
|
+
net_http_req
|
36
27
|
end
|
37
|
-
Hash[normalized]
|
38
|
-
end
|
39
28
|
|
40
|
-
|
41
|
-
|
42
|
-
|
29
|
+
def socket(request)
|
30
|
+
connection = Net::HTTP.new request.host, request.port.to_s
|
31
|
+
connection.use_ssl = request.scheme == 'https'
|
32
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if connection.use_ssl?
|
33
|
+
connection
|
34
|
+
end
|
43
35
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
connection.use_ssl = request.scheme == 'https'
|
49
|
-
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE if connection.use_ssl?
|
50
|
-
connection
|
36
|
+
def request_class(method)
|
37
|
+
capitalized = method.capitalize
|
38
|
+
Net::HTTP.const_get capitalized
|
39
|
+
end
|
51
40
|
end
|
52
41
|
|
53
|
-
|
54
|
-
capitalized = method.capitalize
|
55
|
-
Net::HTTP.const_get capitalized
|
56
|
-
end
|
42
|
+
include Weary::Adapter
|
57
43
|
|
58
|
-
def
|
59
|
-
self.class.
|
44
|
+
def connect(rack_request)
|
45
|
+
self.class.connect(rack_request)
|
60
46
|
end
|
61
47
|
|
62
|
-
private
|
63
|
-
|
64
|
-
UNWANTED_REQUEST_HEADERS = []
|
65
|
-
|
66
48
|
end
|
67
49
|
end
|
68
50
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
module Weary
|
4
|
+
module Adapter
|
5
|
+
class Typhoeus
|
6
|
+
|
7
|
+
class << self
|
8
|
+
include Weary::Adapter
|
9
|
+
|
10
|
+
def connect(rack_request)
|
11
|
+
response = ::Typhoeus::Request.run(rack_request.url, parameters_for(rack_request))
|
12
|
+
Rack::Response.new response.body, response.code, response.headers_hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def parameters_for(request)
|
16
|
+
typhoeus_params = { :headers => normalize_request_headers(request.env),
|
17
|
+
:method => request.request_method.downcase.to_sym,
|
18
|
+
:body => request.body.read }
|
19
|
+
request.body.rewind
|
20
|
+
typhoeus_params
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
include Weary::Adapter
|
25
|
+
|
26
|
+
def connect(rack_request)
|
27
|
+
self.class.connect(rack_request)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/weary/client.rb
CHANGED
@@ -47,6 +47,7 @@ module Weary
|
|
47
47
|
]
|
48
48
|
|
49
49
|
class << self
|
50
|
+
include Weary::Requestable
|
50
51
|
|
51
52
|
REQUEST_METHODS.each do |request_method|
|
52
53
|
# Generate a resource of the specified REQUEST_METHOD. This
|
@@ -116,31 +117,6 @@ module Weary
|
|
116
117
|
@defaults ||= {}
|
117
118
|
end
|
118
119
|
|
119
|
-
# An accessor to set HTTP request headers for all of the client's
|
120
|
-
# resources.
|
121
|
-
#
|
122
|
-
# hash - An optional Hash of key/value pairs that are sent as HTTP
|
123
|
-
# request headers when a resource's request is performed.
|
124
|
-
#
|
125
|
-
# Returns a Hash of the headers.
|
126
|
-
def headers(hash=nil)
|
127
|
-
@headers = hash unless hash.nil?
|
128
|
-
@headers ||= {}
|
129
|
-
end
|
130
|
-
|
131
|
-
# Send a Rack middleware to all of the requests generated by
|
132
|
-
# the client.
|
133
|
-
#
|
134
|
-
# middleware - An object that implements the rack middleware interface.
|
135
|
-
# args - Zero or more optional arguments to send to the middleware.
|
136
|
-
# block - An optional block to send to the middleware.
|
137
|
-
#
|
138
|
-
# Returns an Array of middlewares.
|
139
|
-
def use(middleware, *args, &block)
|
140
|
-
@middlewares ||= []
|
141
|
-
@middlewares << [middleware, args, block]
|
142
|
-
end
|
143
|
-
|
144
120
|
# Internal: Create and build a resource description of a request. The
|
145
121
|
# resource is then stored in an internal hash, generating an instance
|
146
122
|
# method.
|
@@ -158,10 +134,7 @@ module Weary
|
|
158
134
|
resource.optional *optional
|
159
135
|
resource.required *required
|
160
136
|
resource.defaults defaults
|
161
|
-
resource
|
162
|
-
if !@middlewares.nil? && !@middlewares.empty?
|
163
|
-
@middlewares.each {|middleware| resource.use *middleware }
|
164
|
-
end
|
137
|
+
pass_values_onto_requestable resource
|
165
138
|
yield resource if block_given?
|
166
139
|
self[name] = resource
|
167
140
|
end
|
data/lib/weary/deferred.rb
CHANGED
data/lib/weary/env.rb
CHANGED
@@ -17,7 +17,7 @@ module Weary
|
|
17
17
|
'SERVER_NAME' => @request.uri.host,
|
18
18
|
'SERVER_PORT' => port,
|
19
19
|
'REQUEST_URI' => @request.uri.request_uri,
|
20
|
-
'HTTP_HOST' =>
|
20
|
+
'HTTP_HOST' => http_host,
|
21
21
|
'rack.url_scheme' => @request.uri.scheme,
|
22
22
|
'rack.input' => @request.body,
|
23
23
|
'weary.request' => @request
|
@@ -28,5 +28,10 @@ module Weary
|
|
28
28
|
(@request.uri.port || @request.uri.inferred_port).to_s
|
29
29
|
end
|
30
30
|
|
31
|
+
def http_host
|
32
|
+
uri = @request.uri
|
33
|
+
uri.host + (uri.normalized_port ? ":#{uri.normalized_port}" : "")
|
34
|
+
end
|
35
|
+
|
31
36
|
end
|
32
37
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Weary
|
4
|
+
module Middleware
|
5
|
+
class Timeout
|
6
|
+
|
7
|
+
def initialize(app, time = 15)
|
8
|
+
@app = app
|
9
|
+
@time = time
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
begin
|
14
|
+
::Timeout.timeout(@time) { @app.call(env) }
|
15
|
+
rescue ::Timeout::Error => e
|
16
|
+
[504, {'Content-Type' => "text/plain"}, [e.message]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|