message_bus-http_client 1.0.0.pre1
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
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: da6aba476a01a5481d92971aab35612b332e3cb2187d5f5b633b7466202f219b
|
4
|
+
data.tar.gz: 5e8fd7f77341e8e156697a193816dd39fee11cd81e650a65c7bff071a911fb88
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e988e4d40e3cb571c7b3aa7c803465317f913d25bf8b06c30c76b6befde831a7bd06e9b4e8141d19f61a4a154a538b475a82ce3e550df90cca6464a5b3a69cc2
|
7
|
+
data.tar.gz: ba35c7f2e3faf4df33d190d59182bd9c673ba50304b9a8d49a5bb8085c40bb023dc94888da5466f90b2363ee4a6335ff889521d425ff3c904dcbc764bb442a05
|
@@ -0,0 +1,294 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'uri'
|
5
|
+
require 'message_bus/http_client/configuration'
|
6
|
+
require 'message_bus/http_client/channel'
|
7
|
+
|
8
|
+
module MessageBus
|
9
|
+
# MessageBus client that enables subscription via long polling with support
|
10
|
+
# for chunked encoding. Falls back to normal polling if long polling is not
|
11
|
+
# available.
|
12
|
+
#
|
13
|
+
# @!attribute [r] channels
|
14
|
+
# @return [Hash] a map of the channels that the client is subscribed to
|
15
|
+
# @!attribute [r] stats
|
16
|
+
# @return [Stats] a Struct containing the statistics of failed and successful
|
17
|
+
# polling requests
|
18
|
+
class HTTPClient
|
19
|
+
include Configuration
|
20
|
+
|
21
|
+
class InvalidChannel < StandardError; end
|
22
|
+
|
23
|
+
attr_reader :channels, :stats
|
24
|
+
|
25
|
+
CHUNK_SEPARATOR = "\r\n|\r\n".freeze
|
26
|
+
private_constant :CHUNK_SEPARATOR
|
27
|
+
STATUS_CHANNEL = "/__status".freeze
|
28
|
+
private_constant :STATUS_CHANNEL
|
29
|
+
|
30
|
+
STOPPED = 0
|
31
|
+
STARTED = 1
|
32
|
+
|
33
|
+
Stats = Struct.new(:failed, :success)
|
34
|
+
private_constant :Stats
|
35
|
+
|
36
|
+
# @param base_url [String] Base URL of the message_bus server to connect to
|
37
|
+
#
|
38
|
+
# @return [Object] Instance of MessageBus::HTTPClient
|
39
|
+
def initialize(base_url, *args)
|
40
|
+
super
|
41
|
+
@uri = URI(base_url)
|
42
|
+
@client_id = SecureRandom.hex
|
43
|
+
@channels = {}
|
44
|
+
@status = STOPPED
|
45
|
+
@mutex = Mutex.new
|
46
|
+
@stats = Stats.new(0, 0)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Starts a background thread that polls the message bus endpoint
|
50
|
+
# for the given base_url.
|
51
|
+
#
|
52
|
+
# Intervals for long polling can be configured via min_poll_interval and
|
53
|
+
# max_poll_interval.
|
54
|
+
#
|
55
|
+
# Intervals for polling can be configured via background_callback_interval.
|
56
|
+
#
|
57
|
+
# @return [Object] Instance of MessageBus::HTTPClient
|
58
|
+
def start
|
59
|
+
@mutex.synchronize do
|
60
|
+
return if started?
|
61
|
+
|
62
|
+
@status = STARTED
|
63
|
+
|
64
|
+
thread = Thread.new do
|
65
|
+
begin
|
66
|
+
while started?
|
67
|
+
poll unless @channels.empty?
|
68
|
+
@stats.success += 1
|
69
|
+
sleep interval
|
70
|
+
end
|
71
|
+
rescue StandardError => e
|
72
|
+
@stats.failed += 1
|
73
|
+
warn("#{e.class} #{e.message}: #{e.backtrace.join("\n")}")
|
74
|
+
retry
|
75
|
+
ensure
|
76
|
+
stop
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
thread.abort_on_exception = true
|
81
|
+
end
|
82
|
+
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
# Stops the client from polling the message bus endpoint.
|
87
|
+
#
|
88
|
+
# @return [Integer] the current status of the client
|
89
|
+
def stop
|
90
|
+
@status = STOPPED
|
91
|
+
end
|
92
|
+
|
93
|
+
# Subscribes to a channel which executes the given callback when a message
|
94
|
+
# is published to the channel
|
95
|
+
#
|
96
|
+
# @example Subscribing to a channel for message
|
97
|
+
# client = MessageBus::HTTPClient.new('http://some.test.com')
|
98
|
+
#
|
99
|
+
# client.subscribe("/test") do |payload, _message_id, _global_id|
|
100
|
+
# puts payload
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# A last_message_id may be provided.
|
104
|
+
# * -1 will subscribe to all new messages
|
105
|
+
# * -2 will recieve last message + all new messages
|
106
|
+
# * -3 will recieve last 2 message + all new messages
|
107
|
+
#
|
108
|
+
# @example Subscribing to a channel with `last_message_id`
|
109
|
+
# client.subscribe("/test", last_message_id: -2) do |payload|
|
110
|
+
# puts payload
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# @param channel [String] channel to listen for messages on
|
114
|
+
# @param last_message_id [Integer] last message id to start polling on.
|
115
|
+
#
|
116
|
+
# @yield [data, message_id, global_id]
|
117
|
+
# callback to be executed whenever a message is received
|
118
|
+
#
|
119
|
+
# @yieldparam data [Hash] data payload of the message received on the channel
|
120
|
+
# @yieldparam message_id [Integer] id of the message in the channel
|
121
|
+
# @yieldparam global_id [Integer] id of the message in the global backlog
|
122
|
+
# @yieldreturn [void]
|
123
|
+
#
|
124
|
+
# @return [Integer] the current status of the client
|
125
|
+
def subscribe(channel, last_message_id: nil, &callback)
|
126
|
+
raise InvalidChannel unless channel.to_s.start_with?("/")
|
127
|
+
|
128
|
+
last_message_id = -1 if last_message_id && !last_message_id.is_a?(Integer)
|
129
|
+
|
130
|
+
@channels[channel] ||= Channel.new
|
131
|
+
channel = @channels[channel]
|
132
|
+
channel.last_message_id = last_message_id if last_message_id
|
133
|
+
channel.callbacks.push(callback)
|
134
|
+
start if stopped?
|
135
|
+
end
|
136
|
+
|
137
|
+
# unsubscribes from a channel
|
138
|
+
#
|
139
|
+
# @example Unsubscribing from a channel
|
140
|
+
# client = MessageBus::HTTPClient.new('http://some.test.com')
|
141
|
+
# callback = -> { |payload| puts payload }
|
142
|
+
# client.subscribe("/test", &callback)
|
143
|
+
# client.unsubscribe("/test")
|
144
|
+
#
|
145
|
+
# If a callback is given, only the specific callback will be unsubscribed.
|
146
|
+
#
|
147
|
+
# @example Unsubscribing a callback from a channel
|
148
|
+
# client.unsubscribe("/test", &callback)
|
149
|
+
#
|
150
|
+
# When the client does not have any channels left, it will stop polling and
|
151
|
+
# waits until a new subscription is started.
|
152
|
+
#
|
153
|
+
# @param channel [String] channel to unsubscribe
|
154
|
+
# @yield [data, global_id, message_id] specific callback to unsubscribe
|
155
|
+
#
|
156
|
+
# @return [Integer] the current status of the client
|
157
|
+
def unsubscribe(channel, &callback)
|
158
|
+
if callback
|
159
|
+
@channels[channel].callbacks.delete(callback)
|
160
|
+
remove_channel(channel) if @channels[channel].callbacks.empty?
|
161
|
+
else
|
162
|
+
remove_channel(channel)
|
163
|
+
end
|
164
|
+
|
165
|
+
stop if @channels.empty?
|
166
|
+
@status
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def stopped?
|
172
|
+
@status == STOPPED
|
173
|
+
end
|
174
|
+
|
175
|
+
def started?
|
176
|
+
@status == STARTED
|
177
|
+
end
|
178
|
+
|
179
|
+
def remove_channel(channel)
|
180
|
+
@channels.delete(channel)
|
181
|
+
end
|
182
|
+
|
183
|
+
def interval
|
184
|
+
if @enable_long_polling
|
185
|
+
if (failed_count = @stats.failed) > 2
|
186
|
+
(@min_poll_interval * failed_count).clamp(
|
187
|
+
@min_poll_interval, @max_poll_interval
|
188
|
+
)
|
189
|
+
else
|
190
|
+
@min_poll_interval
|
191
|
+
end
|
192
|
+
else
|
193
|
+
@background_callback_interval
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def poll
|
198
|
+
http = Net::HTTP.new(@uri.host, @uri.port)
|
199
|
+
http.use_ssl = true if @uri.scheme == 'https'
|
200
|
+
request = Net::HTTP::Post.new(request_path, headers)
|
201
|
+
request.body = poll_payload
|
202
|
+
|
203
|
+
if @enable_long_polling
|
204
|
+
buffer = ''
|
205
|
+
|
206
|
+
http.request(request) do |response|
|
207
|
+
response.read_body do |chunk|
|
208
|
+
unless chunk.empty?
|
209
|
+
buffer << chunk
|
210
|
+
process_buffer(buffer)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
else
|
215
|
+
response = http.request(request)
|
216
|
+
notify_channels(JSON.parse(response.body))
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def is_chunked?
|
221
|
+
!headers["Dont-Chunk"]
|
222
|
+
end
|
223
|
+
|
224
|
+
def process_buffer(buffer)
|
225
|
+
index = buffer.index(CHUNK_SEPARATOR)
|
226
|
+
|
227
|
+
if is_chunked?
|
228
|
+
return unless index
|
229
|
+
|
230
|
+
messages = buffer[0..(index - 1)]
|
231
|
+
buffer.slice!("#{messages}#{CHUNK_SEPARATOR}")
|
232
|
+
else
|
233
|
+
messages = buffer[0..-1]
|
234
|
+
buffer.slice!(messages)
|
235
|
+
end
|
236
|
+
|
237
|
+
notify_channels(JSON.parse(messages))
|
238
|
+
end
|
239
|
+
|
240
|
+
def notify_channels(messages)
|
241
|
+
messages.each do |message|
|
242
|
+
current_channel = message['channel']
|
243
|
+
|
244
|
+
if current_channel == STATUS_CHANNEL
|
245
|
+
message["data"].each do |channel_name, last_message_id|
|
246
|
+
if (channel = @channels[channel_name])
|
247
|
+
channel.last_message_id = last_message_id
|
248
|
+
end
|
249
|
+
end
|
250
|
+
else
|
251
|
+
@channels.each do |channel_name, channel|
|
252
|
+
next unless channel_name == current_channel
|
253
|
+
|
254
|
+
channel.last_message_id = message['message_id']
|
255
|
+
|
256
|
+
channel.callbacks.each do |callback|
|
257
|
+
callback.call(
|
258
|
+
message['data'],
|
259
|
+
channel.last_message_id,
|
260
|
+
message['global_id']
|
261
|
+
)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def poll_payload
|
269
|
+
payload = {}
|
270
|
+
|
271
|
+
@channels.each do |channel_name, channel|
|
272
|
+
payload[channel_name] = channel.last_message_id
|
273
|
+
end
|
274
|
+
|
275
|
+
payload.to_json
|
276
|
+
end
|
277
|
+
|
278
|
+
def request_path
|
279
|
+
"/message-bus/#{@client_id}/poll"
|
280
|
+
end
|
281
|
+
|
282
|
+
def headers
|
283
|
+
headers = {}
|
284
|
+
headers['Content-Type'] = 'application/json'
|
285
|
+
headers['X-Silence-logger'] = 'true'
|
286
|
+
|
287
|
+
if !@enable_long_polling || !@enable_chunked_encoding
|
288
|
+
headers['Dont-Chunk'] = 'true'
|
289
|
+
end
|
290
|
+
|
291
|
+
headers.merge!(@headers)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MessageBus
|
2
|
+
class HTTPClient
|
3
|
+
# @private
|
4
|
+
class Channel
|
5
|
+
attr_accessor :last_message_id, :callbacks
|
6
|
+
|
7
|
+
def initialize(last_message_id: -1, callbacks: [])
|
8
|
+
@last_message_id = last_message_id
|
9
|
+
@callbacks = callbacks
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module MessageBus
|
2
|
+
class HTTPClient
|
3
|
+
module Configuration
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:attr_accessor,
|
6
|
+
:enable_long_polling,
|
7
|
+
:status,
|
8
|
+
:enable_chunked_encoding,
|
9
|
+
:min_poll_interval,
|
10
|
+
:max_poll_interval,
|
11
|
+
:background_callback_interval
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param enable_long_polling [Boolean] Enable long polling
|
16
|
+
# @param enable_chunked_encoding [Boolean] Enable chunk encoding
|
17
|
+
# @param min_poll_interval [Float, Integer] Min poll interval when long polling
|
18
|
+
# @param max_poll_interval [Float, Integer] Max poll interval when long polling.
|
19
|
+
# When requests fail, the client will backoff and this is the upper limit.
|
20
|
+
# @param background_callback_interval [Float, Integer] Interval to poll when
|
21
|
+
# when polling.
|
22
|
+
# @param headers [Hash] extra HTTP headers to be set on the polling requests.
|
23
|
+
def initialize(_base_url,
|
24
|
+
enable_long_polling: true,
|
25
|
+
enable_chunked_encoding: true,
|
26
|
+
min_poll_interval: 0.1,
|
27
|
+
max_poll_interval: 180,
|
28
|
+
background_callback_interval: 60,
|
29
|
+
headers: {})
|
30
|
+
|
31
|
+
@enable_long_polling = enable_long_polling
|
32
|
+
@enable_chunked_encoding = enable_chunked_encoding
|
33
|
+
@min_poll_interval = min_poll_interval
|
34
|
+
@max_poll_interval = max_poll_interval
|
35
|
+
@background_callback_interval = background_callback_interval
|
36
|
+
@headers = headers
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'message_bus/http_client'
|
3
|
+
|
4
|
+
describe MessageBus::HTTPClient do
|
5
|
+
let(:base_url) { "http://0.0.0.0:9292" }
|
6
|
+
let(:client) { MessageBus::HTTPClient.new(base_url) }
|
7
|
+
let(:headers) { client.send(:headers) }
|
8
|
+
let(:channel) { "/test" }
|
9
|
+
let(:channel2) { "/test2" }
|
10
|
+
|
11
|
+
def publish_message
|
12
|
+
response = Net::HTTP.get_response(URI("#{base_url}/publish"))
|
13
|
+
assert_equal("200", response.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
@threads = Thread.list
|
18
|
+
end
|
19
|
+
|
20
|
+
after do
|
21
|
+
new_threads = Thread.list - @threads
|
22
|
+
client.stop
|
23
|
+
assert(new_threads.size <= 1)
|
24
|
+
new_threads.each(&:join)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#start and #stop' do
|
28
|
+
it 'should be able to start and stop polling correctly' do
|
29
|
+
threads = Thread.list
|
30
|
+
|
31
|
+
assert_equal(MessageBus::HTTPClient::STOPPED, client.status)
|
32
|
+
|
33
|
+
client.start
|
34
|
+
new_threads = Thread.list - threads
|
35
|
+
|
36
|
+
assert_equal(1, new_threads.size)
|
37
|
+
assert_equal(MessageBus::HTTPClient::STARTED, client.status)
|
38
|
+
|
39
|
+
client.start
|
40
|
+
|
41
|
+
assert_equal(new_threads, Thread.list - threads)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#subscribe' do
|
46
|
+
it 'should be able to subscribe to channels for messages' do
|
47
|
+
called = 0
|
48
|
+
called2 = 0
|
49
|
+
|
50
|
+
client.subscribe(channel, last_message_id: -1) do |data|
|
51
|
+
called += 1
|
52
|
+
assert_equal("world", data["hello"])
|
53
|
+
end
|
54
|
+
|
55
|
+
client.subscribe(channel2) do |data|
|
56
|
+
called2 += 1
|
57
|
+
assert_equal("world", data["hello"])
|
58
|
+
end
|
59
|
+
|
60
|
+
while called < 2 && called2 < 2
|
61
|
+
publish_message
|
62
|
+
sleep 0.05
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'supports including extra headers' do
|
67
|
+
let(:client) do
|
68
|
+
MessageBus::HTTPClient.new(base_url, headers: {
|
69
|
+
'Dont-Chunk' => "true"
|
70
|
+
})
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should include the header in the request' do
|
74
|
+
called = 0
|
75
|
+
|
76
|
+
client.subscribe(channel) do |data|
|
77
|
+
called += 1
|
78
|
+
assert_equal("world", data["hello"])
|
79
|
+
end
|
80
|
+
|
81
|
+
while called < 2
|
82
|
+
publish_message
|
83
|
+
sleep 0.05
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'when chunked encoding is disabled' do
|
89
|
+
let(:client) do
|
90
|
+
MessageBus::HTTPClient.new(base_url, enable_chunked_encoding: false)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should still be able to subscribe to channels for messages' do
|
94
|
+
called = 0
|
95
|
+
|
96
|
+
client.subscribe(channel) do |data|
|
97
|
+
called += 1
|
98
|
+
assert_equal("world", data["hello"])
|
99
|
+
end
|
100
|
+
|
101
|
+
while called < 2
|
102
|
+
publish_message
|
103
|
+
sleep 0.05
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'when enable_long_polling is disabled' do
|
109
|
+
let(:client) do
|
110
|
+
MessageBus::HTTPClient.new(base_url,
|
111
|
+
enable_long_polling: false,
|
112
|
+
background_callback_interval: 0.01)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should still be able to subscribe to channels for messages' do
|
116
|
+
called = 0
|
117
|
+
|
118
|
+
client.subscribe(channel) do |data|
|
119
|
+
called += 1
|
120
|
+
assert_equal("world", data["hello"])
|
121
|
+
end
|
122
|
+
|
123
|
+
while called < 2
|
124
|
+
publish_message
|
125
|
+
sleep 0.05
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe 'when channel name is invalid' do
|
131
|
+
it 'should raise the right error' do
|
132
|
+
["test", 1, :test].each do |invalid_channel|
|
133
|
+
assert_raises MessageBus::HTTPClient::InvalidChannel do
|
134
|
+
client.subscribe(invalid_channel)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe 'with last_message_id' do
|
141
|
+
describe 'when invalid' do
|
142
|
+
it 'should subscribe from the latest message' do
|
143
|
+
client.subscribe(channel, last_message_id: 'haha')
|
144
|
+
assert_equal(-1, client.channels[channel].last_message_id)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'when valid' do
|
149
|
+
it 'should subscribe from the right message' do
|
150
|
+
client.subscribe(channel, last_message_id: -2)
|
151
|
+
assert_equal(-2, client.channels[channel].last_message_id)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '#unsubscribe' do
|
158
|
+
it 'should be able to unsubscribe a channel' do
|
159
|
+
client.subscribe(channel) { raise "Not called" }
|
160
|
+
assert(client.channels[channel])
|
161
|
+
|
162
|
+
client.unsubscribe(channel)
|
163
|
+
assert_nil(client.channels[channel])
|
164
|
+
end
|
165
|
+
|
166
|
+
describe 'with callback' do
|
167
|
+
it 'should be able to unsubscribe a callback for a particular channel' do
|
168
|
+
callback = -> { raise "Not called" }
|
169
|
+
callback2 = -> { raise "Not called2" }
|
170
|
+
|
171
|
+
client.subscribe(channel, &callback)
|
172
|
+
client.subscribe(channel, &callback2)
|
173
|
+
assert_equal([callback, callback2], client.channels[channel].callbacks)
|
174
|
+
|
175
|
+
client.unsubscribe(channel, &callback)
|
176
|
+
assert_equal([callback2], client.channels[channel].callbacks)
|
177
|
+
|
178
|
+
client.unsubscribe(channel, &callback2)
|
179
|
+
assert_nil(client.channels[channel])
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: message_bus-http_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tan Guo Xiang
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: puma
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3'
|
27
|
+
description: A Ruby HTTP client for message bus
|
28
|
+
email:
|
29
|
+
- tgx+github@discourse.org
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/message_bus/http_client.rb
|
35
|
+
- lib/message_bus/http_client/channel.rb
|
36
|
+
- lib/message_bus/http_client/configuration.rb
|
37
|
+
- lib/message_bus/http_client/version.rb
|
38
|
+
- spec/integration/http_client_spec.rb
|
39
|
+
homepage: https://github.com/SamSaffron/message_bus
|
40
|
+
licenses:
|
41
|
+
- MIT
|
42
|
+
metadata: {}
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
require_paths:
|
46
|
+
- lib
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '2.4'
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.3.1
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 2.7.6
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: ''
|
63
|
+
test_files:
|
64
|
+
- spec/integration/http_client_spec.rb
|