Implement basic message broker
This commit is contained in:
parent
14557d2a34
commit
b9ea7cca30
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
require_relative "rex/version"
|
require_relative "rex/version"
|
||||||
require_relative "rex/book"
|
require_relative "rex/book"
|
||||||
|
require_relative "rex/server"
|
||||||
|
|
||||||
module Rex
|
module Rex
|
||||||
class Error < StandardError; end
|
class Error < StandardError; end
|
||||||
|
8
lib/rex/server.rb
Normal file
8
lib/rex/server.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative "server/message_broker"
|
||||||
|
module Rex
|
||||||
|
module Server
|
||||||
|
VERSION = "0.1.0"
|
||||||
|
end
|
||||||
|
end
|
72
lib/rex/server/message_broker.rb
Normal file
72
lib/rex/server/message_broker.rb
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
module Rex
|
||||||
|
module Server
|
||||||
|
# Responsible for brokering outgoing messages to the correct clients
|
||||||
|
class MessageBroker
|
||||||
|
def initialize(message_serializer)
|
||||||
|
@connections = {}
|
||||||
|
@connection_message_counter = {}
|
||||||
|
@connection_id = 0
|
||||||
|
@user_connection_associations = {}
|
||||||
|
@connection_user_associations = {}
|
||||||
|
@message_serializer = message_serializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def register(connection)
|
||||||
|
@connection_id += 1
|
||||||
|
|
||||||
|
@connections[@connection_id] = connection
|
||||||
|
@connection_message_counter[@connection_id] = 0
|
||||||
|
|
||||||
|
@connection_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_to_all(message)
|
||||||
|
@connections.each do |connection_id, _|
|
||||||
|
send_to_connection(connection_id, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_to_user(user_id, message)
|
||||||
|
connection_ids = @user_connection_associations[user_id]
|
||||||
|
return unless connection_ids
|
||||||
|
|
||||||
|
connection_ids.each do |connection_id|
|
||||||
|
send_to_connection(connection_id, message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_to_connection(connection_id, message)
|
||||||
|
connection = @connections[connection_id]
|
||||||
|
return unless connection
|
||||||
|
|
||||||
|
@connection_message_counter[connection_id] += 1
|
||||||
|
connection.send(serialize(message))
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_id_for_connection(connection_id)
|
||||||
|
@connection_user_associations[connection_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
def associate_connection_with_user(connection_id, user_id)
|
||||||
|
@user_connection_associations[user_id] ||= []
|
||||||
|
@user_connection_associations[user_id].push(connection_id)
|
||||||
|
@connection_user_associations[connection_id] = user_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def unregister(connection_id)
|
||||||
|
@connections.delete(connection_id)
|
||||||
|
@connection_message_counter.delete(connection_id)
|
||||||
|
@user_connection_associations[
|
||||||
|
@connection_user_associations[connection_id]
|
||||||
|
].delete(connection_id)
|
||||||
|
@connection_user_associations.delete(connection_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def serialize(message)
|
||||||
|
@message_serializer.serialize(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
104
spec/server/message_broker_spec.rb
Normal file
104
spec/server/message_broker_spec.rb
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
# require "eventmachine"
|
||||||
|
RSpec.describe Rex::Server::MessageBroker do
|
||||||
|
subject(:instance) { described_class.new(NoOpSerializer.new) }
|
||||||
|
|
||||||
|
let(:connection_a) { MockConnection.new }
|
||||||
|
let(:connection_b) { MockConnection.new }
|
||||||
|
|
||||||
|
let!(:connection_id_a) { instance.register(connection_a) }
|
||||||
|
let!(:connection_id_b) { instance.register(connection_b) }
|
||||||
|
|
||||||
|
describe "#send_to_all" do
|
||||||
|
subject { instance.send_to_all("Hello, World") }
|
||||||
|
|
||||||
|
it "sends the message to all connections" do
|
||||||
|
expect { subject }.to(
|
||||||
|
change { connection_a.messages }.from([]).to(["Hello, World"])
|
||||||
|
.and(
|
||||||
|
change { connection_b.messages }.from([]).to(["Hello, World"])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#send_to_user" do
|
||||||
|
before do
|
||||||
|
instance.associate_connection_with_user(connection_id_a, "user_id")
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { instance.send_to_user("user_id", "Hello, World") }
|
||||||
|
|
||||||
|
it "sends the message to the connection associated with the user" do
|
||||||
|
expect { subject }.to(
|
||||||
|
change { connection_a.messages }.to(["Hello, World"])
|
||||||
|
.and(
|
||||||
|
not_change { connection_b.messages }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when user id is unknown" do
|
||||||
|
subject { instance.send_to_user("unknown_user_id", "Hello, World") }
|
||||||
|
it "sends no messages" do
|
||||||
|
expect { subject }.to(
|
||||||
|
not_change { connection_a.messages }
|
||||||
|
.and(
|
||||||
|
not_change { connection_b.messages }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#send_to_connection" do
|
||||||
|
subject { instance.send_to_connection(connection_id_a, "Hello, World") }
|
||||||
|
|
||||||
|
it "sends the message to the connection" do
|
||||||
|
expect { subject }.to(
|
||||||
|
change { connection_a.messages }.to(["Hello, World"])
|
||||||
|
.and(
|
||||||
|
not_change { connection_b.messages }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the connection id is unknown" do
|
||||||
|
subject { instance.send_to_connection(-99, "Hello, World") }
|
||||||
|
|
||||||
|
it "sends no messages" do
|
||||||
|
expect { subject }.to(
|
||||||
|
not_change { connection_a.messages }
|
||||||
|
.and(
|
||||||
|
not_change { connection_b.messages }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#user_id_for_connection" do
|
||||||
|
subject { instance.user_id_for_connection(connection_id_a) }
|
||||||
|
context "when connection has user associated with it" do
|
||||||
|
before do
|
||||||
|
instance.associate_connection_with_user(connection_id_a, "user_id")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns the user id" do
|
||||||
|
expect(subject).to eq("user_id")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when connection has no user associated with it" do
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when connection was unregistered after being associated" do
|
||||||
|
before do
|
||||||
|
instance.associate_connection_with_user(connection_id_a, "user_id")
|
||||||
|
instance.unregister(connection_id_a)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_nil }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
spec/support/mock_connection.rb
Normal file
17
spec/support/mock_connection.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class MockConnection
|
||||||
|
def initialize
|
||||||
|
@inbox = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def send(message)
|
||||||
|
@inbox.push(message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def messages
|
||||||
|
@inbox
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_inbox!
|
||||||
|
@inbox = []
|
||||||
|
end
|
||||||
|
end
|
1
spec/support/negated_matchers.rb
Normal file
1
spec/support/negated_matchers.rb
Normal file
@ -0,0 +1 @@
|
|||||||
|
RSpec::Matchers.define_negated_matcher :not_change, :change
|
5
spec/support/no_op_serializer.rb
Normal file
5
spec/support/no_op_serializer.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class NoOpSerializer
|
||||||
|
def serialize(object)
|
||||||
|
object
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user