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 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