routemaster-drain 2.0.0 → 2.2.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +3 -3
- data/gemfiles/rails_3.gemfile.lock +8 -2
- data/gemfiles/rails_4.gemfile.lock +2 -2
- data/gemfiles/rails_5.gemfile.lock +3 -3
- data/lib/routemaster/api_client.rb +36 -2
- data/lib/routemaster/cache.rb +13 -54
- data/lib/routemaster/drain.rb +1 -1
- data/lib/routemaster/errors.rb +8 -8
- data/lib/routemaster/middleware/error_handling.rb +9 -9
- data/lib/routemaster/middleware/metrics.rb +71 -0
- data/lib/routemaster/middleware/response_caching.rb +2 -1
- data/lib/routemaster/resources/rest_resource.rb +8 -0
- data/lib/routemaster/responses/future_response.rb +46 -0
- data/lib/routemaster/responses/hateoas_response.rb +1 -1
- data/spec/routemaster/api_client_spec.rb +17 -0
- data/spec/routemaster/cache_spec.rb +0 -6
- data/spec/routemaster/integration/api_client_spec.rb +134 -18
- data/spec/routemaster/integration/cache_spec.rb +1 -3
- data/spec/routemaster/resources/rest_resource_spec.rb +14 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f2f159625c82cda6149dd385d892605cbbc5381
|
|
4
|
+
data.tar.gz: 4355b7da645ddcaed97629f458ea056e037f1c1f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38adf1dbf83ed92e88ba395cb0cd1cded85c9146d9efafe112d290171663ff7f2794440eb56173e1ea20d05ff30c0353320a834a5d0d6c826294d841028fe4da
|
|
7
|
+
data.tar.gz: 45eac309ef2f3be2339b967b5f46075f1e314a2e07bfe32a1a6b125c4035697872071e4301c644011e4570e61b7676678187a25696e9e4bac18b2b66132ad04e
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
### 2.2.2 (2017-01-10)
|
|
2
|
+
|
|
3
|
+
- Fix logging for error responses:
|
|
4
|
+
For unsuccessful responses rescue the raised error and
|
|
5
|
+
send increment signal to metrics backend
|
|
6
|
+
- Add Telemetry support for requests and responses
|
|
7
|
+
- Add support for PATCH requests.
|
|
8
|
+
Invalidate cached response (if any) on PATCH
|
|
9
|
+
|
|
10
|
+
### 2.0.0 (2016-12-14)
|
|
11
|
+
|
|
12
|
+
Major upgrade: the gem now includes a high-level HTTP API client.
|
|
13
|
+
|
|
14
|
+
- Request materialisation, JSON-HAL parsing, and hypermedia link traversal (#6)
|
|
15
|
+
- Surface HTTP errors as exceptions (#8)
|
|
16
|
+
- Various fixes (#7, #9, #10)
|
|
17
|
+
- Testing against multiple versions of Rails (#11)
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
routemaster-drain (2.
|
|
4
|
+
routemaster-drain (2.2.2)
|
|
5
5
|
faraday (>= 0.9.0)
|
|
6
6
|
faraday_middleware
|
|
7
7
|
hashie
|
|
@@ -31,7 +31,7 @@ GEM
|
|
|
31
31
|
diff-lcs (1.2.5)
|
|
32
32
|
docile (1.1.5)
|
|
33
33
|
dotenv (2.1.1)
|
|
34
|
-
faraday (0.10.
|
|
34
|
+
faraday (0.10.1)
|
|
35
35
|
multipart-post (>= 1.2, < 3)
|
|
36
36
|
faraday_middleware (0.10.1)
|
|
37
37
|
faraday (>= 0.7.4, < 1.0)
|
|
@@ -77,7 +77,7 @@ GEM
|
|
|
77
77
|
pry (~> 0.10)
|
|
78
78
|
psych (2.2.1)
|
|
79
79
|
public_suffix (2.0.4)
|
|
80
|
-
rack (1.
|
|
80
|
+
rack (1.6.5)
|
|
81
81
|
rack-protection (1.5.3)
|
|
82
82
|
rack
|
|
83
83
|
rack-test (0.6.3)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
routemaster-drain (
|
|
4
|
+
routemaster-drain (2.2.1)
|
|
5
5
|
faraday (>= 0.9.0)
|
|
6
6
|
faraday_middleware
|
|
7
7
|
hashie
|
|
@@ -31,6 +31,10 @@ GEM
|
|
|
31
31
|
activesupport (= 3.2.14)
|
|
32
32
|
builder (~> 3.0.0)
|
|
33
33
|
activerecord (3.2.14)
|
|
34
|
+
activemodel (= 3.2.14)
|
|
35
|
+
activesupport (= 3.2.14)
|
|
36
|
+
arel (~> 3.0.2)
|
|
37
|
+
tzinfo (~> 0.3.29)
|
|
34
38
|
activeresource (3.2.14)
|
|
35
39
|
activemodel (= 3.2.14)
|
|
36
40
|
activesupport (= 3.2.14)
|
|
@@ -43,6 +47,7 @@ GEM
|
|
|
43
47
|
bundler
|
|
44
48
|
rake
|
|
45
49
|
thor (>= 0.14.0)
|
|
50
|
+
arel (3.0.3)
|
|
46
51
|
builder (3.0.4)
|
|
47
52
|
byebug (9.0.6)
|
|
48
53
|
codeclimate-test-reporter (1.0.3)
|
|
@@ -108,7 +113,7 @@ GEM
|
|
|
108
113
|
pry-byebug (3.4.2)
|
|
109
114
|
byebug (~> 9.0)
|
|
110
115
|
pry (~> 0.10)
|
|
111
|
-
psych (2.2.
|
|
116
|
+
psych (2.2.2)
|
|
112
117
|
public_suffix (2.0.4)
|
|
113
118
|
rack (1.4.7)
|
|
114
119
|
rack-cache (1.6.1)
|
|
@@ -191,6 +196,7 @@ GEM
|
|
|
191
196
|
treetop (1.4.15)
|
|
192
197
|
polyglot
|
|
193
198
|
polyglot (>= 0.3.1)
|
|
199
|
+
tzinfo (0.3.52)
|
|
194
200
|
vegas (0.1.11)
|
|
195
201
|
rack (>= 1.0.0)
|
|
196
202
|
webmock (2.3.1)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
routemaster-drain (
|
|
4
|
+
routemaster-drain (2.2.1)
|
|
5
5
|
faraday (>= 0.9.0)
|
|
6
6
|
faraday_middleware
|
|
7
7
|
hashie
|
|
@@ -110,7 +110,7 @@ GEM
|
|
|
110
110
|
pry-byebug (3.4.2)
|
|
111
111
|
byebug (~> 9.0)
|
|
112
112
|
pry (~> 0.10)
|
|
113
|
-
psych (2.2.
|
|
113
|
+
psych (2.2.2)
|
|
114
114
|
public_suffix (2.0.4)
|
|
115
115
|
rack (1.5.5)
|
|
116
116
|
rack-protection (1.5.3)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../
|
|
3
3
|
specs:
|
|
4
|
-
routemaster-drain (
|
|
4
|
+
routemaster-drain (2.2.1)
|
|
5
5
|
faraday (>= 0.9.0)
|
|
6
6
|
faraday_middleware
|
|
7
7
|
hashie
|
|
@@ -111,7 +111,7 @@ GEM
|
|
|
111
111
|
mime-types-data (~> 3.2015)
|
|
112
112
|
mime-types-data (3.2016.0521)
|
|
113
113
|
mini_portile2 (2.1.0)
|
|
114
|
-
minitest (5.
|
|
114
|
+
minitest (5.10.1)
|
|
115
115
|
mono_logger (1.1.0)
|
|
116
116
|
multi_json (1.12.1)
|
|
117
117
|
multipart-post (2.0.0)
|
|
@@ -130,7 +130,7 @@ GEM
|
|
|
130
130
|
pry-byebug (3.4.2)
|
|
131
131
|
byebug (~> 9.0)
|
|
132
132
|
pry (~> 0.10)
|
|
133
|
-
psych (2.2.
|
|
133
|
+
psych (2.2.2)
|
|
134
134
|
public_suffix (2.0.4)
|
|
135
135
|
rack (2.0.1)
|
|
136
136
|
rack-protection (1.5.3)
|
|
@@ -5,13 +5,21 @@ require 'hashie'
|
|
|
5
5
|
require 'routemaster/config'
|
|
6
6
|
require 'routemaster/middleware/response_caching'
|
|
7
7
|
require 'routemaster/middleware/error_handling'
|
|
8
|
+
require 'routemaster/middleware/metrics'
|
|
9
|
+
require 'routemaster/responses/future_response'
|
|
8
10
|
|
|
9
11
|
module Routemaster
|
|
10
12
|
class APIClient
|
|
11
|
-
def initialize(middlewares: [],
|
|
13
|
+
def initialize(middlewares: [],
|
|
14
|
+
listener: nil,
|
|
15
|
+
response_class: nil,
|
|
16
|
+
metrics_client: nil,
|
|
17
|
+
source_peer: nil)
|
|
12
18
|
@listener = listener
|
|
13
19
|
@middlewares = middlewares
|
|
14
20
|
@response_class = response_class
|
|
21
|
+
@metrics_client = metrics_client
|
|
22
|
+
@source_peer = source_peer
|
|
15
23
|
end
|
|
16
24
|
|
|
17
25
|
# Performs a GET HTTP request for the `url`, with optional
|
|
@@ -27,13 +35,38 @@ module Routemaster
|
|
|
27
35
|
end
|
|
28
36
|
end
|
|
29
37
|
|
|
38
|
+
def fget(url, params: {}, headers: {})
|
|
39
|
+
Responses::FutureResponse.new { get(url, params: {}, headers: {}) }
|
|
40
|
+
end
|
|
41
|
+
|
|
30
42
|
def post(url, body: {}, headers: {})
|
|
31
43
|
host = URI.parse(url).host
|
|
32
44
|
response_wrapper do
|
|
33
45
|
connection.post do |req|
|
|
34
46
|
req.url url
|
|
35
47
|
req.headers = headers.merge(auth_header(host))
|
|
36
|
-
req.body = body
|
|
48
|
+
req.body = body
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def patch(url, body: {}, headers: {})
|
|
54
|
+
host = URI.parse(url).host
|
|
55
|
+
response_wrapper do
|
|
56
|
+
connection.patch do |req|
|
|
57
|
+
req.url url
|
|
58
|
+
req.headers = headers.merge(auth_header(host))
|
|
59
|
+
req.body = body
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def delete(url, headers: {})
|
|
65
|
+
host = URI.parse(url).host
|
|
66
|
+
response_wrapper do
|
|
67
|
+
connection.delete do |req|
|
|
68
|
+
req.url url
|
|
69
|
+
req.headers = headers.merge(auth_header(host))
|
|
37
70
|
end
|
|
38
71
|
end
|
|
39
72
|
end
|
|
@@ -56,6 +89,7 @@ module Routemaster
|
|
|
56
89
|
f.response :mashify
|
|
57
90
|
f.response :json, content_type: /\bjson/
|
|
58
91
|
f.use Routemaster::Middleware::ResponseCaching, listener: @listener
|
|
92
|
+
f.use Routemaster::Middleware::Metrics, client: @metrics_client, source_peer: @source_peer
|
|
59
93
|
f.adapter :net_http_persistent
|
|
60
94
|
f.use Routemaster::Middleware::ErrorHandling
|
|
61
95
|
|
data/lib/routemaster/cache.rb
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
require 'routemaster/api_client'
|
|
2
|
-
require 'thread/pool'
|
|
3
|
-
require 'thread/future'
|
|
4
|
-
require 'singleton'
|
|
5
|
-
require 'delegate'
|
|
6
|
-
require 'json'
|
|
7
2
|
require 'wisper'
|
|
8
3
|
|
|
9
4
|
module Routemaster
|
|
@@ -21,47 +16,8 @@ module Routemaster
|
|
|
21
16
|
class Cache
|
|
22
17
|
include Wisper::Publisher
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
include Singleton
|
|
27
|
-
|
|
28
|
-
def initialize
|
|
29
|
-
Thread.pool(5, 20).tap do |p|
|
|
30
|
-
# TODO: configurable pool size and trim timeout?
|
|
31
|
-
p.auto_trim!
|
|
32
|
-
p.idle_trim! 10 # 10 seconds
|
|
33
|
-
super p
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Wraps a future response, so it quacks exactly like an ordinary response.
|
|
39
|
-
class FutureResponse
|
|
40
|
-
extend Forwardable
|
|
41
|
-
|
|
42
|
-
# The `block` is expected to return a {Response}
|
|
43
|
-
def initialize(&block)
|
|
44
|
-
@future = Pool.instance.future(&block)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# @!attribute status
|
|
48
|
-
# @return [Integer]
|
|
49
|
-
# Delegated to the `block`'s return value.
|
|
50
|
-
|
|
51
|
-
# @!attribute headers
|
|
52
|
-
# @return [Hash]
|
|
53
|
-
# Delegated to the `block`'s return value.
|
|
54
|
-
|
|
55
|
-
# @!attribute body
|
|
56
|
-
# @return [Hashie::Mash]
|
|
57
|
-
# Delegated to the `block`'s return value.
|
|
58
|
-
|
|
59
|
-
delegate :value => :@future
|
|
60
|
-
delegate %i(status headers body) => :value
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def initialize(redis:nil, client:nil)
|
|
64
|
-
@redis = redis || Config.cache_redis
|
|
19
|
+
def initialize(redis: nil, client: nil)
|
|
20
|
+
@redis = redis || Config.cache_redis
|
|
65
21
|
@client = client || APIClient.new(listener: self)
|
|
66
22
|
end
|
|
67
23
|
|
|
@@ -87,13 +43,7 @@ module Routemaster
|
|
|
87
43
|
#
|
|
88
44
|
# @return [Response], which responds to `status`, `headers`, and `body`.
|
|
89
45
|
def get(url, version: nil, locale: nil)
|
|
90
|
-
headers
|
|
91
|
-
'Accept' => version ?
|
|
92
|
-
"application/json;v=#{version}" :
|
|
93
|
-
"application/json"
|
|
94
|
-
}
|
|
95
|
-
headers['Accept-Language'] = locale if locale
|
|
96
|
-
@client.get(url, headers: headers)
|
|
46
|
+
@client.get(url, headers: headers(version: version, locale: locale))
|
|
97
47
|
end
|
|
98
48
|
|
|
99
49
|
# Like {#get}, but schedules any request in the background using a thread
|
|
@@ -102,7 +52,16 @@ module Routemaster
|
|
|
102
52
|
# @return [FutureResponse], which responds to `status`, `headers`, and `body`
|
|
103
53
|
# like [Response].
|
|
104
54
|
def fget(url, version: nil, locale: nil)
|
|
105
|
-
|
|
55
|
+
@client.fget(url, headers: headers(version: version, locale: locale))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def headers(version: nil, locale: nil)
|
|
61
|
+
@headers ||= {}.tap do |hash|
|
|
62
|
+
hash['Accept'] = version ? "application/json;v=#{version}" : "application/json"
|
|
63
|
+
hash['Accept-Language'] = locale if locale
|
|
64
|
+
end
|
|
106
65
|
end
|
|
107
66
|
end
|
|
108
67
|
end
|
data/lib/routemaster/drain.rb
CHANGED
data/lib/routemaster/errors.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Routemaster
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def errors
|
|
12
|
-
body
|
|
12
|
+
body.fetch('errors', {})
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def message
|
|
@@ -27,44 +27,44 @@ module Routemaster
|
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
class
|
|
30
|
+
class UnauthorizedResourceAccess < BaseError
|
|
31
31
|
def message
|
|
32
32
|
"Unauthorized Resource Access Error"
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
class
|
|
36
|
+
class InvalidResource < BaseError
|
|
37
37
|
def message
|
|
38
38
|
"Invalid Resource Error"
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
class
|
|
42
|
+
class ResourceNotFound < BaseError
|
|
43
43
|
def message
|
|
44
44
|
"Resource Not Found Error"
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
class
|
|
48
|
+
class FatalResource < BaseError
|
|
49
49
|
def message
|
|
50
50
|
"Fatal Resource Error. body: #{body}, url: #{env.url}, method: #{env.method}"
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
class
|
|
54
|
+
class ConflictResource < BaseError
|
|
55
55
|
def message
|
|
56
56
|
"ConflictResourceError Resource Error"
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
class
|
|
60
|
+
class IncompatibleVersion < BaseError
|
|
61
61
|
def message
|
|
62
62
|
headers = env.request_headers.select { |k, _| k != 'Authorization' }
|
|
63
63
|
"Incompatible Version Error. headers: #{headers}, url: #{env.url}, method: #{env.method}"
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
class
|
|
67
|
+
class ResourceThrottling < BaseError
|
|
68
68
|
def message
|
|
69
69
|
"Resource Throttling Error"
|
|
70
70
|
end
|
|
@@ -5,15 +5,15 @@ module Routemaster
|
|
|
5
5
|
module Middleware
|
|
6
6
|
class ErrorHandling < Faraday::Response::Middleware
|
|
7
7
|
ERRORS_MAPPING = {
|
|
8
|
-
(400..400) => Errors::
|
|
9
|
-
(401..401) => Errors::
|
|
10
|
-
(403..403) => Errors::
|
|
11
|
-
(404..404) => Errors::
|
|
12
|
-
(409..409) => Errors::
|
|
13
|
-
(412..412) => Errors::
|
|
14
|
-
(413..413) => Errors::
|
|
15
|
-
(429..429) => Errors::
|
|
16
|
-
(407..500) => Errors::
|
|
8
|
+
(400..400) => Errors::InvalidResource,
|
|
9
|
+
(401..401) => Errors::UnauthorizedResourceAccess,
|
|
10
|
+
(403..403) => Errors::UnauthorizedResourceAccess,
|
|
11
|
+
(404..404) => Errors::ResourceNotFound,
|
|
12
|
+
(409..409) => Errors::ConflictResource,
|
|
13
|
+
(412..412) => Errors::IncompatibleVersion,
|
|
14
|
+
(413..413) => Errors::InvalidResource,
|
|
15
|
+
(429..429) => Errors::ResourceThrottling,
|
|
16
|
+
(407..500) => Errors::FatalResource
|
|
17
17
|
}.freeze
|
|
18
18
|
|
|
19
19
|
def on_complete(env)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Routemaster
|
|
2
|
+
module Middleware
|
|
3
|
+
class Metrics
|
|
4
|
+
INTERACTION_KEY = 'api_client'.freeze
|
|
5
|
+
|
|
6
|
+
def initialize(app, client: nil, source_peer: nil)
|
|
7
|
+
@app = app
|
|
8
|
+
@client = client
|
|
9
|
+
@source_peer = source_peer
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(request_env)
|
|
13
|
+
return @app.call(request_env) unless can_log?
|
|
14
|
+
|
|
15
|
+
increment_req_count(request_tags(request_env))
|
|
16
|
+
|
|
17
|
+
record_latency(request_tags(request_env)) do
|
|
18
|
+
begin
|
|
19
|
+
@app.call(request_env).on_complete do |response_env|
|
|
20
|
+
increment_response_count(response_tags(response_env))
|
|
21
|
+
end
|
|
22
|
+
rescue Routemaster::Errors::BaseError => e
|
|
23
|
+
increment_response_count(response_tags(e.env))
|
|
24
|
+
raise e
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :client, :source_peer
|
|
32
|
+
|
|
33
|
+
def increment_req_count(tags)
|
|
34
|
+
client.increment("#{INTERACTION_KEY}.request.count", tags: tags)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def increment_response_count(tags)
|
|
38
|
+
client.increment("#{INTERACTION_KEY}.response.count", tags: tags)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def record_latency(tags, &block)
|
|
42
|
+
client.time("#{INTERACTION_KEY}.latency", tags: tags) do
|
|
43
|
+
block.call
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def can_log?
|
|
48
|
+
client && source_peer
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def destination_peer(env)
|
|
52
|
+
env.url.host
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def peers_tags(env)
|
|
56
|
+
[
|
|
57
|
+
"source:#{source_peer}",
|
|
58
|
+
"destination:#{destination_peer(env)}"
|
|
59
|
+
]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def request_tags(env)
|
|
63
|
+
peers_tags(env).concat(["verb:#{env.method}"])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def response_tags(env)
|
|
67
|
+
peers_tags(env).concat(["status:#{env.status}"])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -16,7 +16,8 @@ module Routemaster
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def call(env)
|
|
19
|
-
|
|
19
|
+
@cache.del(cache_key(env)) if %i(patch delete).include?(env.method)
|
|
20
|
+
return @app.call(env) if env.method != :get
|
|
20
21
|
|
|
21
22
|
fetch_from_cache(env) || fetch_from_service(env)
|
|
22
23
|
end
|
|
@@ -21,6 +21,14 @@ module Routemaster
|
|
|
21
21
|
def index
|
|
22
22
|
@client.get(@url)
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
def update(id=nil, params)
|
|
26
|
+
@client.patch(@url.gsub('{id}', id.to_s), body: params)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def destroy(id=nil)
|
|
30
|
+
@client.delete(@url.gsub('{id}', id.to_s))
|
|
31
|
+
end
|
|
24
32
|
end
|
|
25
33
|
end
|
|
26
34
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'thread/pool'
|
|
2
|
+
require 'thread/future'
|
|
3
|
+
require 'singleton'
|
|
4
|
+
require 'delegate'
|
|
5
|
+
|
|
6
|
+
module Routemaster
|
|
7
|
+
module Responses
|
|
8
|
+
# A pool of threads, used for parallel/future request processing.
|
|
9
|
+
class Pool < SimpleDelegator
|
|
10
|
+
include Singleton
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
Thread.pool(5, 20).tap do |p|
|
|
14
|
+
# TODO: configurable pool size and trim timeout?
|
|
15
|
+
p.auto_trim!
|
|
16
|
+
p.idle_trim! 10 # 10 seconds
|
|
17
|
+
super p
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class FutureResponse
|
|
23
|
+
extend Forwardable
|
|
24
|
+
|
|
25
|
+
# The `block` is expected to return a {Response}
|
|
26
|
+
def initialize(&block)
|
|
27
|
+
@future = Pool.instance.future(&block)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @!attribute status
|
|
31
|
+
# @return [Integer]
|
|
32
|
+
# Delegated to the `block`'s return value.
|
|
33
|
+
|
|
34
|
+
# @!attribute headers
|
|
35
|
+
# @return [Hash]
|
|
36
|
+
# Delegated to the `block`'s return value.
|
|
37
|
+
|
|
38
|
+
# @!attribute body
|
|
39
|
+
# @return [Hashie::Mash]
|
|
40
|
+
# Delegated to the `block`'s return value.
|
|
41
|
+
|
|
42
|
+
delegate :value => :@future
|
|
43
|
+
delegate %i(status headers body) => :value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -26,7 +26,7 @@ module Routemaster
|
|
|
26
26
|
unless respond_to?(method_name)
|
|
27
27
|
resource = Resources::RestResource.new(_links[normalized_method_name]['href'], client: @client)
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
define_singleton_method(method_name) do |*m_args|
|
|
30
30
|
resource
|
|
31
31
|
end
|
|
32
32
|
|
|
@@ -32,6 +32,14 @@ describe Routemaster::APIClient do
|
|
|
32
32
|
'content-type' => 'application/json;v=1'
|
|
33
33
|
}
|
|
34
34
|
)
|
|
35
|
+
|
|
36
|
+
@patch_req = stub_request(:patch, /example\.com/).to_return(
|
|
37
|
+
status: 200,
|
|
38
|
+
body: { id: 132, type: 'widget' }.to_json,
|
|
39
|
+
headers: {
|
|
40
|
+
'content-type' => 'application/json;v=1'
|
|
41
|
+
}
|
|
42
|
+
)
|
|
35
43
|
end
|
|
36
44
|
|
|
37
45
|
it 'GETs from the URL' do
|
|
@@ -48,6 +56,15 @@ describe Routemaster::APIClient do
|
|
|
48
56
|
end
|
|
49
57
|
end
|
|
50
58
|
|
|
59
|
+
context 'PATCH request' do
|
|
60
|
+
subject { fetcher.patch(url, body: {}, headers: headers) }
|
|
61
|
+
|
|
62
|
+
it 'PATCH from the URL' do
|
|
63
|
+
subject
|
|
64
|
+
expect(@patch_req).to have_been_requested
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
51
68
|
it 'has :status, :headers, :body' do
|
|
52
69
|
expect(subject.status).to eq(200)
|
|
53
70
|
expect(subject.headers).to have_key('content-type')
|
|
@@ -70,12 +70,6 @@ module Routemaster
|
|
|
70
70
|
it_behaves_like 'a GET request'
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
describe '#fget' do
|
|
74
|
-
let(:perform) { subject.fget(url, **options) }
|
|
75
|
-
|
|
76
|
-
it_behaves_like 'a GET request'
|
|
77
|
-
end
|
|
78
|
-
|
|
79
73
|
describe '#bust' do
|
|
80
74
|
let(:cache) { Config.cache_redis }
|
|
81
75
|
let(:perform) { subject.bust(url) }
|
|
@@ -5,6 +5,14 @@ require 'routemaster/api_client'
|
|
|
5
5
|
require 'webrick'
|
|
6
6
|
|
|
7
7
|
RSpec.describe 'Api client integration specs' do
|
|
8
|
+
module WEBrick
|
|
9
|
+
module HTTPServlet
|
|
10
|
+
class ProcHandler
|
|
11
|
+
alias do_PATCH do_GET
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
8
16
|
uses_dotenv
|
|
9
17
|
uses_redis
|
|
10
18
|
|
|
@@ -17,6 +25,11 @@ RSpec.describe 'Api client integration specs' do
|
|
|
17
25
|
res.body = { field: 'test' }.to_json
|
|
18
26
|
end
|
|
19
27
|
end
|
|
28
|
+
|
|
29
|
+
server.mount_proc "/success" do |req, res|
|
|
30
|
+
res.status = 200
|
|
31
|
+
res.body = { field: 'test' }.to_json
|
|
32
|
+
end
|
|
20
33
|
end
|
|
21
34
|
end
|
|
22
35
|
|
|
@@ -37,40 +50,143 @@ RSpec.describe 'Api client integration specs' do
|
|
|
37
50
|
let(:host) { 'http://localhost:8000' }
|
|
38
51
|
|
|
39
52
|
describe 'error handling' do
|
|
40
|
-
it 'raises an
|
|
41
|
-
expect { subject.get(host + '/404') }.to raise_error(Routemaster::Errors::
|
|
53
|
+
it 'raises an ResourceNotFound on 404' do
|
|
54
|
+
expect { subject.get(host + '/404') }.to raise_error(Routemaster::Errors::ResourceNotFound)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'raises an InvalidResource on 400' do
|
|
58
|
+
expect { subject.get(host + '/400') }.to raise_error(Routemaster::Errors::InvalidResource)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'raises an UnauthorizedResourceAccess on 401' do
|
|
62
|
+
expect { subject.get(host + '/401') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccess)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'raises an UnauthorizedResourceAccess on 403' do
|
|
66
|
+
expect { subject.get(host + '/403') }.to raise_error(Routemaster::Errors::UnauthorizedResourceAccess)
|
|
42
67
|
end
|
|
43
68
|
|
|
44
|
-
it 'raises an
|
|
45
|
-
expect { subject.get(host + '/
|
|
69
|
+
it 'raises an ConflictResource on 409' do
|
|
70
|
+
expect { subject.get(host + '/409') }.to raise_error(Routemaster::Errors::ConflictResource)
|
|
46
71
|
end
|
|
47
72
|
|
|
48
|
-
it 'raises an
|
|
49
|
-
expect { subject.get(host + '/
|
|
73
|
+
it 'raises an IncompatibleVersion on 412' do
|
|
74
|
+
expect { subject.get(host + '/412') }.to raise_error(Routemaster::Errors::IncompatibleVersion)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'raises an InvalidResource on 413' do
|
|
78
|
+
expect { subject.get(host + '/413') }.to raise_error(Routemaster::Errors::InvalidResource)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'raises an ResourceThrottling on 429' do
|
|
82
|
+
expect { subject.get(host + '/429') }.to raise_error(Routemaster::Errors::ResourceThrottling)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'raises an FatalResource on 500' do
|
|
86
|
+
expect { subject.get(host + '/500') }.to raise_error(Routemaster::Errors::FatalResource)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe 'Future GET request' do
|
|
91
|
+
let(:body_cache_keys) { ["cache:#{url}", "v:,l:,body"] }
|
|
92
|
+
let(:headers_cache_keys) { ["cache:#{url}", "v:,l:,headers"] }
|
|
93
|
+
let(:url) { "#{host}/success" }
|
|
94
|
+
|
|
95
|
+
context 'when there is a previous cached resource' do
|
|
96
|
+
let(:cache) { Routemaster::Config.cache_redis }
|
|
97
|
+
|
|
98
|
+
it 'returns the response from within the future' do
|
|
99
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be_nil
|
|
100
|
+
|
|
101
|
+
future = subject.fget(url)
|
|
102
|
+
expect(future).to be_an_instance_of(Routemaster::Responses::FutureResponse)
|
|
103
|
+
|
|
104
|
+
future.value
|
|
105
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
|
|
106
|
+
end
|
|
50
107
|
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe 'PATCH request' do
|
|
111
|
+
let(:body_cache_keys) { ["cache:#{url}", "v:,l:,body"] }
|
|
112
|
+
let(:headers_cache_keys) { ["cache:#{url}", "v:,l:,headers"] }
|
|
113
|
+
let(:url) { "#{host}/success" }
|
|
114
|
+
|
|
115
|
+
context 'when there is a previous cached resource' do
|
|
116
|
+
before { subject.get(url) }
|
|
117
|
+
let(:cache) { Routemaster::Config.cache_redis }
|
|
51
118
|
|
|
52
|
-
|
|
53
|
-
|
|
119
|
+
it 'invalidates the cache on update' do
|
|
120
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
|
|
121
|
+
subject.patch(url, body: {})
|
|
122
|
+
|
|
123
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be_nil
|
|
124
|
+
|
|
125
|
+
subject.get(url)
|
|
126
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
|
|
127
|
+
end
|
|
54
128
|
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe 'DELETE request' do
|
|
132
|
+
let(:body_cache_keys) { ["cache:#{url}", "v:,l:,body"] }
|
|
133
|
+
let(:headers_cache_keys) { ["cache:#{url}", "v:,l:,headers"] }
|
|
134
|
+
let(:url) { "#{host}/success" }
|
|
135
|
+
|
|
136
|
+
context 'when there is a previous cached resource' do
|
|
137
|
+
before { subject.get(url) }
|
|
138
|
+
let(:cache) { Routemaster::Config.cache_redis }
|
|
55
139
|
|
|
56
|
-
|
|
57
|
-
|
|
140
|
+
it 'invalidates the cache on destroy' do
|
|
141
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
|
|
142
|
+
subject.delete(url)
|
|
143
|
+
|
|
144
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be_nil
|
|
145
|
+
|
|
146
|
+
subject.get(url)
|
|
147
|
+
expect(cache.hget("cache:#{url}", "v:,l:,body")).to be
|
|
148
|
+
end
|
|
58
149
|
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe 'Telemetry' do
|
|
153
|
+
let(:metrics_client) { double('MetricsClient') }
|
|
154
|
+
let(:source_peer) { 'test_service' }
|
|
155
|
+
let(:url) { "#{host}/success" }
|
|
59
156
|
|
|
60
|
-
|
|
61
|
-
|
|
157
|
+
subject do
|
|
158
|
+
Routemaster::APIClient.new(metrics_client: metrics_client,
|
|
159
|
+
source_peer: source_peer)
|
|
62
160
|
end
|
|
63
161
|
|
|
64
|
-
|
|
65
|
-
|
|
162
|
+
context 'when metrics source peer is absent' do
|
|
163
|
+
subject { Routemaster::APIClient.new(metrics_client: metrics_client) }
|
|
164
|
+
|
|
165
|
+
it 'does not send metrics' do
|
|
166
|
+
expect(metrics_client).to receive(:increment).never
|
|
167
|
+
subject.get(url)
|
|
168
|
+
end
|
|
66
169
|
end
|
|
67
170
|
|
|
68
|
-
it '
|
|
69
|
-
|
|
171
|
+
it 'does send request metrics' do
|
|
172
|
+
allow(metrics_client).to receive(:time).and_yield
|
|
173
|
+
allow(metrics_client).to receive(:increment)
|
|
174
|
+
expected_req_count_tags = ["source:test_service", "destination:localhost", "verb:get"]
|
|
175
|
+
|
|
176
|
+
expect(metrics_client).to receive(:increment).with('api_client.request.count', tags: expected_req_count_tags)
|
|
177
|
+
|
|
178
|
+
subject.get(url)
|
|
70
179
|
end
|
|
71
180
|
|
|
72
|
-
it '
|
|
73
|
-
|
|
181
|
+
it 'does send response metrics' do
|
|
182
|
+
allow(metrics_client).to receive(:increment)
|
|
183
|
+
expected_res_count_tags = ["source:test_service", "destination:localhost", "status:200"]
|
|
184
|
+
expected_latency_tags = ["source:test_service", "destination:localhost", "verb:get"]
|
|
185
|
+
|
|
186
|
+
expect(metrics_client).to receive(:increment).with('api_client.response.count', tags: expected_res_count_tags)
|
|
187
|
+
expect(metrics_client).to receive(:time).with('api_client.latency', tags: expected_latency_tags).and_yield
|
|
188
|
+
|
|
189
|
+
subject.get(url)
|
|
74
190
|
end
|
|
75
191
|
end
|
|
76
192
|
end
|
|
@@ -58,9 +58,7 @@ RSpec.describe 'Requests with caching' do
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
context 'when there is a previous cached response' do
|
|
61
|
-
before
|
|
62
|
-
subject.get(url)
|
|
63
|
-
end
|
|
61
|
+
before { subject.get(url) }
|
|
64
62
|
|
|
65
63
|
it 'fetches the cached response' do
|
|
66
64
|
expect(subject.get(url).body).to eq({ field: 'test' }.to_json)
|
|
@@ -30,6 +30,20 @@ module Routemaster
|
|
|
30
30
|
subject.index
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
describe '#update' do
|
|
35
|
+
it 'updates the given resource' do
|
|
36
|
+
expect(client).to receive(:patch).with(url, body: params)
|
|
37
|
+
subject.update(1, params)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe '#destroy' do
|
|
42
|
+
it 'destroys the given resource' do
|
|
43
|
+
expect(client).to receive(:delete).with(url)
|
|
44
|
+
subject.destroy(1)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
33
47
|
end
|
|
34
48
|
end
|
|
35
49
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: routemaster-drain
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julien Letessier
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2017-01-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -137,6 +137,7 @@ files:
|
|
|
137
137
|
- ".travis.yml"
|
|
138
138
|
- ".yardopts"
|
|
139
139
|
- Appraisals
|
|
140
|
+
- CHANGELOG.md
|
|
140
141
|
- Gemfile
|
|
141
142
|
- Gemfile.lock
|
|
142
143
|
- Guardfile
|
|
@@ -172,11 +173,13 @@ files:
|
|
|
172
173
|
- lib/routemaster/middleware/dirty.rb
|
|
173
174
|
- lib/routemaster/middleware/error_handling.rb
|
|
174
175
|
- lib/routemaster/middleware/filter.rb
|
|
176
|
+
- lib/routemaster/middleware/metrics.rb
|
|
175
177
|
- lib/routemaster/middleware/parse.rb
|
|
176
178
|
- lib/routemaster/middleware/response_caching.rb
|
|
177
179
|
- lib/routemaster/middleware/root_post_only.rb
|
|
178
180
|
- lib/routemaster/redis_broker.rb
|
|
179
181
|
- lib/routemaster/resources/rest_resource.rb
|
|
182
|
+
- lib/routemaster/responses/future_response.rb
|
|
180
183
|
- lib/routemaster/responses/hateoas_response.rb
|
|
181
184
|
- routemaster-drain.gemspec
|
|
182
185
|
- spec/routemaster/api_client_spec.rb
|