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 CHANGED
@@ -4,3 +4,4 @@ doc/
4
4
  .bundle/
5
5
  .yardoc/
6
6
  .rbenv-version
7
+ *.rbc
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.0"
9
- gem "webmock", "~> 1.8.5"
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.7.5"
14
- gem "yard-tomdoc", "~> 0.4.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
@@ -2,8 +2,8 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  weary (1.0.1)
5
- addressable (~> 2.2.7)
6
- multi_json (~> 1.2.0)
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.7)
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
- multi_json (1.2.0)
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
- rspec (2.9.0)
22
- rspec-core (~> 2.9.0)
23
- rspec-expectations (~> 2.9.0)
24
- rspec-mocks (~> 2.9.0)
25
- rspec-core (2.9.0)
26
- rspec-expectations (2.9.0)
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.9.0)
29
- simple_oauth (0.1.5)
30
- tomparse (0.2.0)
31
- webmock (1.8.5)
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.7.5)
35
- yard-tomdoc (0.4.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.9.0)
60
+ rspec (~> 2.11.0)
61
+ typhoeus
46
62
  weary!
47
- webmock (~> 1.8.5)
48
- yard (~> 0.7.5)
49
- yard-tomdoc (~> 0.4.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
+ [![Build Status](https://secure.travis-ci.org/mwunsch/weary.png)](http://travis-ci.org/mwunsch/weary) [![Code Climate](https://codeclimate.com/badge.png)](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
- 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.
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
 
@@ -1,9 +1,12 @@
1
1
  require 'weary/response'
2
- require 'weary/adapters/net_http'
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
- def self.connect(request)
14
- connection = socket(request)
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
- def self.prepare(request)
20
- req_class = request_class(request.request_method)
21
- req = req_class.new(request.fullpath, normalize_request_headers(request.env))
22
- if req.request_body_permitted? # What's the best way of passing the body?
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
- def self.normalize_request_headers(env)
32
- req_headers = env.reject {|k,v| !k.start_with? "HTTP_" }
33
- normalized = req_headers.map do |k, v|
34
- new_key = k.sub("HTTP_",'').split('_').map(&:capitalize).join('-')
35
- [new_key, v] unless UNWANTED_REQUEST_HEADERS.include? new_key
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
- def self.normalize_response(headers)
41
- headers.reject {|k,v| k.downcase == 'status' }
42
- end
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
- def self.socket(request)
45
- host = request.env['HTTP_HOST'] || request.env['SERVER_NAME']
46
- port = request.env['SERVER_PORT'].to_s
47
- connection = Net::HTTP.new host, port
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
- def self.request_class(method)
54
- capitalized = method.capitalize
55
- Net::HTTP.const_get capitalized
56
- end
42
+ include Weary::Adapter
57
43
 
58
- def call(env)
59
- self.class.call(env)
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
@@ -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.headers headers
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
@@ -25,7 +25,7 @@ module Weary
25
25
 
26
26
  def method_missing(method, *args, &block)
27
27
  if @model.method_defined? method
28
- @target ||= @callable.call @model, @future.force
28
+ @target ||= @callable.call @model, @future
29
29
  @target.send(method, *args, &block)
30
30
  else
31
31
  super
@@ -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' => @request.uri.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