routemaster-drain 2.0.0 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|