2021-04-28 17:27:32 +00:00
# include <chrono>
# include <thread>
2021-04-27 21:52:48 +00:00
# include <QtConcurrent>
2021-04-28 18:28:07 +00:00
# include <QDateTime>
2021-04-28 22:59:29 +00:00
# include <QTime>
2021-04-27 21:52:48 +00:00
# include "WebSocketServer.h"
2021-04-29 17:52:29 +00:00
# include "WebSocketProtocol.h"
2021-04-27 23:33:47 +00:00
# include "obs-websocket.h"
# include "Config.h"
2021-04-28 17:27:32 +00:00
# include "utils/Utils.h"
2021-04-27 23:33:47 +00:00
2021-04-27 23:41:10 +00:00
# include "plugin-macros.generated.h"
2021-04-27 23:33:47 +00:00
WebSocketServer : : WebSocketServer ( ) :
2021-04-29 05:13:02 +00:00
QObject ( nullptr ) ,
2021-04-27 23:33:47 +00:00
_sessions ( )
{
2021-04-28 22:59:29 +00:00
// Randomize the random number generator
qsrand ( QTime : : currentTime ( ) . msec ( ) ) ;
2021-04-28 20:24:34 +00:00
_server . get_alog ( ) . clear_channels ( websocketpp : : log : : alevel : : all ) ;
_server . get_elog ( ) . clear_channels ( websocketpp : : log : : elevel : : all ) ;
2021-04-27 23:33:47 +00:00
_server . init_asio ( ) ;
# ifndef _WIN32
_server . set_reuse_addr ( true ) ;
# endif
_server . set_open_handler (
websocketpp : : lib : : bind (
& WebSocketServer : : onOpen , this , websocketpp : : lib : : placeholders : : _1
)
) ;
_server . set_close_handler (
websocketpp : : lib : : bind (
& WebSocketServer : : onClose , this , websocketpp : : lib : : placeholders : : _1
)
) ;
_server . set_message_handler (
websocketpp : : lib : : bind (
& WebSocketServer : : onMessage , this , websocketpp : : lib : : placeholders : : _1 , websocketpp : : lib : : placeholders : : _2
)
) ;
}
WebSocketServer : : ~ WebSocketServer ( )
{
2021-04-28 19:20:56 +00:00
if ( _server . is_listening ( ) )
Stop ( ) ;
2021-04-27 23:33:47 +00:00
}
2021-04-28 17:27:32 +00:00
void WebSocketServer : : ServerRunner ( )
{
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::ServerRunner] IO thread started. " ) ;
2021-04-28 17:27:32 +00:00
try {
_server . run ( ) ;
} catch ( websocketpp : : exception const & e ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_ERROR , " [WebSocketServer::ServerRunner] websocketpp instance returned an error: %s " , e . what ( ) ) ;
2021-04-28 17:27:32 +00:00
} catch ( const std : : exception & e ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_ERROR , " [WebSocketServer::ServerRunner] websocketpp instance returned an error: %s " , e . what ( ) ) ;
2021-04-28 17:27:32 +00:00
} catch ( . . . ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_ERROR , " [WebSocketServer::ServerRunner] websocketpp instance returned an error " ) ;
2021-04-28 17:27:32 +00:00
}
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::ServerRunner] IO thread exited. " ) ;
2021-04-28 17:27:32 +00:00
}
2021-04-27 23:33:47 +00:00
void WebSocketServer : : Start ( )
{
2021-04-28 17:27:32 +00:00
if ( _server . is_listening ( ) ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_WARNING , " [WebSocketServer::Start] Call to Start() but the server is already listening. " ) ;
2021-04-28 17:27:32 +00:00
return ;
}
auto conf = GetConfig ( ) ;
if ( ! conf ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_ERROR , " [WebSocketServer::Start] Unable to retreive config! " ) ;
2021-04-28 17:27:32 +00:00
return ;
}
_serverPort = conf - > ServerPort ;
2021-04-29 15:59:54 +00:00
_serverPassword = conf - > ServerPassword ;
2021-04-29 15:24:27 +00:00
_debugEnabled = conf - > DebugEnabled ;
2021-04-29 05:03:23 +00:00
_authenticationRequired = conf - > AuthRequired ;
2021-04-28 17:27:32 +00:00
_authenticationSalt = Utils : : Crypto : : GenerateSalt ( ) ;
_authenticationSecret = Utils : : Crypto : : GenerateSecret ( conf - > ServerPassword . toStdString ( ) , _authenticationSalt ) ;
2021-04-28 20:24:34 +00:00
// Set log levels if debug is enabled
2021-04-29 15:24:27 +00:00
if ( _debugEnabled ) {
2021-04-28 20:24:34 +00:00
_server . get_alog ( ) . set_channels ( websocketpp : : log : : alevel : : all ) ;
_server . get_alog ( ) . clear_channels ( websocketpp : : log : : alevel : : frame_header | websocketpp : : log : : alevel : : frame_payload | websocketpp : : log : : alevel : : control ) ;
_server . get_elog ( ) . set_channels ( websocketpp : : log : : elevel : : all ) ;
2021-04-29 17:11:19 +00:00
_server . get_alog ( ) . clear_channels ( websocketpp : : log : : elevel : : devel | websocketpp : : log : : elevel : : library ) ;
2021-04-28 20:24:34 +00:00
} else {
_server . get_alog ( ) . clear_channels ( websocketpp : : log : : alevel : : all ) ;
_server . get_elog ( ) . clear_channels ( websocketpp : : log : : elevel : : all ) ;
}
2021-04-28 17:27:32 +00:00
_server . reset ( ) ;
websocketpp : : lib : : error_code errorCode ;
2021-04-28 20:24:34 +00:00
_server . listen ( websocketpp : : lib : : asio : : ip : : tcp : : v4 ( ) , _serverPort , errorCode ) ;
2021-04-28 17:27:32 +00:00
if ( errorCode ) {
std : : string errorCodeMessage = errorCode . message ( ) ;
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::Start] Listen failed: %s " , errorCodeMessage . c_str ( ) ) ;
2021-04-28 17:27:32 +00:00
return ;
}
_server . start_accept ( ) ;
_serverThread = std : : thread ( & WebSocketServer : : ServerRunner , this ) ;
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::Start] Server started successfully on port %d. Possible connect address: %s " , _serverPort , Utils : : Platform : : GetLocalAddress ( ) . c_str ( ) ) ;
2021-04-27 23:33:47 +00:00
}
void WebSocketServer : : Stop ( )
{
2021-04-28 17:27:32 +00:00
if ( ! _server . is_listening ( ) ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_WARNING , " [WebSocketServer::Stop] Call to Stop() but the server is not listening. " ) ;
2021-04-28 17:27:32 +00:00
return ;
}
_server . stop_listening ( ) ;
std : : unique_lock < std : : mutex > lock ( _sessionMutex ) ;
for ( auto const & [ hdl , session ] : _sessions ) {
2021-04-28 19:09:12 +00:00
websocketpp : : lib : : error_code errorCode ;
_server . pause_reading ( hdl , errorCode ) ;
if ( errorCode ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::Stop] Error: %s " , errorCode . message ( ) . c_str ( ) ) ;
2021-04-28 19:09:12 +00:00
continue ;
}
_server . close ( hdl , websocketpp : : close : : status : : going_away , " Server stopping. " , errorCode ) ;
if ( errorCode ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::Stop] Error: %s " , errorCode . message ( ) . c_str ( ) ) ;
2021-04-28 19:09:12 +00:00
continue ;
}
2021-04-28 17:27:32 +00:00
}
lock . unlock ( ) ;
_threadPool . waitForDone ( ) ;
2021-04-28 19:36:15 +00:00
// This can delay the thread that it is running on. Bad but kinda required.
2021-04-28 17:27:32 +00:00
while ( _sessions . size ( ) > 0 ) {
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 10 ) ) ;
}
_serverThread . join ( ) ;
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::Stop] Server stopped successfully " ) ;
2021-04-27 23:33:47 +00:00
}
void WebSocketServer : : InvalidateSession ( websocketpp : : connection_hdl hdl )
{
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::InvalidateSession] Invalidating a session. " ) ;
2021-04-28 19:09:12 +00:00
websocketpp : : lib : : error_code errorCode ;
2021-04-29 15:34:50 +00:00
_server . pause_reading ( hdl , errorCode ) ;
2021-04-28 19:09:12 +00:00
if ( errorCode ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::InvalidateSession] Error: %s " , errorCode . message ( ) . c_str ( ) ) ;
2021-04-28 19:09:12 +00:00
return ;
}
2021-04-29 15:34:50 +00:00
_server . close ( hdl , WebSocketCloseCode : : SessionInvalidated , " Your session has been invalidated. " , errorCode ) ;
2021-04-28 19:09:12 +00:00
if ( errorCode ) {
2021-04-29 15:34:50 +00:00
blog ( LOG_INFO , " [WebSocketServer::InvalidateSession] Error: %s " , errorCode . message ( ) . c_str ( ) ) ;
2021-04-28 19:09:12 +00:00
return ;
}
2021-04-27 23:33:47 +00:00
}
2021-04-28 18:28:07 +00:00
std : : vector < WebSocketServer : : WebSocketSessionState > WebSocketServer : : GetWebSocketSessions ( )
2021-04-27 23:33:47 +00:00
{
2021-04-28 18:28:07 +00:00
std : : vector < WebSocketServer : : WebSocketSessionState > webSocketSessions ;
std : : unique_lock < std : : mutex > lock ( _sessionMutex ) ;
for ( auto & [ hdl , session ] : _sessions ) {
uint64_t connectedAt = session . ConnectedAt ( ) ;
uint64_t incomingMessages = session . IncomingMessages ( ) ;
uint64_t outgoingMessages = session . OutgoingMessages ( ) ;
std : : string remoteAddress = session . RemoteAddress ( ) ;
webSocketSessions . emplace_back ( WebSocketSessionState { hdl , remoteAddress , connectedAt , incomingMessages , outgoingMessages } ) ;
}
lock . unlock ( ) ;
2021-04-27 23:33:47 +00:00
return webSocketSessions ;
}
2021-04-29 15:59:54 +00:00
QString WebSocketServer : : GetConnectString ( )
2021-04-27 23:33:47 +00:00
{
2021-04-29 15:59:54 +00:00
QString ret ;
if ( _authenticationRequired )
ret = QString ( " obswebsocket|%1:%2|%3 " ) . arg ( QString : : fromStdString ( Utils : : Platform : : GetLocalAddress ( ) ) ) . arg ( _serverPort ) . arg ( _serverPassword ) ;
else
ret = QString ( " obswebsocket|%1:%2 " ) . arg ( QString : : fromStdString ( Utils : : Platform : : GetLocalAddress ( ) ) ) . arg ( _serverPort ) ;
return ret ;
2021-04-27 23:33:47 +00:00
}
2021-04-30 02:03:32 +00:00
// It isn't consistent to directly call the WebSocketServer from the events system, but it would also be dumb to make it unnecessarily complicated.
2021-04-27 23:33:47 +00:00
void WebSocketServer : : BroadcastEvent ( uint64_t requiredIntent , std : : string eventType , json eventData )
{
2021-04-29 05:52:19 +00:00
QtConcurrent : : run ( & _threadPool , [ = ] ( ) {
2021-04-29 15:52:40 +00:00
// Populate message object
2021-04-29 05:52:19 +00:00
json eventMessage ;
eventMessage [ " messageType " ] = " Event " ;
eventMessage [ " eventType " ] = eventType ;
if ( eventData . is_object ( ) )
eventMessage [ " eventData " ] = eventData ;
2021-04-29 15:52:40 +00:00
// Initialize objects. The broadcast process only dumps the data when its needed.
std : : string messageJson ;
std : : string messageMsgPack ;
2021-04-29 05:52:19 +00:00
2021-04-29 15:59:54 +00:00
// Recurse connected sessions and send the event to suitable sessions.
2021-04-29 05:52:19 +00:00
std : : unique_lock < std : : mutex > lock ( _sessionMutex ) ;
for ( auto & it : _sessions ) {
if ( ! it . second . IsIdentified ( ) )
continue ;
if ( ( it . second . EventSubscriptions ( ) & requiredIntent ) ! = 0 ) {
2021-04-29 16:09:20 +00:00
websocketpp : : lib : : error_code errorCode ;
2021-04-29 05:52:19 +00:00
switch ( it . second . Encoding ( ) ) {
case WebSocketEncoding : : Json :
2021-04-29 15:52:40 +00:00
if ( messageJson . empty ( ) ) {
messageJson = eventMessage . dump ( ) ;
}
2021-04-29 16:09:20 +00:00
_server . send ( ( websocketpp : : connection_hdl ) it . first , messageJson , websocketpp : : frame : : opcode : : text , errorCode ) ;
2021-04-29 16:55:52 +00:00
it . second . IncrementOutgoingMessages ( ) ;
2021-04-29 05:52:19 +00:00
break ;
case WebSocketEncoding : : MsgPack :
2021-04-29 15:52:40 +00:00
if ( messageMsgPack . empty ( ) ) {
auto msgPackData = json : : to_msgpack ( eventMessage ) ;
messageMsgPack = std : : string ( msgPackData . begin ( ) , msgPackData . end ( ) ) ;
}
2021-04-29 16:09:20 +00:00
_server . send ( ( websocketpp : : connection_hdl ) it . first , messageMsgPack , websocketpp : : frame : : opcode : : binary , errorCode ) ;
2021-04-29 16:55:52 +00:00
it . second . IncrementOutgoingMessages ( ) ;
2021-04-29 05:52:19 +00:00
break ;
}
}
}
lock . unlock ( ) ;
2021-04-29 16:55:52 +00:00
if ( _debugEnabled )
blog ( LOG_INFO , " [WebSocketServer::BroadcastEvent] Outgoing event: \n %s " , eventMessage . dump ( 2 ) . c_str ( ) ) ;
2021-04-29 05:52:19 +00:00
} ) ;
2021-04-27 23:33:47 +00:00
}
void WebSocketServer : : onOpen ( websocketpp : : connection_hdl hdl )
{
2021-04-28 20:24:34 +00:00
auto conn = _server . get_con_from_hdl ( hdl ) ;
2021-04-29 15:34:50 +00:00
// Build new session
2021-04-28 20:24:34 +00:00
std : : unique_lock < std : : mutex > lock ( _sessionMutex ) ;
2021-04-29 05:03:23 +00:00
auto & session = _sessions [ hdl ] ;
2021-04-28 20:24:34 +00:00
lock . unlock ( ) ;
2021-04-29 05:03:23 +00:00
2021-04-29 15:34:50 +00:00
// Configure session details
2021-04-29 05:03:23 +00:00
session . SetRemoteAddress ( conn - > get_remote_endpoint ( ) ) ;
session . SetConnectedAt ( QDateTime : : currentSecsSinceEpoch ( ) ) ;
std : : string contentType = conn - > get_request_header ( " Content-Type " ) ;
if ( contentType = = " " ) {
;
} else if ( contentType = = " application/json " ) {
session . SetEncoding ( WebSocketEncoding : : Json ) ;
} else if ( contentType = = " application/msgpack " ) {
session . SetEncoding ( WebSocketEncoding : : MsgPack ) ;
} else {
conn - > close ( WebSocketCloseCode : : InvalidContentType , " Your HTTP `Content-Type` header specifies an invalid encoding type. " ) ;
return ;
}
2021-04-29 15:34:50 +00:00
// Build `Hello`
2021-04-29 05:03:23 +00:00
json helloMessage ;
helloMessage [ " messageType " ] = " Hello " ;
helloMessage [ " obsWebSocketVersion " ] = OBS_WEBSOCKET_VERSION ;
helloMessage [ " rpcVersion " ] = OBS_WEBSOCKET_RPC_VERSION ;
// todo: Add request and event lists
if ( _authenticationRequired ) {
std : : string sessionChallenge = Utils : : Crypto : : GenerateSalt ( ) ;
session . SetChallenge ( sessionChallenge ) ;
helloMessage [ " authentication " ] = { } ;
helloMessage [ " authentication " ] [ " challenge " ] = sessionChallenge ;
helloMessage [ " authentication " ] [ " salt " ] = _authenticationSalt ;
}
2021-04-29 15:34:50 +00:00
// Send object to client
2021-04-29 16:14:03 +00:00
websocketpp : : lib : : error_code errorCode ;
2021-04-29 05:03:23 +00:00
auto sessionEncoding = session . Encoding ( ) ;
if ( sessionEncoding = = WebSocketEncoding : : Json ) {
2021-04-29 16:14:03 +00:00
std : : string helloMessageJson = helloMessage . dump ( ) ;
_server . send ( hdl , helloMessageJson , websocketpp : : frame : : opcode : : text , errorCode ) ;
2021-04-29 05:03:23 +00:00
} else if ( sessionEncoding = = WebSocketEncoding : : MsgPack ) {
2021-04-29 16:14:03 +00:00
auto msgPackData = json : : to_msgpack ( helloMessage ) ;
std : : string messageMsgPack ( msgPackData . begin ( ) , msgPackData . end ( ) ) ;
_server . send ( hdl , messageMsgPack , websocketpp : : frame : : opcode : : binary , errorCode ) ;
2021-04-29 05:03:23 +00:00
}
2021-04-29 16:55:52 +00:00
session . IncrementOutgoingMessages ( ) ;
2021-04-27 23:33:47 +00:00
}
void WebSocketServer : : onClose ( websocketpp : : connection_hdl hdl )
{
2021-04-29 15:34:50 +00:00
auto conn = _server . get_con_from_hdl ( hdl ) ;
2021-04-29 15:52:40 +00:00
// Get info from the session and then delete it
2021-04-28 20:24:34 +00:00
std : : unique_lock < std : : mutex > lock ( _sessionMutex ) ;
2021-04-29 15:52:40 +00:00
auto & session = _sessions [ hdl ] ;
bool isIdentified = session . IsIdentified ( ) ;
uint64_t connectedAt = session . ConnectedAt ( ) ;
uint64_t incomingMessages = session . IncomingMessages ( ) ;
uint64_t outgoingMessages = session . OutgoingMessages ( ) ;
std : : string remoteAddress = session . RemoteAddress ( ) ;
2021-04-28 20:24:34 +00:00
_sessions . erase ( hdl ) ;
lock . unlock ( ) ;
2021-04-29 15:52:40 +00:00
// Build SessionState object for signal
WebSocketSessionState state ;
state . remoteAddress = remoteAddress ;
state . connectedAt = connectedAt ;
state . incomingMessages = incomingMessages ;
state . outgoingMessages = outgoingMessages ;
// Emit signals
emit ClientDisconnected ( state , conn - > get_local_close_code ( ) ) ;
if ( isIdentified )
emit IdentifiedClientDisconnected ( state , conn - > get_local_close_code ( ) ) ;
2021-04-27 23:33:47 +00:00
}
void WebSocketServer : : onMessage ( websocketpp : : connection_hdl hdl , websocketpp : : server < websocketpp : : config : : asio > : : message_ptr message )
{
2021-04-29 16:42:22 +00:00
auto opcode = message - > get_opcode ( ) ;
std : : string payload = message - > get_payload ( ) ;
QtConcurrent : : run ( & _threadPool , [ = ] ( ) {
std : : unique_lock < std : : mutex > lock ( _sessionMutex ) ;
auto & session = _sessions [ hdl ] ;
lock . unlock ( ) ;
2021-04-29 16:55:52 +00:00
session . IncrementIncomingMessages ( ) ;
2021-04-29 16:42:22 +00:00
json incomingMessage ;
// Check for invalid opcode and decode
websocketpp : : lib : : error_code errorCode ;
2021-04-30 02:03:32 +00:00
uint8_t sessionEncoding = session . Encoding ( ) ;
if ( sessionEncoding = = WebSocketEncoding : : Json ) {
2021-04-29 17:11:19 +00:00
if ( opcode ! = websocketpp : : frame : : opcode : : text ) {
2021-04-30 02:14:23 +00:00
if ( ! session . IgnoreInvalidMessages ( ) )
2021-04-29 16:42:22 +00:00
_server . close ( hdl , WebSocketCloseCode : : MessageDecodeError , " The session encoding is set to Json, but the client sent a binary message. " , errorCode ) ;
return ;
}
2021-04-30 02:14:23 +00:00
2021-04-29 16:42:22 +00:00
try {
2021-04-29 17:11:19 +00:00
incomingMessage = json : : parse ( payload ) ;
2021-04-29 16:42:22 +00:00
} catch ( json : : parse_error & e ) {
2021-04-30 02:14:23 +00:00
if ( ! session . IgnoreInvalidMessages ( ) )
2021-04-29 16:42:22 +00:00
_server . close ( hdl , WebSocketCloseCode : : MessageDecodeError , std : : string ( " Unable to decode Json: " ) + e . what ( ) , errorCode ) ;
return ;
}
2021-04-30 02:03:32 +00:00
} else if ( sessionEncoding = = WebSocketEncoding : : MsgPack ) {
2021-04-29 17:11:19 +00:00
if ( opcode ! = websocketpp : : frame : : opcode : : binary ) {
2021-04-30 02:14:23 +00:00
if ( ! session . IgnoreInvalidMessages ( ) )
2021-04-29 16:42:22 +00:00
_server . close ( hdl , WebSocketCloseCode : : MessageDecodeError , " The session encoding is set to MsgPack, but the client sent a text message. " , errorCode ) ;
return ;
}
2021-04-30 02:14:23 +00:00
2021-04-29 16:42:22 +00:00
try {
2021-04-29 17:11:19 +00:00
incomingMessage = json : : from_msgpack ( payload ) ;
2021-04-29 16:42:22 +00:00
} catch ( json : : parse_error & e ) {
2021-04-30 02:14:23 +00:00
if ( ! session . IgnoreInvalidMessages ( ) )
2021-04-29 16:55:52 +00:00
_server . close ( hdl , WebSocketCloseCode : : MessageDecodeError , std : : string ( " Unable to decode MsgPack: " ) + e . what ( ) , errorCode ) ;
2021-04-29 16:42:22 +00:00
return ;
}
}
2021-04-29 16:55:52 +00:00
if ( _debugEnabled )
blog ( LOG_INFO , " [WebSocketServer::onMessage] Incoming message (decoded): \n %s " , incomingMessage . dump ( 2 ) . c_str ( ) ) ;
2021-04-30 02:03:32 +00:00
2021-04-30 02:14:23 +00:00
WebSocketProtocol : : ProcessResult ret = WebSocketProtocol : : ProcessMessage ( hdl , & session , incomingMessage ) ;
2021-04-30 02:03:32 +00:00
if ( ret . closeCode ) {
websocketpp : : lib : : error_code errorCode ;
_server . close ( hdl , ret . closeCode , ret . closeReason , errorCode ) ;
return ;
}
if ( ret . result ) {
websocketpp : : lib : : error_code errorCode ;
if ( sessionEncoding = = WebSocketEncoding : : Json ) {
std : : string helloMessageJson = ret . result . dump ( ) ;
_server . send ( hdl , helloMessageJson , websocketpp : : frame : : opcode : : text , errorCode ) ;
} else if ( sessionEncoding = = WebSocketEncoding : : MsgPack ) {
auto msgPackData = json : : to_msgpack ( ret . result ) ;
std : : string messageMsgPack ( msgPackData . begin ( ) , msgPackData . end ( ) ) ;
_server . send ( hdl , messageMsgPack , websocketpp : : frame : : opcode : : binary , errorCode ) ;
}
session . IncrementOutgoingMessages ( ) ;
2021-04-30 02:14:23 +00:00
if ( _debugEnabled )
blog ( LOG_INFO , " [WebSocketServer::onMessage] Outgoing message: \n %s " , ret . result . dump ( 2 ) . c_str ( ) ) ;
if ( errorCode )
2021-04-30 02:03:32 +00:00
blog ( LOG_WARNING , " [WebSocketServer::onMessage] Sending message to client failed: %s " , errorCode . message ( ) . c_str ( ) ) ;
}
2021-04-29 16:42:22 +00:00
} ) ;
2021-04-27 23:33:47 +00:00
}