commute 0.2.0.rc.2 → 0.3.0.pre
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/.todo +28 -12
- data/README.md +0 -1
- data/commute.gemspec +4 -5
- data/lib/commute/common/basic_auth.rb +10 -9
- data/lib/commute/common/caching.rb +208 -0
- data/lib/commute/common/chemicals.rb +47 -24
- data/lib/commute/common/eventmachine.rb +68 -0
- data/lib/commute/common/synchrony.rb +42 -0
- data/lib/commute/common/typhoeus.rb +64 -0
- data/lib/commute/core/api.rb +42 -29
- data/lib/commute/core/builder.rb +4 -15
- data/lib/commute/core/context.rb +156 -15
- data/lib/commute/core/http.rb +124 -0
- data/lib/commute/core/layer.rb +187 -0
- data/lib/commute/core/sequence.rb +83 -132
- data/lib/commute/core/stack.rb +63 -72
- data/lib/commute/core/status.rb +45 -0
- data/lib/commute/core/util/event_emitter.rb +58 -0
- data/lib/commute/core/util/path.rb +37 -0
- data/lib/commute/core/util/stream.rb +141 -0
- data/lib/commute/extensions/crud.rb +88 -0
- data/lib/commute/extensions/param.rb +20 -0
- data/lib/commute/extensions/url.rb +53 -0
- data/lib/commute/version.rb +1 -1
- data/spec/commute/common/caching_spec.rb +158 -0
- data/spec/commute/common/eventmachine_spec.rb +74 -0
- data/spec/commute/common/typhoeus_spec.rb +67 -0
- data/spec/commute/core/api_spec.rb +3 -1
- data/spec/commute/core/builder_spec.rb +8 -8
- data/spec/commute/core/http_spec.rb +39 -0
- data/spec/commute/core/layer_spec.rb +81 -0
- data/spec/commute/core/sequence_spec.rb +36 -150
- data/spec/commute/core/stack_spec.rb +33 -83
- data/spec/commute/core/util/event_emitter_spec.rb +35 -0
- data/spec/commute/core/util/path_spec.rb +29 -0
- data/spec/commute/core/util/stream_spec.rb +90 -0
- data/spec/commute/extensions/url_spec.rb +76 -0
- data/spec/spec_helper.rb +3 -1
- metadata +61 -48
- data/examples/gist_api.rb +0 -71
- data/examples/highrise_task_api.rb +0 -59
- data/examples/pastie_api.rb +0 -18
- data/lib/commute/aspects/caching.rb +0 -37
- data/lib/commute/aspects/crud.rb +0 -41
- data/lib/commute/aspects/pagination.rb +0 -16
- data/lib/commute/aspects/url.rb +0 -57
- data/lib/commute/common/cache.rb +0 -43
- data/lib/commute/common/conditional.rb +0 -27
- data/lib/commute/common/em-synchrony_adapter.rb +0 -29
- data/lib/commute/common/em_http_request_adapter.rb +0 -57
- data/lib/commute/common/typhoeus_adapter.rb +0 -40
- data/lib/commute/common/xml.rb +0 -7
- data/lib/commute/core/commuter.rb +0 -116
- data/lib/commute/core/processors/code_status_processor.rb +0 -40
- data/lib/commute/core/processors/hook.rb +0 -14
- data/lib/commute/core/processors/request_builder.rb +0 -26
- data/lib/commute/core/processors/sequencer.rb +0 -46
- data/lib/commute/core/request.rb +0 -58
- data/lib/commute/core/response.rb +0 -18
- data/spec/commute/aspects/caching_spec.rb +0 -12
- data/spec/commute/aspects/url_spec.rb +0 -61
- data/spec/commute/core/commuter_spec.rb +0 -64
- data/spec/commute/core/processors/code_status_processor_spec.rb +0 -5
- data/spec/commute/core/processors/hook_spec.rb +0 -25
- data/spec/commute/core/processors/request_builder_spec.rb +0 -25
- data/spec/commute/core/processors/sequencer_spec.rb +0 -33
data/.todo
CHANGED
@@ -1,15 +1,31 @@
|
|
1
|
-
conditional sequencer (body), routing on constants
|
2
|
-
conditional processors instead of only sequencers
|
3
1
|
easy transforms, reactivity? When not to add a transform
|
4
|
-
disabling
|
5
|
-
groups of commuters (Group and delaying)
|
6
|
-
queueing
|
7
|
-
batching/grouping
|
8
2
|
net/http
|
9
3
|
stubbing
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
4
|
+
request error handling (timeouts...)
|
5
|
+
|
6
|
+
layers:
|
7
|
+
OAuth with refreshing (and on_refresh callback)
|
8
|
+
Redirect following
|
9
|
+
Caching
|
10
|
+
Batching
|
11
|
+
Page Crusher
|
12
|
+
|
13
|
+
api method on context to select context
|
14
|
+
complete method for quick layer (eg github gist star).
|
15
|
+
why not use stream, run, request in layers or in complete system.
|
16
|
+
|
17
|
+
HOOKS
|
18
|
+
|
19
|
+
stream flattening [[1,2,3]] => [1,2,3] (flat_map?)
|
20
|
+
stream::lazy; enumerable?
|
21
|
+
|
22
|
+
New relic instrumentation
|
23
|
+
Separate Parse/Render
|
24
|
+
|
25
|
+
move layer specific config to using istead of with
|
26
|
+
using [:render, :parse] => {
|
27
|
+
template: 'person'
|
28
|
+
}
|
29
|
+
|
30
|
+
param :reload # transform { |r, param| r.query[param] = c[param] }
|
31
|
+
cache param blocks
|
data/README.md
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# Commute
|
2
2
|
[](http://travis-ci.org/challengee/commute)
|
3
|
-
[](https://gemnasium.com/challengee/commute)
|
4
3
|
|
5
4
|
Commute helps you to:
|
6
5
|
|
data/commute.gemspec
CHANGED
@@ -14,17 +14,16 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.require_paths = ["lib"]
|
15
15
|
s.version = Commute::VERSION
|
16
16
|
|
17
|
-
s.
|
18
|
-
s.
|
17
|
+
s.add_development_dependency 'typhoeus'
|
18
|
+
s.add_development_dependency 'em-http-request'
|
19
|
+
s.add_development_dependency 'yajl-ruby'
|
19
20
|
|
20
21
|
s.add_development_dependency 'rake'
|
21
22
|
s.add_development_dependency 'mocha'
|
23
|
+
s.add_development_dependency 'rb-fsevent'
|
22
24
|
s.add_development_dependency 'guard'
|
23
25
|
s.add_development_dependency 'guard-minitest'
|
24
26
|
s.add_development_dependency 'yard'
|
25
27
|
s.add_development_dependency 'simplecov'
|
26
28
|
s.add_development_dependency 'webmock'
|
27
|
-
|
28
|
-
# For the examples.
|
29
|
-
s.add_development_dependency 'yajl-ruby'
|
30
29
|
end
|
@@ -4,16 +4,17 @@ module Commute
|
|
4
4
|
module Common
|
5
5
|
|
6
6
|
class BasicAuth
|
7
|
-
@
|
7
|
+
@name = :auth
|
8
8
|
|
9
|
-
def call
|
10
|
-
|
11
|
-
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
def call router, request, options = {}
|
10
|
+
# Create the Authorization header.
|
11
|
+
authorization = Base64.strict_encode64 \
|
12
|
+
"#{options[:username]}:#{options[:password]}}"
|
13
|
+
# Set the header on the http request.
|
14
|
+
request.http.headers['Authorization'] = "Basic #{authorization}"
|
15
|
+
|
16
|
+
# Send the request, pipe the data, and forward the response.
|
17
|
+
request.pipe router.call(request.http, &request.responder)
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require 'commute/core/http'
|
2
|
+
|
3
|
+
module Commute
|
4
|
+
module Common
|
5
|
+
|
6
|
+
# Internal: Fetches cached responses to make requests faster.
|
7
|
+
#
|
8
|
+
# Any GET requests that comes in will be fetched in the cache
|
9
|
+
# based on its URL.
|
10
|
+
#
|
11
|
+
# When nothing is found in the cache, a request is made as
|
12
|
+
# planned, and the result is stored in the cache (when it is a GET request).
|
13
|
+
#
|
14
|
+
# When it is found in the cache, there are actually two possibilities.
|
15
|
+
# One is if validation is on (:validate option). When it is on, it will
|
16
|
+
# detect if the resource has already changed on the server before returning
|
17
|
+
# from the cache (using etags).
|
18
|
+
# If validation is off, the result from the cache is returned.
|
19
|
+
#
|
20
|
+
class Caching
|
21
|
+
@name = :caching
|
22
|
+
|
23
|
+
# Internal: Special status that wraps a http status and
|
24
|
+
# adds cache indications.
|
25
|
+
#
|
26
|
+
class CacheStatus < Status
|
27
|
+
|
28
|
+
# Public: The underlaying http status.
|
29
|
+
attr_accessor :http
|
30
|
+
|
31
|
+
# Public: Whether the response was cached.
|
32
|
+
attr_accessor :cached
|
33
|
+
|
34
|
+
# Public: Whether the cache was updated on this request.
|
35
|
+
attr_accessor :updated
|
36
|
+
|
37
|
+
# Public: Creates a new CacheStatus.
|
38
|
+
#
|
39
|
+
# http - Underlaying http status, can be nil.
|
40
|
+
# cached - Whether the response was cached.
|
41
|
+
# updated - Whether the cache was updated on this request.
|
42
|
+
#
|
43
|
+
def initialize http
|
44
|
+
@http = http
|
45
|
+
@updated = false
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Success or not?
|
49
|
+
def success?
|
50
|
+
(http? && http.success?) || cached
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Whether there was an http request performed.
|
54
|
+
def http?
|
55
|
+
!http.nil?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Internal: One entry in the cache, contains the data of a
|
60
|
+
# resource and the etag it corresponds with.
|
61
|
+
class CacheEntry
|
62
|
+
|
63
|
+
# Public: The value present in the cache.
|
64
|
+
attr_reader :value
|
65
|
+
|
66
|
+
# Public: The key of this cache value.
|
67
|
+
attr_reader :key
|
68
|
+
|
69
|
+
# Initialize a new Entry.
|
70
|
+
#
|
71
|
+
# cache - The cache to work in.
|
72
|
+
# key - The key for this cache entry.
|
73
|
+
# value - The contents for this cache entry.
|
74
|
+
#
|
75
|
+
def initialize cache, key, value, exists = false
|
76
|
+
@cache = cache
|
77
|
+
@key = key
|
78
|
+
@value = value
|
79
|
+
@exists = exists
|
80
|
+
end
|
81
|
+
|
82
|
+
# Search for a CacheEntry in a cache.
|
83
|
+
#
|
84
|
+
# cache - The cache to look in.
|
85
|
+
# key - The key to look for.
|
86
|
+
#
|
87
|
+
# Returns a CacheEntry or nil if none was found.
|
88
|
+
def self.lookup cache, key
|
89
|
+
value = cache.get(key)
|
90
|
+
CacheEntry.new(cache, key, value, !value.nil?)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Stores the entry in the cache.
|
94
|
+
def store
|
95
|
+
@cache.set @key, @value
|
96
|
+
@exists = true
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
# Updates the entry to a new value
|
101
|
+
def update value
|
102
|
+
@value = value
|
103
|
+
self.store
|
104
|
+
end
|
105
|
+
|
106
|
+
def exists?
|
107
|
+
@exists
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# The caching logic itself.
|
112
|
+
#
|
113
|
+
# options - Hash of options for caching (default: {}):
|
114
|
+
# :cache - A cache (#get, #set).
|
115
|
+
# :validate - Whether to check if the cache
|
116
|
+
# entry is still valid (default: false).
|
117
|
+
#
|
118
|
+
def call router, request, options = {}
|
119
|
+
# Fetch some options.
|
120
|
+
cache, validate = options[:cache], options[:validate] || false
|
121
|
+
# Cannot proceed if there is no cache.
|
122
|
+
raise 'You need to configure a cache first' unless cache
|
123
|
+
|
124
|
+
# This cache only works for get request, proceed if something else.
|
125
|
+
router.call request and return unless request.http.method == :get
|
126
|
+
|
127
|
+
# Get the cache entry.
|
128
|
+
entry = CacheEntry.lookup(cache, request.http.uri.to_s)
|
129
|
+
|
130
|
+
# To do when we have something to show.
|
131
|
+
on_response = proc do |response, status|
|
132
|
+
# Just pipe the response from the net/cache to the response for this request.
|
133
|
+
response.pipe request.respond(response, status)
|
134
|
+
end
|
135
|
+
|
136
|
+
# When validation is on, or we have no cache entry, get the latest (request or cache)
|
137
|
+
if validate || !entry.exists?
|
138
|
+
respond_latest router, entry, request, &on_response
|
139
|
+
# When we have something in the cache and we do not want to validate it,
|
140
|
+
# Then just return our cache entry.
|
141
|
+
else
|
142
|
+
respond_from_cache entry, request, &on_response
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
# Responds with the latest version for a given remote resource. Either from the
|
149
|
+
# cache or from the internets. Checks based on etags.
|
150
|
+
# Warning: Could modify the given request.
|
151
|
+
#
|
152
|
+
# stack - The Commute stack.
|
153
|
+
# entry - The cache entry.
|
154
|
+
# request - The request for the remote resource.
|
155
|
+
#
|
156
|
+
# Yields a response like you would get from a stack call.
|
157
|
+
#
|
158
|
+
def respond_latest router, entry, request, &callback
|
159
|
+
# Modify the request to be a head request of nothing was found in the cache.
|
160
|
+
if entry.exists?
|
161
|
+
request.http.headers['If-None-Match'] = entry.value[:etag]
|
162
|
+
end
|
163
|
+
# Fire the conditional request.
|
164
|
+
validate_request = router.call request.http do |response, http_status|
|
165
|
+
# If it was not modified, the it is still valid.
|
166
|
+
if http_status.not_modified?
|
167
|
+
# Respond from cache.
|
168
|
+
respond_from_cache entry, request do |response, status|
|
169
|
+
callback.call response, status.tap { |s| s.http = http_status }
|
170
|
+
end
|
171
|
+
|
172
|
+
# If it was modified and a success, respond and update the cache.
|
173
|
+
elsif http_status.success?
|
174
|
+
# Update the cache.
|
175
|
+
response.once(:data) do |body|
|
176
|
+
entry.update data: body, etag: response.http.etag
|
177
|
+
end
|
178
|
+
# Respond with wrapped status marked as invalidated.
|
179
|
+
callback.call response, CacheStatus.new(http_status).tap { |status|
|
180
|
+
status.updated = true
|
181
|
+
status.cached = entry.exists?
|
182
|
+
}
|
183
|
+
|
184
|
+
# The response was not a success, that sucks, respond with it anyway.
|
185
|
+
else
|
186
|
+
callback.call response, CacheStatus.new(http_status).mark(cached: !entry.nil?)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
validate_request.end
|
190
|
+
end
|
191
|
+
|
192
|
+
# Responds with a cached version.
|
193
|
+
# Raises an error when the entry is nil.
|
194
|
+
#
|
195
|
+
# entry - The cache entry.
|
196
|
+
#
|
197
|
+
# Yields a response like you would get from a stack call.
|
198
|
+
#
|
199
|
+
def respond_from_cache entry, request, &callback
|
200
|
+
raise ArgumentError, "Entry is nil" unless entry
|
201
|
+
response = Layer::Response.new Http::Response.new(request)
|
202
|
+
callback.call response, CacheStatus.new(nil).tap { |s| s.cached = true }
|
203
|
+
# Stream the cached data in one big chunk.
|
204
|
+
response.end entry.value[:data]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -2,36 +2,59 @@ require 'chemicals'
|
|
2
2
|
|
3
3
|
module Commute
|
4
4
|
module Common
|
5
|
-
class Chemicals
|
6
|
-
@id = :chemicals
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
module Chemicals
|
7
|
+
|
8
|
+
module Chemicals::Base
|
9
|
+
TEMPLATE_CACHE = {}
|
10
|
+
|
11
|
+
def initialize template_root = './'
|
12
|
+
@template_root = template_root
|
13
|
+
end
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def template path
|
16
|
+
file = File.expand_path "#{path}.xml", @template_root
|
17
|
+
|
18
|
+
if TEMPLATE_CACHE.has_key? file
|
19
|
+
TEMPLATE_CACHE[file]
|
20
|
+
else
|
21
|
+
TEMPLATE_CACHE[file] = ::Chemicals::Template.new IO.read(file)
|
22
|
+
end
|
19
23
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
end
|
25
|
+
|
26
|
+
class Parser
|
27
|
+
include Chemicals::Base
|
28
|
+
@name = :parse
|
29
|
+
|
30
|
+
def call router, request, options
|
31
|
+
path = options[:path]
|
32
|
+
|
33
|
+
_request = router.call(request.http) do |_response, status|
|
34
|
+
response = request.respond _response.http, status
|
35
|
+
|
36
|
+
_response.inject('', &:<<).map { |body|
|
37
|
+
template(path).parse body
|
38
|
+
}.pipe response
|
26
39
|
end
|
40
|
+
request.pipe _request
|
27
41
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
42
|
+
end
|
43
|
+
|
44
|
+
class Renderer
|
45
|
+
include Chemicals::Base
|
46
|
+
@name = :render
|
47
|
+
|
48
|
+
def call router, request, options
|
49
|
+
path = options[:path]
|
50
|
+
|
51
|
+
_request = router.call(request.http) do |_response, status|
|
52
|
+
_response.pipe request.respond(_response.http, status)
|
34
53
|
end
|
54
|
+
|
55
|
+
request.map { |body|
|
56
|
+
template(path).render(body).to_s
|
57
|
+
}.pipe _request
|
35
58
|
end
|
36
59
|
end
|
37
60
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'em-http-request'
|
2
|
+
|
3
|
+
require 'commute/core/http'
|
4
|
+
|
5
|
+
module Commute
|
6
|
+
module Common
|
7
|
+
|
8
|
+
# Public: Adapter that uses em-http-request, an Eventmachine based
|
9
|
+
# HTTP client, as a underlaying commute engine.
|
10
|
+
#
|
11
|
+
# This requires all the commute requests to be made within the
|
12
|
+
# Eventmachine loop.
|
13
|
+
#
|
14
|
+
class Eventmachine
|
15
|
+
@name = :adapter
|
16
|
+
|
17
|
+
# Internal: Make a request through Eventmachine.
|
18
|
+
def call router, request, options = {}
|
19
|
+
# Buffer the request (only streaming response).
|
20
|
+
request.buffer.on(:data) do |body|
|
21
|
+
response = nil
|
22
|
+
# Create a native em-http request.
|
23
|
+
em_request = to_request request.http, body.join
|
24
|
+
|
25
|
+
em_request.headers do
|
26
|
+
# Create a Http response.
|
27
|
+
http_response = to_response request, em_request.response_header
|
28
|
+
# Create a Http::Status (determined from response code).
|
29
|
+
status = Http::Status.new(http_response.code)
|
30
|
+
# Respond.
|
31
|
+
response = request.respond(http_response, status)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set the data callback.
|
35
|
+
em_request.stream do |chunk|
|
36
|
+
response.write chunk
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set the end callback.
|
40
|
+
em_request.callback do
|
41
|
+
response.end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
# Internal: Converts a Commute requests into an em-http request.
|
49
|
+
def to_request request, body
|
50
|
+
EventMachine::HttpRequest.new(request.uri.to_s).send request.method, \
|
51
|
+
query: request.query,
|
52
|
+
head: {
|
53
|
+
'Accept-Encoding' => 'gzip, deflate'
|
54
|
+
}.merge!(request.headers),
|
55
|
+
body: body
|
56
|
+
end
|
57
|
+
|
58
|
+
# Internal: Converts an em-http response into a Commute response.
|
59
|
+
# Note: trims whitespace bodies.
|
60
|
+
def to_response request, eventmachine_response
|
61
|
+
Http::Response.new(request).tap do |response|
|
62
|
+
response.code = eventmachine_response.status
|
63
|
+
response.headers = eventmachine_response.raw
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "em-synchrony"
|
2
|
+
|
3
|
+
require 'commute/common/eventmachine'
|
4
|
+
|
5
|
+
module Commute
|
6
|
+
module Common
|
7
|
+
|
8
|
+
# Public: Adapter that uses em-synchrony (with em-http-request).
|
9
|
+
#
|
10
|
+
# Requires use of em-synchrony and thus Eventmachine.
|
11
|
+
#
|
12
|
+
class Synchrony < Eventmachine
|
13
|
+
@name = :adapter
|
14
|
+
|
15
|
+
# Internal: Make a request through em-synchrony.
|
16
|
+
def call router, request, options = {}
|
17
|
+
# Get the current fiber.
|
18
|
+
body = nil
|
19
|
+
request.on(:data) { |_body| body = _body }
|
20
|
+
# Buffer the request (only streaming response).
|
21
|
+
request.on(:end) do |body|
|
22
|
+
response = nil
|
23
|
+
# Create a native em-http request.
|
24
|
+
em_request = to_request request.http, body
|
25
|
+
|
26
|
+
# Create a Http response.
|
27
|
+
http_response = to_response request, em_request.response_header
|
28
|
+
# Create a Http::Status (determined from response code).
|
29
|
+
status = Http::Status.new(http_response.code)
|
30
|
+
# Respond.
|
31
|
+
response = request.respond(http_response, status)
|
32
|
+
|
33
|
+
response.write em_request.response unless \
|
34
|
+
em_request.response.strip.empty?
|
35
|
+
|
36
|
+
# Request is finished, end the response.
|
37
|
+
response.end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
require 'commute/core/http'
|
4
|
+
|
5
|
+
module Commute
|
6
|
+
module Common
|
7
|
+
|
8
|
+
# Internal: Adapter that uses Typhoeus to make requests.
|
9
|
+
#
|
10
|
+
# Request bodies are entirely buffered before sending.
|
11
|
+
# Responses are also completely buffered before they
|
12
|
+
# are sent out in one big chunk from this layer.
|
13
|
+
#
|
14
|
+
class Typhoeus
|
15
|
+
@name = :adapter
|
16
|
+
|
17
|
+
def call router, request, options = {}
|
18
|
+
# Here we can only send when the request is buffered (body is needed).
|
19
|
+
body = []
|
20
|
+
request.buffer.on(:data) { |_body| body = _body }
|
21
|
+
request.on(:end) do
|
22
|
+
# Build a Typhoeus request from this request.
|
23
|
+
# Body should be a String.
|
24
|
+
typhoeus_request = to_request(request.http, body.join)
|
25
|
+
# Run the request with Hydra.
|
26
|
+
hydra = ::Typhoeus::Hydra.new
|
27
|
+
hydra.queue typhoeus_request
|
28
|
+
hydra.run
|
29
|
+
# Build a Http response.
|
30
|
+
http_response = to_response(request.http, typhoeus_request.response)
|
31
|
+
# Create a Http::Status (determined from response code).
|
32
|
+
status = Http::Status.new(http_response.code)
|
33
|
+
# Respond with that response and send the data.
|
34
|
+
response = request.respond(http_response, status) do |r|
|
35
|
+
# Write the response body if there is something besides whitespace.
|
36
|
+
r.write typhoeus_request.response.body unless \
|
37
|
+
typhoeus_request.response.body.strip.empty? || status.fail?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Internal: Creates a Typhoeus::Request from a Http::Request
|
45
|
+
# and body.
|
46
|
+
def to_request request, body
|
47
|
+
::Typhoeus::Request.new request.uri.to_s, \
|
48
|
+
method: request.method,
|
49
|
+
params: request.query || {},
|
50
|
+
headers: request.headers,
|
51
|
+
body: body,
|
52
|
+
accept_encoding: ''
|
53
|
+
end
|
54
|
+
|
55
|
+
# Internal: Creates a Http::Response from a Typhoeus::Response.
|
56
|
+
def to_response request, typhoeus_response
|
57
|
+
Http::Response.new(request).tap do |response|
|
58
|
+
response.code = typhoeus_response.code
|
59
|
+
response.headers = typhoeus_response.headers
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/commute/core/api.rb
CHANGED
@@ -1,10 +1,5 @@
|
|
1
1
|
require 'commute/core/context'
|
2
2
|
|
3
|
-
require 'commute/core/processors/hook'
|
4
|
-
require 'commute/core/processors/request_builder'
|
5
|
-
require 'commute/core/processors/sequencer'
|
6
|
-
require 'commute/core/processors/code_status_processor'
|
7
|
-
|
8
3
|
module Commute
|
9
4
|
|
10
5
|
# Public: An API is a context that already provides some standard
|
@@ -45,6 +40,7 @@ module Commute
|
|
45
40
|
class Api < Builder
|
46
41
|
class << self
|
47
42
|
include Buildable
|
43
|
+
include Forwardable
|
48
44
|
|
49
45
|
# The Builder methods should only be called in the Class itself.
|
50
46
|
# Otherwise, something like TestApi.with ... would modify
|
@@ -55,13 +51,38 @@ module Commute
|
|
55
51
|
private *Builder::METHODS
|
56
52
|
|
57
53
|
# Internal: Memoized base builder.
|
54
|
+
#
|
55
|
+
# The builder is actually an instance of the Api itself.
|
56
|
+
# This means that all methods available on the Api can
|
57
|
+
# be used in the base builder to create a base Api context.
|
58
58
|
def builder
|
59
|
-
@builder ||=
|
59
|
+
@builder ||= self.new Context.new(Stack.new {})
|
60
60
|
end
|
61
61
|
|
62
62
|
# Internal: When an Api inherits another Api, it inherits its context.
|
63
63
|
def inherited klass
|
64
|
-
klass.instance_variable_set :@builder,
|
64
|
+
klass.instance_variable_set :@builder, klass.new(builder.context)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Internal: On include, define forwarding methods to the builder.
|
68
|
+
#
|
69
|
+
# Whenever some extension is included, it works for Api instances.
|
70
|
+
# However we want to define a default context in the Api class, so
|
71
|
+
# the extension methods need to be present the class as wel (and be
|
72
|
+
# forwarded to the builder). We define these forwarding methods here.
|
73
|
+
#
|
74
|
+
def include mod
|
75
|
+
# Do a normal include.
|
76
|
+
super
|
77
|
+
# Define a forwarding method for each extension method.
|
78
|
+
mod.const_get(:METHODS).each do |method|
|
79
|
+
# Define the method.
|
80
|
+
self.instance_eval %{
|
81
|
+
def #{method} *args, &block
|
82
|
+
builder.#{method} *args, &block
|
83
|
+
end
|
84
|
+
}
|
85
|
+
end
|
65
86
|
end
|
66
87
|
end
|
67
88
|
|
@@ -79,28 +100,12 @@ module Commute
|
|
79
100
|
#
|
80
101
|
# Also provides basic on_request, on_response and
|
81
102
|
# on_complete hooks.
|
82
|
-
using do
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
main.append Hook.new, as: :on_complete
|
89
|
-
|
90
|
-
stack.sequence(:execution) do
|
91
|
-
append Proc.new { |c|
|
92
|
-
Configuration.adapter.call c
|
93
|
-
}
|
94
|
-
end
|
95
|
-
|
96
|
-
# sequence(:request) do
|
97
|
-
# append Scope.new(Sequencer.new(:request_body), :body), as: :body
|
98
|
-
# end
|
99
|
-
|
100
|
-
# sequence(:response) do
|
101
|
-
# append Hook.new, as: :on_response
|
102
|
-
# append Scope.new(Sequencer.new(:response_body), :body), as: :body
|
103
|
-
# end
|
103
|
+
using do
|
104
|
+
sequence.append sequence(:request)
|
105
|
+
sequence.append sequence(:response)
|
106
|
+
sequence.append proc { |router, request, options = {}|
|
107
|
+
Configuration.adapter.call router, request, options
|
108
|
+
}
|
104
109
|
end
|
105
110
|
|
106
111
|
# Public: Creates a new Api Builder, starting to build from
|
@@ -112,5 +117,13 @@ module Commute
|
|
112
117
|
def initialize context = self.class.builder.context
|
113
118
|
super
|
114
119
|
end
|
120
|
+
|
121
|
+
# Public: Executes a method only if it exists. Otherwise just
|
122
|
+
# returns self.
|
123
|
+
#
|
124
|
+
# Returns the output of the existing method or self.
|
125
|
+
def try method, *args, &block
|
126
|
+
self.respond_to?(method) ? self.send(method, *args, &block) : self
|
127
|
+
end
|
115
128
|
end
|
116
129
|
end
|