mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
571 lines
21 KiB
C++
571 lines
21 KiB
C++
// See the file "COPYING" in the main distribution directory for copyright.
|
|
|
|
// Implement Broker's WebSocket client handling in Zeek.
|
|
|
|
#include "zeek/cluster/websocket/WebSocket.h"
|
|
|
|
#include <memory>
|
|
#include <string_view>
|
|
#include <variant>
|
|
|
|
#include "zeek/Reporter.h"
|
|
#include "zeek/cluster/Backend.h"
|
|
#include "zeek/cluster/BifSupport.h"
|
|
#include "zeek/cluster/Manager.h"
|
|
#include "zeek/cluster/OnLoop.h"
|
|
#include "zeek/cluster/Serializer.h"
|
|
#include "zeek/cluster/serializer/broker/Serializer.h"
|
|
#include "zeek/cluster/websocket/Plugin.h"
|
|
#include "zeek/cluster/websocket/events.bif.h"
|
|
#include "zeek/net_util.h"
|
|
#include "zeek/threading/MsgThread.h"
|
|
|
|
#include "broker/data_envelope.hh"
|
|
#include "broker/error.hh"
|
|
#include "broker/format/json.hh"
|
|
#include "broker/zeek.hh"
|
|
#include "rapidjson/document.h"
|
|
#include "rapidjson/rapidjson.h"
|
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
|
#define WS_DEBUG(...) PLUGIN_DBG_LOG(zeek::plugin::Cluster_WebSocket::plugin, __VA_ARGS__)
|
|
|
|
namespace zeek {
|
|
const char* zeek_version();
|
|
}
|
|
|
|
using namespace zeek::cluster::websocket::detail;
|
|
|
|
namespace {
|
|
|
|
class WebSocketEventHandlingStrategy : public zeek::cluster::detail::EventHandlingStrategy {
|
|
public:
|
|
WebSocketEventHandlingStrategy(std::shared_ptr<WebSocketClient> ws, WebSocketEventDispatcher* dispatcher)
|
|
: wsc(std::move(ws)), dispatcher(dispatcher) {}
|
|
|
|
private:
|
|
/**
|
|
* Any received remote event is encoded into Broker's JSON v1 format and
|
|
* send over to the WebSocket client.
|
|
*
|
|
* We leverage low-level Broker encoding functions here directly. This
|
|
* will need some abstractions if client's can opt to use different encodings
|
|
* of events in the future.
|
|
*/
|
|
bool DoProcessEvent(std::string_view topic, zeek::cluster::detail::Event e) override {
|
|
// If the client has left, no point in sending it any pending event.
|
|
if ( wsc->IsTerminated() )
|
|
return true;
|
|
|
|
|
|
// Any events received from the backend before an Ack was sent
|
|
// are discarded.
|
|
if ( ! wsc->IsAcked() )
|
|
return true;
|
|
|
|
// XXX The serialization is somewhat slow, it would be good to offload
|
|
// it to a thread, or try to go from Val's directly to JSON and see
|
|
// if that's faster.
|
|
auto ev = zeek::cluster::detail::to_broker_event(e);
|
|
if ( ! ev ) {
|
|
fprintf(stderr, "[ERROR] Unable to go from detail::Event to broker::event\n");
|
|
return false;
|
|
}
|
|
|
|
buffer.clear();
|
|
auto envelope = broker::data_envelope::make(topic, ev->as_data());
|
|
broker::format::json::v1::encode(envelope, std::back_inserter(buffer));
|
|
|
|
dispatcher->QueueReply(WebSocketSendReply{wsc, buffer});
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Events from backends aren't enqueued into the event loop when
|
|
* running for WebSocket clients.
|
|
*/
|
|
void DoProcessLocalEvent(zeek::EventHandlerPtr h, zeek::Args args) override {}
|
|
|
|
/**
|
|
* Send errors directly to the client.
|
|
*/
|
|
void DoProcessError(std::string_view tag, std::string_view message) override {
|
|
// Just send out the error.
|
|
wsc->SendError(tag, message);
|
|
}
|
|
|
|
std::string buffer;
|
|
std::shared_ptr<WebSocketClient> wsc;
|
|
WebSocketEventDispatcher* dispatcher;
|
|
};
|
|
|
|
class ReplyInputMessage : public zeek::threading::BasicInputMessage {
|
|
public:
|
|
ReplyInputMessage(WebSocketReply work) : zeek::threading::BasicInputMessage("ReplyInput"), work(std::move(work)) {};
|
|
bool Process() override {
|
|
return std::visit([this](auto& item) -> bool { return Process(item); }, work);
|
|
};
|
|
|
|
private:
|
|
bool Process(const WebSocketSendReply& sr) {
|
|
const auto& wsc = sr.wsc;
|
|
if ( wsc->IsTerminated() )
|
|
return true;
|
|
|
|
auto send_info = wsc->SendText(sr.msg);
|
|
if ( ! send_info.success && ! wsc->IsTerminated() )
|
|
fprintf(stderr, "[ERROR] Failed to send reply to WebSocket client %s (%s:%d)\n", wsc->getId().c_str(),
|
|
wsc->getRemoteIp().c_str(), wsc->getRemotePort());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Process(const WebSocketCloseReply& cr) {
|
|
const auto& wsc = cr.wsc;
|
|
if ( ! wsc->IsTerminated() )
|
|
wsc->Close(cr.code, cr.reason);
|
|
|
|
return true;
|
|
}
|
|
|
|
WebSocketReply work;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
// Inspired by broker/internal/json_client.cc
|
|
WebSocketClient::SendInfo WebSocketClient::SendError(std::string_view tag, std::string_view message) {
|
|
std::string buf;
|
|
buf.reserve(tag.size() + message.size() + 32);
|
|
auto out = std::back_inserter(buf);
|
|
*out++ = '{';
|
|
broker::format::json::v1::append_field("type", "error", out);
|
|
*out++ = ',';
|
|
broker::format::json::v1::append_field("code", tag, out);
|
|
*out++ = ',';
|
|
broker::format::json::v1::append_field("message", message, out);
|
|
*out++ = '}';
|
|
return SendText(buf);
|
|
}
|
|
|
|
// Inspired by broker/internal/json_client.cc
|
|
WebSocketClient::SendInfo WebSocketClient::SendAck(std::string_view endpoint, std::string_view version) {
|
|
std::string buf;
|
|
buf.reserve(endpoint.size() + version.size() + 32);
|
|
auto out = std::back_inserter(buf);
|
|
*out++ = '{';
|
|
broker::format::json::v1::append_field("type", "ack", out);
|
|
*out++ = ',';
|
|
broker::format::json::v1::append_field("endpoint", endpoint, out);
|
|
*out++ = ',';
|
|
broker::format::json::v1::append_field("version", version, out);
|
|
*out++ = '}';
|
|
auto r = SendText(buf);
|
|
acked = true;
|
|
return r;
|
|
}
|
|
|
|
void WebSocketClient::SetSubscriptions(const std::vector<std::string>& topic_prefixes) {
|
|
for ( const auto& topic_prefix : topic_prefixes )
|
|
subscriptions_state[topic_prefix] = false;
|
|
}
|
|
|
|
void WebSocketClient::SetSubscriptionActive(const std::string& topic_prefix) {
|
|
if ( subscriptions_state.count(topic_prefix) == 0 ) {
|
|
zeek::reporter->InternalWarning("Unknown topic_prefix for WebSocket client %s!", topic_prefix.c_str());
|
|
return;
|
|
}
|
|
|
|
subscriptions_state[topic_prefix] = true;
|
|
}
|
|
|
|
bool WebSocketClient::AllSubscriptionsActive() const {
|
|
for ( const auto& [_, status] : subscriptions_state ) {
|
|
if ( ! status )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const std::vector<std::string> WebSocketClient::GetSubscriptions() const {
|
|
std::vector<std::string> subs;
|
|
subs.reserve(subscriptions_state.size());
|
|
|
|
for ( const auto& [topic, _] : subscriptions_state )
|
|
subs.emplace_back(topic);
|
|
|
|
return subs;
|
|
}
|
|
|
|
class zeek::cluster::websocket::detail::ReplyMsgThread : public zeek::threading::MsgThread {
|
|
public:
|
|
ReplyMsgThread() : zeek::threading::MsgThread() { SetName("ws-reply-thread"); }
|
|
|
|
void Run() override {
|
|
zeek::util::detail::set_thread_name("zk/ws-reply-thread");
|
|
MsgThread::Run();
|
|
}
|
|
|
|
bool OnHeartbeat(double network_time, double current_time) override { return true; }
|
|
|
|
bool OnFinish(double network_time) override { return true; }
|
|
};
|
|
|
|
WebSocketEventDispatcher::WebSocketEventDispatcher(std::string ident, size_t queue_size) {
|
|
onloop =
|
|
new zeek::detail::OnLoopProcess<WebSocketEventDispatcher, WebSocketEvent>(this,
|
|
"WebSocketEventDispatcher:" + ident,
|
|
queue_size);
|
|
// Register the onloop instance the IO loop. Lifetime will be managed by the loop.
|
|
onloop->Register(false);
|
|
|
|
reply_msg_thread = new ReplyMsgThread();
|
|
reply_msg_thread->Start();
|
|
}
|
|
|
|
WebSocketEventDispatcher::~WebSocketEventDispatcher() {
|
|
// Freed by threading manager.
|
|
reply_msg_thread = nullptr;
|
|
}
|
|
|
|
void WebSocketEventDispatcher::Terminate() {
|
|
WS_DEBUG("Terminating WebSocketEventDispatcher");
|
|
|
|
for ( auto& [_, client] : clients ) {
|
|
const auto& wsc = client.wsc;
|
|
const auto& backend = client.backend;
|
|
WS_DEBUG("Sending close to WebSocket client %s (%s:%d)", wsc->getId().c_str(), wsc->getRemoteIp().c_str(),
|
|
wsc->getRemotePort());
|
|
|
|
QueueReply(WebSocketCloseReply{wsc, 1001, "Terminating"});
|
|
|
|
if ( backend )
|
|
backend->Terminate();
|
|
}
|
|
|
|
clients.clear();
|
|
|
|
onloop->Close();
|
|
|
|
// Wait for the reply_msg_thread to process any outstanding
|
|
// WebSocketReply messages before returning.
|
|
reply_msg_thread->SignalStop();
|
|
reply_msg_thread->WaitForStop();
|
|
}
|
|
|
|
void WebSocketEventDispatcher::QueueForProcessing(WebSocketEvent&& event) {
|
|
// Just delegate to onloop. The work will be done in Process()
|
|
onloop->QueueForProcessing(std::move(event));
|
|
}
|
|
|
|
void WebSocketEventDispatcher::QueueReply(WebSocketReply&& reply) {
|
|
// Delegate to the reply thread.
|
|
reply_msg_thread->SendIn(new ReplyInputMessage(std::move(reply)));
|
|
}
|
|
|
|
// Process a WebSocketEvent message on the Zeek IO loop.
|
|
void WebSocketEventDispatcher::Process(const WebSocketEvent& event) {
|
|
std::visit([this](auto&& arg) { Process(arg); }, event);
|
|
}
|
|
|
|
void WebSocketEventDispatcher::Process(const WebSocketOpen& open) {
|
|
const auto& wsc = open.wsc;
|
|
const auto& id = open.id;
|
|
const auto& it = clients.find(id);
|
|
if ( it != clients.end() ) {
|
|
// This shouldn't happen!
|
|
reporter->Error("Open for existing WebSocket client with id %s!", id.c_str());
|
|
QueueReply(WebSocketCloseReply{wsc, 1001, "Internal error"});
|
|
return;
|
|
}
|
|
|
|
// As of now, terminate clients coming to anything other than /v1/messages/json.
|
|
if ( open.uri != "/v1/messages/json" ) {
|
|
open.wsc->SendError("invalid_uri", "Invalid URI - use /v1/messages/json");
|
|
open.wsc->Close(1008, "Invalid URI - use /v1/messages/json");
|
|
|
|
// Still create an entry as we might see messages and close events coming in.
|
|
clients[id] = WebSocketClientEntry{id, wsc, nullptr};
|
|
return;
|
|
}
|
|
|
|
// Generate an ID for this client.
|
|
auto ws_id = cluster::backend->NodeId() + "-websocket-" + id;
|
|
|
|
// If the globally configured backend is CLUSTER_BACKEND_BROKER, then switch
|
|
// the WebSocket client's backend to CLUSTER_BACKEND_BROKER_WEBSOCKET_SHIM
|
|
// so that pub/sub is using the local broker endpoint via its hub functionality
|
|
// instead of instantiating a new Broker manager.
|
|
static const auto& event_serializer_val = id::find_val<zeek::EnumVal>("Cluster::event_serializer");
|
|
auto event_serializer = cluster::manager->InstantiateEventSerializer(event_serializer_val);
|
|
static const auto& cluster_backend_val = id::find_val<zeek::EnumVal>("Cluster::backend");
|
|
auto effective_backend_val = cluster_backend_val;
|
|
|
|
static const auto& broker_enum_val = zeek::id::find_val<EnumVal>("Cluster::CLUSTER_BACKEND_BROKER");
|
|
static const auto& broker_ws_shim_enum_val =
|
|
zeek::id::find_val<EnumVal>("Cluster::CLUSTER_BACKEND_BROKER_WEBSOCKET_SHIM");
|
|
if ( effective_backend_val == broker_enum_val ) {
|
|
WS_DEBUG("Using broker websocket shim");
|
|
effective_backend_val = broker_ws_shim_enum_val;
|
|
}
|
|
|
|
auto event_handling_strategy = std::make_unique<WebSocketEventHandlingStrategy>(wsc, this);
|
|
auto backend = zeek::cluster::manager->InstantiateBackend(effective_backend_val, std::move(event_serializer),
|
|
nullptr, std::move(event_handling_strategy));
|
|
|
|
if ( ! backend ) {
|
|
reporter->Error("Failed to instantiate backend for client with id %s!", id.c_str());
|
|
QueueReply(WebSocketCloseReply{wsc, 1001, "Internal error"});
|
|
return;
|
|
}
|
|
|
|
WS_DEBUG("New WebSocket client %s (%s:%d) - using id %s backend=%p", id.c_str(), wsc->getRemoteIp().c_str(),
|
|
wsc->getRemotePort(), ws_id.c_str(), backend.get());
|
|
|
|
// XXX: We call InitPostScript to populate member vars required for connectivity.
|
|
backend->InitPostScript();
|
|
backend->Init(std::move(ws_id));
|
|
|
|
clients[id] = WebSocketClientEntry{id, wsc, std::move(backend)};
|
|
}
|
|
|
|
void WebSocketEventDispatcher::Process(const WebSocketClose& close) {
|
|
const auto& id = close.id;
|
|
const auto& it = clients.find(id);
|
|
|
|
if ( it == clients.end() ) {
|
|
reporter->Error("Close from non-existing WebSocket client with id %s!", id.c_str());
|
|
return;
|
|
}
|
|
|
|
auto& wsc = it->second.wsc;
|
|
auto& backend = it->second.backend;
|
|
|
|
WS_DEBUG("Close from client %s (%s:%d) backend=%p", wsc->getId().c_str(), wsc->getRemoteIp().c_str(),
|
|
wsc->getRemotePort(), backend.get());
|
|
|
|
// If the client doesn't have a backend, it wasn't ever properly instantiated.
|
|
if ( backend ) {
|
|
backend->Terminate();
|
|
|
|
// Raise Cluster::websocket_client_lost() after the backend has terminated.
|
|
// In case any messages/events were still pending, Cluster::websocket_client_lost()
|
|
// should be the last event related to this WebSocket client.
|
|
auto rec = zeek::cluster::detail::bif::make_endpoint_info(backend->NodeId(), wsc->getRemoteIp(),
|
|
wsc->getRemotePort(), TRANSPORT_TCP);
|
|
zeek::event_mgr.Enqueue(Cluster::websocket_client_lost, std::move(rec), zeek::val_mgr->Count(close.code),
|
|
zeek::make_intrusive<zeek::StringVal>(close.reason));
|
|
}
|
|
|
|
clients.erase(it);
|
|
}
|
|
|
|
// SubscribeFinished is produced internally.
|
|
void WebSocketEventDispatcher::Process(const WebSocketSubscribeFinished& fin) {
|
|
const auto& it = clients.find(fin.id);
|
|
if ( it == clients.end() ) {
|
|
reporter->Error("Subscribe finished from non-existing WebSocket client with id %s!", fin.id.c_str());
|
|
return;
|
|
}
|
|
|
|
auto& entry = it->second;
|
|
|
|
entry.wsc->SetSubscriptionActive(fin.topic_prefix);
|
|
|
|
if ( ! entry.wsc->AllSubscriptionsActive() ) {
|
|
// More subscriptions to come.
|
|
return;
|
|
}
|
|
|
|
if ( ! entry.ready_to_publish ) {
|
|
// Still waiting for the backend to be ready.
|
|
return;
|
|
}
|
|
|
|
HandleSubscriptionsActive(entry);
|
|
}
|
|
|
|
void WebSocketEventDispatcher::Process(const WebSocketBackendReadyToPublish& ready) {
|
|
const auto& it = clients.find(ready.id);
|
|
if ( it == clients.end() ) {
|
|
reporter->Error("Backend ready from non-existing WebSocket client with id %s!", ready.id.c_str());
|
|
return;
|
|
}
|
|
|
|
auto& entry = it->second;
|
|
|
|
entry.ready_to_publish = true;
|
|
|
|
if ( ! entry.wsc->AllSubscriptionsActive() ) {
|
|
// More subscriptions to come!
|
|
return;
|
|
}
|
|
|
|
HandleSubscriptionsActive(entry);
|
|
}
|
|
|
|
void WebSocketEventDispatcher::HandleSubscriptions(WebSocketClientEntry& entry, std::string_view buf) {
|
|
rapidjson::Document doc;
|
|
doc.Parse(buf.data(), buf.size());
|
|
if ( ! doc.IsArray() ) {
|
|
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "subscriptions not an array");
|
|
return;
|
|
}
|
|
|
|
std::vector<std::string> subscriptions;
|
|
|
|
for ( rapidjson::SizeType i = 0; i < doc.Size(); i++ ) {
|
|
if ( ! doc[i].IsString() ) {
|
|
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed),
|
|
"individual subscription not a string");
|
|
return;
|
|
}
|
|
|
|
subscriptions.emplace_back(doc[i].GetString());
|
|
}
|
|
|
|
entry.wsc->SetSubscriptions(subscriptions);
|
|
|
|
auto cb = [this, id = entry.id, wsc = entry.wsc](const std::string& topic,
|
|
const Backend::SubscriptionCallbackInfo& info) {
|
|
if ( info.status == Backend::CallbackStatus::Error ) {
|
|
zeek::reporter->Error("Subscribe for WebSocket client failed!");
|
|
|
|
// Is this going to work out?
|
|
QueueReply(WebSocketCloseReply{wsc, 1011, "Could not subscribe. Something bad happened!"});
|
|
}
|
|
else {
|
|
Process(WebSocketSubscribeFinished{id, topic});
|
|
}
|
|
};
|
|
|
|
for ( const auto& subscription : subscriptions ) {
|
|
if ( ! entry.backend->Subscribe(subscription, cb) ) {
|
|
zeek::reporter->Error("Subscribe for WebSocket client failed!");
|
|
QueueReply(WebSocketCloseReply{entry.wsc, 1011, "Could not subscribe. Something bad happened!"});
|
|
}
|
|
}
|
|
|
|
// Register a callback to be invoked when the backend is ready for publishing.
|
|
entry.backend->ReadyToPublishCallback([this, id = entry.id](const auto& info) {
|
|
// Ready callbacks are supposed to run on the main thread,
|
|
// so we can just start processing a WebSocketBackendReady.
|
|
Process(WebSocketBackendReadyToPublish{id});
|
|
});
|
|
}
|
|
|
|
void WebSocketEventDispatcher::HandleSubscriptionsActive(const WebSocketClientEntry& entry) {
|
|
auto& wsc = entry.wsc;
|
|
|
|
auto rec = zeek::cluster::detail::bif::make_endpoint_info(entry.backend->NodeId(), wsc->getRemoteIp(),
|
|
wsc->getRemotePort(), TRANSPORT_TCP);
|
|
auto subscriptions_vec = zeek::cluster::detail::bif::make_string_vec(wsc->GetSubscriptions());
|
|
zeek::event_mgr.Enqueue(Cluster::websocket_client_added, std::move(rec), std::move(subscriptions_vec));
|
|
|
|
entry.wsc->SendAck(entry.backend->NodeId(), zeek::zeek_version());
|
|
|
|
WS_DEBUG("Sent Ack to client %s (%s:%d) %s\n", entry.id.c_str(), wsc->getRemoteIp().c_str(), wsc->getRemotePort(),
|
|
entry.backend->NodeId().c_str());
|
|
|
|
// Process any queued messages now.
|
|
for ( auto& msg : entry.queue ) {
|
|
assert(entry.msg_count > 1);
|
|
Process(msg);
|
|
}
|
|
}
|
|
|
|
void WebSocketEventDispatcher::HandleEvent(WebSocketClientEntry& entry, std::string_view buf) {
|
|
// Unserialize the message as an event.
|
|
broker::variant res;
|
|
auto err = broker::format::json::v1::decode(buf, res);
|
|
if ( err ) {
|
|
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "failed to decode JSON object");
|
|
return;
|
|
}
|
|
|
|
std::string topic = std::string(res->shared_envelope()->topic());
|
|
|
|
if ( topic == broker::topic::reserved ) {
|
|
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "no topic in top-level JSON object");
|
|
return;
|
|
}
|
|
|
|
broker::zeek::Event broker_ev(std::move(res));
|
|
|
|
// This is not guaranteed to work! If the node running the WebSocket
|
|
// API does not have the declaration of the event that another node
|
|
// is sending, it cannot instantiate the zeek::cluster::Event for
|
|
// re-publishing to a cluster backend.
|
|
//
|
|
// Does that make conceptional sense? Basically the WebSocket API
|
|
// has Zeek-script awareness.
|
|
//
|
|
// It works with Broker today because Broker treats messages opaquely.
|
|
// It knows how to convert from JSON into Broker binary format as these
|
|
// are compatible.
|
|
//
|
|
// However, the broker format is under specified (vectors are used for various
|
|
// types without being tagged explicitly), so it's not possible to determine
|
|
// the final Zeek type without having access to the script-layer.
|
|
//
|
|
// I'm not sure this is a real problem, other than it being unfortunate that
|
|
// the Zeek process running the WebSocket API requires access to all declarations
|
|
// of events being transmitted via WebSockets. Though this might be a given anyhow.
|
|
//
|
|
// See broker/Data.cc for broker::vector conversion to see the collisions:
|
|
// vector, list, func, record, pattern, opaque are all encoded using
|
|
// broker::vector rather than dedicated types.
|
|
//
|
|
// Switching to a JSON v2 format that ensures all Zeek types are represented
|
|
// explicitly would help.
|
|
auto zeek_ev = cluster::detail::to_zeek_event(broker_ev);
|
|
if ( ! zeek_ev ) {
|
|
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "failed to create Zeek event");
|
|
return;
|
|
}
|
|
|
|
WS_DEBUG("Publishing event %s to topic '%s'", std::string(zeek_ev->HandlerName()).c_str(), topic.c_str());
|
|
entry.backend->PublishEvent(topic, *zeek_ev);
|
|
}
|
|
|
|
// Process a WebSocket message from a client.
|
|
//
|
|
// If it's the first message, the code is expecting a subscriptions
|
|
// array, otherwise it'll be a remote event.
|
|
void WebSocketEventDispatcher::Process(const WebSocketMessage& msg) {
|
|
const auto& id = msg.id;
|
|
|
|
const auto& it = clients.find(id);
|
|
if ( it == clients.end() ) {
|
|
reporter->Error("WebSocket message from non-existing WebSocket client %s", id.c_str());
|
|
return;
|
|
}
|
|
|
|
// Client without backend wasn't accepted, just discard its message.
|
|
if ( ! it->second.backend )
|
|
return;
|
|
|
|
auto& entry = it->second;
|
|
const auto& wsc = entry.wsc;
|
|
entry.msg_count++;
|
|
|
|
WS_DEBUG("Message %" PRIu64 " size=%zu from client %s (%s:%d) backend=%p", entry.msg_count, msg.msg.size(),
|
|
wsc->getId().c_str(), wsc->getRemoteIp().c_str(), wsc->getRemotePort(), entry.backend.get());
|
|
|
|
// First message is the subscription message.
|
|
if ( entry.msg_count == 1 ) {
|
|
WS_DEBUG("Subscriptions from client %s: (%s:%d)", id.c_str(), wsc->getRemoteIp().c_str(), wsc->getRemotePort());
|
|
HandleSubscriptions(entry, msg.msg);
|
|
}
|
|
else {
|
|
if ( ! wsc->IsAcked() ) {
|
|
WS_DEBUG("Client sending messages before receiving ack!");
|
|
entry.queue.push_back(msg);
|
|
return;
|
|
}
|
|
|
|
HandleEvent(entry, msg.msg);
|
|
}
|
|
}
|