routemaster-client 2.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +38 -13
- data/routemaster/client.rb +128 -101
- data/routemaster/client/assertion_helpers.rb +18 -0
- data/routemaster/client/backends/missing_asynchronous.rb +15 -0
- data/routemaster/client/backends/sidekiq.rb +20 -11
- data/routemaster/client/backends/sidekiq/configuration.rb +48 -0
- data/routemaster/client/backends/sidekiq/worker.rb +5 -10
- data/routemaster/client/backends/synchronous.rb +3 -13
- data/routemaster/client/configuration.rb +70 -0
- data/routemaster/client/connection.rb +50 -51
- data/routemaster/client/errors.rb +12 -0
- data/routemaster/client/version.rb +2 -2
- data/spec/client/backends/sidekiq/configuration_spec.rb +64 -0
- data/spec/client/configuration_spec.rb +215 -0
- data/spec/client_spec.rb +133 -75
- data/spec/spec_helper.rb +6 -0
- data/spec/support/configuration_helper.rb +17 -0
- metadata +13 -3
- data/routemaster/client/backends.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17d6f2b6dd28fd082771df65f57fd41f8cbbc2f6
|
4
|
+
data.tar.gz: a97e8c84d7c1a193e64fefbf2a55a18805d65aec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b8fa2d106cc69d3cb0edc75eb430d8c659a81b2686b92b6501b3789f006dbf0e92a7e4900361f2461090cb4f2560267db2d016b0f6c3d554c1171e94127e897
|
7
|
+
data.tar.gz: 66d7c256ae0ada32ea8afed4590bcd4a99df183791a8193a08249ea97f76d9586be067bc322f47b435bde09f90d5f88779a711121c1dc29b1df84746cec532ef
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -25,27 +25,42 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
```ruby
|
27
27
|
require 'routemaster/client'
|
28
|
-
|
28
|
+
Routemaster::Client.configure do |config|
|
29
|
+
config.url = 'https://bs.example.com'
|
30
|
+
config.uuid = 'demo'
|
31
|
+
end
|
29
32
|
```
|
30
33
|
|
31
34
|
You can also specify a timeout value in seconds if you like with the ```timeout``` option.
|
32
35
|
|
33
36
|
```ruby
|
34
|
-
Routemaster::Client.
|
37
|
+
Routemaster::Client.configure do |config|
|
38
|
+
config.url = 'https://bs.example.com'
|
39
|
+
config.uuid = 'demo'
|
40
|
+
config.timeout = 2
|
41
|
+
end
|
35
42
|
```
|
36
43
|
|
37
44
|
If you are using Sidekiq in your project, you can specify the usage of a Sidekiq backend, where event sending will be processed asynchronously.
|
38
45
|
|
39
46
|
```ruby
|
40
|
-
Routemaster::Client.
|
47
|
+
Routemaster::Client.configure do |config|
|
48
|
+
config.url = 'https://bs.example.com'
|
49
|
+
config.uuid = 'demo'
|
50
|
+
config.timeout = 2
|
51
|
+
config.async_backend = Routemaster::Client::Backends::Sidekiq.configure do |sidekiq|
|
52
|
+
sidekiq.queue = :realtime
|
53
|
+
sidekiq.retry = false
|
54
|
+
end
|
55
|
+
end
|
41
56
|
```
|
42
57
|
|
43
58
|
**Push** an event about an entity in the topic `widgets` with a callback URL:
|
44
59
|
|
45
60
|
```ruby
|
46
|
-
|
47
|
-
|
48
|
-
|
61
|
+
Routemaster::Client.created('widgets', 'https://app.example.com/widgets/1')
|
62
|
+
Routemaster::Client.updated('widgets', 'https://app.example.com/widgets/2')
|
63
|
+
Routemaster::Client.noop('widgets', 'https://app.example.com/widgets/3')
|
49
64
|
```
|
50
65
|
|
51
66
|
There are methods for the four canonical event types: `created`, `updated`,
|
@@ -59,14 +74,24 @@ A timestamp argument may be passed (it will be set by the bus automatically
|
|
59
74
|
otherwise); it must be an integer number of milliseconds since the UNIX Epoch:
|
60
75
|
|
61
76
|
```ruby
|
62
|
-
|
77
|
+
Routemaster::Client.created('widgets', 'https://app.example.com/widgets/1', 1473080555409)
|
63
78
|
```
|
64
79
|
|
80
|
+
**Async**
|
81
|
+
You can also push events asynchronously if you have an async backend defined, for each
|
82
|
+
event method there is a corresponding `event_async` method. eg
|
83
|
+
```ruby
|
84
|
+
Routemaster::Client.updated_async('widgets', 'https://app.example.com/widgets/2')
|
85
|
+
```
|
86
|
+
|
87
|
+
You cannot use these methods without defining an async backend, if you try then an error will
|
88
|
+
be raised.
|
89
|
+
|
65
90
|
**Subscribe** to be notified about `widgets` and `kitten` at most 60 seconds after
|
66
91
|
events, in batches of at most 500 events, to a given callback URL:
|
67
92
|
|
68
93
|
```ruby
|
69
|
-
|
94
|
+
Routemaster::Client.subscribe(
|
70
95
|
topics: ['widgets', 'kitten'],
|
71
96
|
callback: 'https://app.example.com/events',
|
72
97
|
uuid: 'demo',
|
@@ -102,29 +127,29 @@ gem](https://github.com/krisleech/wisper#wisper).
|
|
102
127
|
**Unsubscribe** from a single topic:
|
103
128
|
|
104
129
|
```ruby
|
105
|
-
|
130
|
+
Routemaster::Client.unsubscribe('widgets')
|
106
131
|
```
|
107
132
|
|
108
133
|
**Unsubscribe** from all topics:
|
109
134
|
|
110
135
|
```ruby
|
111
|
-
|
136
|
+
Routemaster::Client.unsubscribe_all
|
112
137
|
```
|
113
138
|
|
114
139
|
**Delete** a topic (only possible if you're the emitter for this topic):
|
115
140
|
|
116
141
|
```ruby
|
117
|
-
|
142
|
+
Routemaster::Client.delete_topic('widgets')
|
118
143
|
```
|
119
144
|
|
120
145
|
|
121
146
|
**Monitor** the status of topics and subscriptions:
|
122
147
|
|
123
148
|
```ruby
|
124
|
-
|
149
|
+
Routemaster::Client.monitor_topics
|
125
150
|
#=> [ #<Routemaster::Topic:XXXX @name="widgets", @publisher="demo", @events=12589>, ...]
|
126
151
|
|
127
|
-
|
152
|
+
Routemaster::Client.monitor_subscriptions
|
128
153
|
#=> [ {
|
129
154
|
# subscriber: 'bob',
|
130
155
|
# callback: 'https://app.example.com/events',
|
data/routemaster/client.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
require 'routemaster/client/backends'
|
1
|
+
require 'routemaster/client/backends/synchronous'
|
2
2
|
require 'routemaster/client/connection'
|
3
|
+
require 'routemaster/client/configuration'
|
3
4
|
require 'routemaster/client/version'
|
5
|
+
require 'routemaster/client/errors'
|
6
|
+
require 'routemaster/client/assertion_helpers'
|
4
7
|
require 'routemaster/topic'
|
5
8
|
require 'uri'
|
6
9
|
require 'json'
|
@@ -10,150 +13,174 @@ require 'typhoeus/adapters/faraday'
|
|
10
13
|
require 'oj'
|
11
14
|
|
12
15
|
module Routemaster
|
13
|
-
|
16
|
+
module Client
|
17
|
+
class << self
|
18
|
+
extend Forwardable
|
19
|
+
include AssertionHelpers
|
20
|
+
|
21
|
+
def_delegator :'Routemaster::Client::Configuration', :async_backend
|
22
|
+
def_delegator :'Routemaster::Client::Configuration', :lazy
|
23
|
+
|
24
|
+
def configure
|
25
|
+
self.tap do
|
26
|
+
Configuration.configure do |c|
|
27
|
+
yield c
|
28
|
+
end
|
29
|
+
_check_pulse! unless lazy
|
30
|
+
end
|
31
|
+
end
|
14
32
|
|
15
|
-
|
16
|
-
|
17
|
-
o[:timeout] ||= 1
|
18
|
-
o[:backend_type] ||= Backends::Synchronous
|
33
|
+
def created(topic, callback, timestamp = nil)
|
34
|
+
_send_event('create', topic, callback, timestamp)
|
19
35
|
end
|
20
36
|
|
21
|
-
|
37
|
+
def created_async(topic, callback, timestamp = nil)
|
38
|
+
_send_event('create', topic, callback, timestamp, async: true)
|
39
|
+
end
|
22
40
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
_assert_valid_timeout(@_options[:timeout])
|
41
|
+
def updated(topic, callback, timestamp = nil)
|
42
|
+
_send_event('update', topic, callback, timestamp)
|
43
|
+
end
|
27
44
|
|
45
|
+
def updated_async(topic, callback, timestamp = nil)
|
46
|
+
_send_event('update', topic, callback, timestamp, async: true)
|
47
|
+
end
|
28
48
|
|
29
|
-
|
30
|
-
|
31
|
-
raise 'cannot connect to bus' unless response.success?
|
32
|
-
end
|
49
|
+
def deleted(topic, callback, timestamp = nil)
|
50
|
+
_send_event('delete', topic, callback, timestamp)
|
33
51
|
end
|
34
|
-
end
|
35
52
|
|
36
|
-
|
37
|
-
|
38
|
-
|
53
|
+
def deleted_async(topic, callback, timestamp = nil)
|
54
|
+
_send_event('delete', topic, callback, timestamp, async: true)
|
55
|
+
end
|
39
56
|
|
40
|
-
|
41
|
-
|
42
|
-
|
57
|
+
def noop(topic, callback, timestamp = nil)
|
58
|
+
_send_event('noop', topic, callback, timestamp)
|
59
|
+
end
|
43
60
|
|
44
|
-
|
45
|
-
|
46
|
-
|
61
|
+
def noop_async(topic, callback, timestamp = nil)
|
62
|
+
_send_event('noop', topic, callback, timestamp, async: true)
|
63
|
+
end
|
47
64
|
|
48
|
-
|
49
|
-
|
50
|
-
|
65
|
+
def subscribe(topics:, callback:, **options)
|
66
|
+
_assert_valid_topics!(topics)
|
67
|
+
_assert_valid_url!(callback)
|
68
|
+
_assert_valid_max_events! options[:max] if options[:max]
|
69
|
+
_assert_valid_timeout! options[:timeout] if options[:timeout]
|
51
70
|
|
52
|
-
|
53
|
-
if (options.keys - [:topics, :callback, :timeout, :max, :uuid]).any?
|
54
|
-
raise ArgumentError.new('bad options')
|
71
|
+
_conn.subscribe(topics: topics, callback: callback, **options)
|
55
72
|
end
|
56
|
-
_assert options[:topics].kind_of?(Enumerable), 'topics required'
|
57
|
-
_assert options[:callback], 'callback required'
|
58
|
-
_assert_valid_timeout options[:timeout] if options[:timeout]
|
59
|
-
_assert_valid_max_events options[:max] if options[:max]
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
_conn.subscribe(options)
|
64
|
-
end
|
74
|
+
def unsubscribe(*topics)
|
75
|
+
_assert_valid_topics!(topics)
|
65
76
|
|
66
|
-
|
67
|
-
|
77
|
+
topics.each do |t|
|
78
|
+
response = _conn.delete("/subscriber/topics/#{t}")
|
68
79
|
|
69
|
-
|
70
|
-
|
80
|
+
unless response.success?
|
81
|
+
raise 'unsubscribe rejected'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def unsubscribe_all
|
87
|
+
response = _conn.delete('/subscriber')
|
71
88
|
|
72
89
|
unless response.success?
|
73
|
-
raise 'unsubscribe rejected'
|
90
|
+
raise 'unsubscribe all rejected'
|
74
91
|
end
|
75
92
|
end
|
76
|
-
end
|
77
93
|
|
78
|
-
|
79
|
-
|
94
|
+
def delete_topic(topic)
|
95
|
+
_assert_valid_topic!(topic)
|
96
|
+
|
97
|
+
response = _conn.delete("/topics/#{topic}")
|
80
98
|
|
81
|
-
|
82
|
-
|
99
|
+
unless response.success?
|
100
|
+
raise 'failed to delete topic'
|
101
|
+
end
|
83
102
|
end
|
84
|
-
end
|
85
103
|
|
86
|
-
|
87
|
-
|
104
|
+
def monitor_topics
|
105
|
+
response = _conn.get('/topics') do |r|
|
106
|
+
r.headers['Content-Type'] = 'application/json'
|
107
|
+
end
|
88
108
|
|
89
|
-
|
109
|
+
unless response.success?
|
110
|
+
raise 'failed to connect to /topics'
|
111
|
+
end
|
90
112
|
|
91
|
-
|
92
|
-
|
113
|
+
Oj.load(response.body).map do |raw_topic|
|
114
|
+
Topic.new raw_topic
|
115
|
+
end
|
93
116
|
end
|
94
|
-
end
|
95
117
|
|
96
|
-
|
97
|
-
|
98
|
-
|
118
|
+
private
|
119
|
+
|
120
|
+
def _conn
|
121
|
+
Connection
|
99
122
|
end
|
100
123
|
|
101
|
-
|
102
|
-
|
124
|
+
def _synchronous_backend
|
125
|
+
Routemaster::Client::Backends::Synchronous
|
103
126
|
end
|
104
127
|
|
105
|
-
|
106
|
-
|
128
|
+
def _assert_valid_url!(url)
|
129
|
+
assert_valid_url_throwing_error!(url, InvalidArgumentError)
|
107
130
|
end
|
108
|
-
end
|
109
131
|
|
110
|
-
|
132
|
+
def _assert_valid_max_events!(max)
|
133
|
+
unless (1..10_000).include?(max)
|
134
|
+
raise InvalidArgumentError, "max events '#{max}' is invalid, must be between 1 and 10,000"
|
135
|
+
end
|
136
|
+
end
|
111
137
|
|
112
|
-
|
113
|
-
|
114
|
-
|
138
|
+
def _assert_valid_topics!(topics)
|
139
|
+
unless topics.kind_of? Enumerable
|
140
|
+
raise InvalidArgumentError, "topics must be a list"
|
141
|
+
end
|
115
142
|
|
116
|
-
|
117
|
-
|
118
|
-
|
143
|
+
unless topics.length > 0
|
144
|
+
raise InvalidArgumentError "topics must contain at least one element"
|
145
|
+
end
|
119
146
|
|
120
|
-
|
121
|
-
|
122
|
-
_assert (uri.scheme == 'https'), 'HTTPS required'
|
123
|
-
return url
|
124
|
-
end
|
147
|
+
topics.each { |t| _assert_valid_topic!(t) }
|
148
|
+
end
|
125
149
|
|
126
|
-
|
127
|
-
|
128
|
-
|
150
|
+
def _assert_valid_topic!(topic)
|
151
|
+
unless topic =~ /^[a-z_]{1,64}$/
|
152
|
+
raise InvalidArgumentError, 'bad topic name: must only include letters and underscores'
|
153
|
+
end
|
154
|
+
end
|
129
155
|
|
130
|
-
|
131
|
-
|
132
|
-
|
156
|
+
def _assert_valid_timestamp!(timestamp)
|
157
|
+
unless timestamp.kind_of? Integer
|
158
|
+
raise InvalidArgumentError, "timestamp '#{timestamp}' is invalid, must be an integer"
|
159
|
+
end
|
160
|
+
end
|
133
161
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
162
|
+
def _assert_valid_timeout!(timeout)
|
163
|
+
unless (0..3_600_000).include? timeout
|
164
|
+
raise InvalidArgumentError, "timeout '#{timeout}' is invalid, must be an integer between 0 and 3,600,000"
|
165
|
+
end
|
166
|
+
end
|
139
167
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
_backend.send_event(event, topic, callback, timestamp)
|
145
|
-
end
|
168
|
+
def _send_event(event, topic, callback, timestamp = nil, async: false)
|
169
|
+
_assert_valid_url!(callback)
|
170
|
+
_assert_valid_topic!(topic)
|
171
|
+
_assert_valid_timestamp!(timestamp) if timestamp
|
146
172
|
|
147
|
-
|
148
|
-
|
149
|
-
|
173
|
+
backend = async ? async_backend : _synchronous_backend
|
174
|
+
backend.send_event(event, topic, callback, timestamp)
|
175
|
+
end
|
150
176
|
|
151
|
-
|
152
|
-
|
153
|
-
|
177
|
+
def _check_pulse!
|
178
|
+
_conn.get('/pulse').tap do |response|
|
179
|
+
raise 'cannot connect to bus' unless response.success?
|
180
|
+
end
|
181
|
+
end
|
154
182
|
|
155
|
-
|
156
|
-
@_worker ||= @_backend_type.configure(@_options)
|
183
|
+
private :async_backend, :lazy
|
157
184
|
end
|
158
185
|
end
|
159
186
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Routemaster
|
4
|
+
module Client
|
5
|
+
module AssertionHelpers
|
6
|
+
def assert_valid_url_throwing_error!(url, error_class)
|
7
|
+
begin
|
8
|
+
uri = URI.parse(url)
|
9
|
+
unless uri.is_a? URI::HTTPS
|
10
|
+
raise error_class, "url '#{url}' is invalid, must be an https url"
|
11
|
+
end
|
12
|
+
rescue URI::InvalidURIError
|
13
|
+
raise error_class, "url '#{url}' is invalid, must be an https url"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|