routemaster-client 2.1.0 → 3.0.0
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/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
|