#include "Renderive_WebSocket_Controller.h" #include "Asio_WebSocket_Scene_Loop.h" #include "Web_Event_Adapter.h" #include "Web_Plot_Session.h" #include #include #include #include #include namespace renderive::web { namespace { using Scene_Loop = Asio_WebSocket_Scene_Loop; void send_response(const std::weak_ptr& weak_connection, Web_Response response) { const auto connection = weak_connection.lock(); if (!connection || !connection->connected()) return; const auto message_type = response.type == Web_Response_Type::Pixels ? drogon::WebSocketMessageType::Binary : drogon::WebSocketMessageType::Text; connection->send(response.payload.data(), response.payload.size(), message_type); } void fail_session(const std::weak_ptr& weak_connection, std::exception_ptr exception) { static_cast(::renderive::error::capture("handling Renderive WebSocket scene loop", exception)); LOG_ERROR << "Renderive WebSocket scene loop failed"; if (const auto connection = weak_connection.lock(); connection && connection->connected()) connection->shutdown(drogon::CloseCode::kUnexpectedCondition, "Renderive rendering failed"); } } Renderive_WebSocket_Controller::Renderive_WebSocket_Controller(std::shared_ptr scene_pool) : scene_pool_(std::move(scene_pool)) {} void Renderive_WebSocket_Controller::handleNewConnection(const drogon::HttpRequestPtr&, const drogon::WebSocketConnectionPtr& connection) { const std::weak_ptr weak_connection = connection; connection->setContext(Scene_Loop::create(scene_pool_->get_executor(), [weak_connection](Web_Response response) { send_response(weak_connection, std::move(response)); }, [weak_connection](std::exception_ptr exception) { fail_session(weak_connection, exception); })); connection->setPingMessage("renderive", std::chrono::seconds(20)); LOG_INFO << "Renderive WebSocket connected: " << connection->peerAddr().toIpPort(); } void Renderive_WebSocket_Controller::handleNewMessage(const drogon::WebSocketConnectionPtr& connection, std::string&& message, const drogon::WebSocketMessageType& type) { if (type == drogon::WebSocketMessageType::Ping || type == drogon::WebSocketMessageType::Pong || type == drogon::WebSocketMessageType::Close) return; if (type != drogon::WebSocketMessageType::Text) { connection->shutdown(drogon::CloseCode::kInvalidMessage, "Renderive accepts text events only"); return; } if (message.size() > 16 * 1024) { connection->shutdown(drogon::CloseCode::kMessageTooBig, "Renderive event is too large"); return; } const auto event = Web_Event_Adapter::decode(message); if (!event) { connection->shutdown(drogon::CloseCode::kWrongMessageContent, "Invalid Renderive event"); return; } const auto session = connection->getContext(); if (!session) { connection->shutdown(drogon::CloseCode::kUnexpectedCondition, "Renderive session is unavailable"); return; } if (session->submit(std::move(*event)) == Scene_Loop::Submit_Result::queue_full) connection->shutdown(drogon::CloseCode::kViolation, "Renderive scene queue is full"); } void Renderive_WebSocket_Controller::handleConnectionClosed(const drogon::WebSocketConnectionPtr& connection) { LOG_INFO << "Renderive WebSocket closed: " << connection->peerAddr().toIpPort(); if (const auto session = connection->getContext()) session->stop(); connection->clearContext(); } }