commute 0.2.0.rc.2 → 0.3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://secure.travis-ci.org/challengee/commute.png)](http://travis-ci.org/challengee/commute)
|
3
|
-
[![Dependency Status](https://gemnasium.com/challengee/commute.png?travis)](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
|