Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
#
# $Id$
# $HeadURL$
# $Author$
# Created by Philipp Dassow, SLST, 2017-11-28
#
# --- Library LtsWebSocketCommon --------------------------------------------------------------------------------
#
SET(LtsWebSocketCommon_SRCS
client_message_factory.cpp
client_message_factory.h
client_message.cpp
client_message.h
reply_message.cpp
reply_message.h
data_manager_base_dl.h
json_keys.h
logger.cpp
logger.h
)
NGL_ADD_LIBRARY2(WS AAR SV LtsWebSocketCommon SHARED ${LtsWebSocketCommon_SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(LtsWebSocketCommon
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsWebSocketCommon
Qt5::Core
Qt5::WebSockets
NGLLogger
)
#
# --- Library ltsWebSocketServer --------------------------------------------------------------------------------
#
SET(LtsWebSocketServer_SRCS
client_connection.cpp
client_connection.h
connection_handler.cpp
connection_handler.h
lts_web_socket_server.cpp
lts_web_socket_server.h
)
NGL_ADD_LIBRARY2(WS AAR SV LtsWebSocketServer SHARED ${LtsWebSocketServer_SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(LtsWebSocketServer
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsWebSocketServer
Qt5::Core
LtsWebSocketCommon
)
#
# --- Library ltsWebSocketClient --------------------------------------------------------------------------------
#
SET(LtsWebSocketClient_SRCS
lts_web_socket_client.cpp
lts_web_socket_client.h
)
NGL_ADD_LIBRARY2(WS AAR SV LtsWebSocketClient SHARED ${LtsWebSocketClient_SRCS})
NGL_TARGET_INCLUDE_DIRECTORIES(LtsWebSocketClient
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
NGL_TARGET_LINK_LIBRARIES(LtsWebSocketClient
Qt5::Core
LtsWebSocketCommon
)
+90
View File
@@ -0,0 +1,90 @@
/**
* @RDE_HEADER@
* The ClientConnection class represents a connection to a single client.
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 2016-08-25
*/
#include "client_connection.h"
#include <QWebSocket>
namespace LtsWebSocket
{
ClientConnection::ClientConnection(QWebSocket *socket, QObject *parent)
: QThread(parent)
, m_socket(socket ? socket : new QWebSocket)
, m_logger(getLogger("ClientConnection"))
{
}
ClientConnection::~ClientConnection()
{
DEBUG_LOG(m_logger) << "Thread closed" << this;
delete m_socket;
qDeleteAll(m_messageQueue);
}
bool ClientConnection::init()
{
if (m_socket == nullptr)
{
ERROR_LOG(m_logger) << "Initialisation failed due to an invalid socket";
return false;
}
DEBUG_LOG(m_logger) << "Started new thread" << this;
connect(m_socket, &QWebSocket::textMessageReceived, this, &ClientConnection::onNewTextMessageFromSocket, Qt::DirectConnection);
connect(m_socket, &QWebSocket::connected, this, &ClientConnection::sendMessagesFromQueue);
connect(m_socket, &QWebSocket::disconnected, this, &ClientConnection::onSocketDisconnect);
return true;
}
void ClientConnection::addReply(ReplyMessage *message)
{
if (!m_socket || m_socket->state() != QAbstractSocket::ConnectedState)
{
m_messageQueue.enqueue(message);
DEBUG_LOG(m_logger) << "Adding message to queue, size: " << m_messageQueue.size();
return;
}
const QString msg(message->toJsonString());
DEBUG_LOG(m_logger) << "Sending message: " << msg ;//<< msg;
m_socket->sendTextMessage(msg);
delete message;
}
void ClientConnection::onNewTextMessageFromSocket(const QString &message)
{
DEBUG_LOG(m_logger) << "Recv msg: " << message << this;
emit newTextMessage(this, message);
}
void ClientConnection::sendMessagesFromQueue()
{
INFO_LOG(m_logger) << "Client connected ip: " << m_socket->peerAddress().toString() << ":" << m_socket->peerPort();
if (m_socket->state() != QAbstractSocket::ConnectedState)
return;
while (!m_messageQueue.isEmpty())
{
ReplyMessage *message = m_messageQueue.dequeue();
const QString msg(message->toJsonString());
TRACE_LOG(m_logger) << "Send msg: " << msg;
m_socket->sendTextMessage(msg);
delete message;
DEBUG_LOG(m_logger) << "Sent message from queue, new size: " << m_messageQueue.size();
}
}
void ClientConnection::onSocketDisconnect()
{
DEBUG_LOG(m_logger) << "Client disconnected ip: " << m_socket->peerAddress().toString() << ":" << m_socket->peerPort();
exit(0);
emit disconnected(this);
}
} // namespace LtsWebSocket
+91
View File
@@ -0,0 +1,91 @@
/**
* @RDE_HEADER@
* The ClientConnection class represents a connection to a single client.
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 2016-08-25
*/
#pragma once
#include <QtCore/QThread>
#include <QtCore/QQueue>
#include "reply_message.h"
#include "logger.h"
#include <LtsWebSocket/ltswebsocketserver_export.h>
class QWebSocket;
namespace LtsWebSocket
{
/**
* @brief The ClientConnection class represents a connection to a single client
*/
class LTSWEBSOCKETSERVER_EXPORT ClientConnection
: public QThread
, public ReplyHandler
{
Q_OBJECT
public:
/**
* @brief ClientConnection Constructor
* @param socket Pointer to a QWebSocket Object. Should be QWebSocketServer::nextPendingConnection()
* @param parent QObject parent
*/
explicit ClientConnection(QWebSocket *socket, QObject *parent = nullptr);
/**
* @brief ~ClientConnection Destructor
*/
~ClientConnection();
/**
* @brief init Initialises the ClientConnection
* @return True on success, otherwise false
*/
bool init();
/**
* @brief writeTextMessage Add a message to a queue to be sent to the client
* @param message Message which is queued
*/
void addReply(ReplyMessage *message) override;
QWebSocket *webSocket() { return m_socket; }
signals:
/**
* @brief newTextMessage Emitted when a new text message arrives at the associated socket
* @param message The new message
*/
void newTextMessage(ClientConnection *client, const QString &message);
/**
* @brief disconnected Emitted when a client disconnects
* @param client The client
*/
void disconnected(ClientConnection *client);
private slots:
void onNewTextMessageFromSocket(const QString &message);
void sendMessagesFromQueue();
void onSocketDisconnect();
private: // methods
/**
* @brief sendMessages Runs in an own thread and constantly tries to send
* queued messages
*/
void sendMessages();
private: // attributes
QWebSocket *m_socket;
QQueue<ReplyMessage *> m_messageQueue;
LOGGER_T m_logger;
}; // class ClientConnection
} // namespace LtsWebSocket
+44
View File
@@ -0,0 +1,44 @@
/**
* @RDE_HEADER@
* LTS web socket server - Abstract Client Message
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#include "client_message.h"
#include "json_keys.h"
#include <QJsonDocument>
namespace LtsWebSocket
{
// ######## AbstractClientMessage end ########
// ######## InvalidMessage begin ########
InvalidMessage::InvalidMessage(ReplyHandler *client, const QString &uuid, const QString &errorText)
: AbstractClientMessage(client, QString(), 1, uuid) // Check if 1 is feasible - or if it needs to be a part of InvalidMessage
, m_errorText(errorText)
{}
void InvalidMessage::process()
{
ErrorMessage::ErrorData errorData;
errorData.errorClass = 1; // ??????????
errorData.errorMessage = m_errorText;
errorData.uuid = m_uuid;
m_client->addReply(new ErrorMessage(errorData));
}
bool InvalidMessage::hasValidData() const
{
// always true because there is no data part
return true;
}
// ######## InvalidMessage end ########
} // namespace LtsWebSocketServer
+84
View File
@@ -0,0 +1,84 @@
/**
* @RDE_HEADER@
* LTS web socket server - Abstract Client Message
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#pragma once
#include <LtsWebSocket/ltswebsocketcommon_export.h>
#include "reply_message.h"
#include <QString>
#include <QJsonValue>
namespace LtsWebSocket
{
/**
* @brief API for client message implementations that are needed by the LtsWebSocketServer
*/
class LTSWEBSOCKETCOMMON_EXPORT AbstractClientMessage
{
public:
AbstractClientMessage(ReplyHandler *client, const QString &type, const int typeId, const QString &uuid, const QJsonValue &data = QJsonValue())
: m_type(type)
, m_typeId(typeId)
, m_uuid(uuid)
, m_data(data)
, m_client(client)
{}
virtual ~AbstractClientMessage() = default;
/**
* @brief process Processes the messages data
*/
virtual void process() = 0;
/**
* @brief hasValidData Validates the content of the message
* @return True if the message is ICD conform, otherwise false
*/
virtual bool hasValidData() const = 0;
/**
* @brief reply Send a Reply to the client that has send this message
* @param reply
*/
void reply(ReplyMessage *reply)
{
m_client->addReply(reply);
}
protected: // attributes
const QString m_type;
const int m_typeId;
const QString m_uuid;
const QJsonValue m_data;
ReplyHandler *m_client;
}; // class AbstractClientMessage
// #################### basic error messages ####################
/**
* @brief The InvalidMessage class represents any message from the client which either could
* not be validated, is not JSON conform or has no type or uuid
*/
class LTSWEBSOCKETCOMMON_EXPORT InvalidMessage final : public AbstractClientMessage
{
public:
explicit InvalidMessage(ReplyHandler *client, const QString &uuid, const QString &errorText);
void process() override;
bool hasValidData() const override;
private: // attributes
QString m_errorText;
}; // class InvalidMessage
} // namespace LtsWebSocket
+75
View File
@@ -0,0 +1,75 @@
/**
* @RDE_HEADER@
* LTS web socket server - Abstract Client Message Factory
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#include "client_message_factory.h"
#include "client_connection.h"
#include "json_keys.h"
#include <QJsonDocument>
namespace LtsWebSocket
{
// ######## AbstractClientMessageFactory begin ########
QJsonParseError AbstractClientMessageFactory::parseMessage(const QString &message)
{
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error != QJsonParseError::NoError)
return error;
QJsonObject obj = doc.object();
m_data = obj.value(JsonKeys::DATA());
m_type = obj.value(JsonKeys::TYPE()).toString();
m_typeId = obj.value(JsonKeys::TYPEID()).toInt();
m_uuid = obj.value(JsonKeys::UUID_()).toString();
return error;
}
AbstractClientMessage *AbstractClientMessageFactory::checkAttributes(ReplyHandler *client,
const QString &message) const
{
if (!m_uuid.isEmpty() && !m_type.isEmpty())
return nullptr;
if (m_uuid.isEmpty())
{
WARNING_LOG(m_logger) << "Got message without uuid: " << message << "'";
return new LtsWebSocket::InvalidMessage(client, QString(), QStringLiteral("Got message without uuid"));
}
WARNING_LOG(m_logger) << "Got message no type: " << message << "'";
return new LtsWebSocket::InvalidMessage(client, m_uuid, QStringLiteral("Message has no type"));
}
AbstractClientMessage *AbstractClientMessageFactory::checkResult(ReplyHandler *client,
const QString &message,
AbstractClientMessage *result) const
{
if (result == nullptr)
{
WARNING_LOG(m_logger) << "Could not parse message: " << message;
result = new LtsWebSocket::InvalidMessage(client, m_uuid, QStringLiteral("Invalid type"));
}
if (!result->hasValidData())
{
WARNING_LOG(m_logger) << "Resulting JSon message has invalid data... " << message;
delete result;
result = new LtsWebSocket::InvalidMessage(client, m_uuid, QStringLiteral("Has invalid data part"));
}
return result;
}
// ######## AbstractClientMessageFactory end ########
} // namespace LtsWebSocketServer
+85
View File
@@ -0,0 +1,85 @@
/**
* @RDE_HEADER@
* LTS web socket server - Abstract Client Message Factory
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#pragma once
#include "client_message.h"
#include "reply_message.h"
#include <LtsWebSocket/ltswebsocketcommon_export.h>
#include <QString>
#include <QJsonParseError>
namespace NGL
{
class INGLLogger;
}
namespace LtsWebSocket
{
/**
* @brief API for the client message factory that has to be provided to the LtsWebSocketServer
* */
class LTSWEBSOCKETCOMMON_EXPORT AbstractClientMessageFactory
{
public:
explicit AbstractClientMessageFactory(NGL::INGLLogger &logger)
: m_logger(logger)
{}
virtual ~AbstractClientMessageFactory() {}
/**
* @brief parseMessage parses the message for TYPE, UUID and DATA
* @param message to be parsed
* @return QJsonParseError
*/
QJsonParseError parseMessage(const QString &message);
/**
* @brief createMessage Creates the ClientMessage which will process the message
* Since the message is not created in the main thread, nothing should be done here!
* @param client Handler for replys
* @return new client message processor
*/
virtual AbstractClientMessage *createMessage(ReplyHandler *client, const QString &message) = 0;
protected:
AbstractClientMessage *checkAttributes(ReplyHandler *client,
const QString &message) const;
AbstractClientMessage *checkResult(ReplyHandler *client,
const QString &message,
AbstractClientMessage *result) const;
protected: // attributes of currently processed message
QString m_type;
int m_typeId;
QString m_uuid;
QJsonValue m_data;
NGL::INGLLogger &m_logger;
}; // class AbstractClientMessageFactory
class PongMessage final : public ReplyMessage
{
public:
static QString strPong() { return QStringLiteral("pong"); }
static int idPong() { return 2; }
explicit PongMessage(const QString &payload)
: ReplyMessage(strPong(), idPong(), QString())
, m_payload(payload)
{}
QString toJsonString() const override
{
return m_payload.isEmpty() ? strPong() : QStringLiteral("%1 %2").arg(strPong(), m_payload);
}
private:
QString m_payload;
};
} // namespace LtsWebSocket
+95
View File
@@ -0,0 +1,95 @@
/**
* @RDE_HEADER@
* The ConnectionHandler class functions as a WebSocket server and manages incoming client connections.
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 2016-08-25
*/
#include "connection_handler.h"
#include "client_connection.h"
#include <QtWebSockets/QWebSocket>
using namespace LtsWebSocket;
ConnectionHandler::ConnectionHandler(QObject *parent)
: QObject(parent)
, m_server(nullptr)
, m_logger(getLogger("ConnectionHandler"))
{}
ConnectionHandler::~ConnectionHandler()
{
for (auto connection : qAsConst(m_connections))
connection->disconnect(this);
}
bool ConnectionHandler::init(const QString &serviceName, unsigned short port)
{
if (m_server != nullptr)
{
m_server->disconnect();
delete m_server;
}
// TODO: get the SslMode from config ?
m_server = new QWebSocketServer(QStringLiteral("RME-") + serviceName, QWebSocketServer::NonSecureMode, this);
connect(m_server, &QWebSocketServer::newConnection, this, &ConnectionHandler::onNewConnection);
connect(m_server, &QWebSocketServer::serverError, this, &ConnectionHandler::onServerError);
DEBUG_LOG(m_logger) << "Trying to listen ";
if (!m_server->listen(QHostAddress::Any, port))
{
ERROR_LOG(m_logger) << "Not possible to listen to port " << port;
return false;
}
DEBUG_LOG(m_logger) << "... ok, port " << m_server->serverPort();
return true;
}
void ConnectionHandler::sendToAllClients(ReplyMessage *message)
{
for (auto connection : qAsConst(m_connections))
connection->addReply(new ReplyMessage(*message));
delete message;
}
void ConnectionHandler::onServerError(QWebSocketProtocol::CloseCode closeCode)
{
ERROR_LOG(m_logger) << "Got server error with close code " << closeCode;
}
void ConnectionHandler::onNewConnection()
{
while (m_server->hasPendingConnections())
{
QWebSocket *socket = m_server->nextPendingConnection();
ClientConnection *newConnection = new ClientConnection(socket, this);
connect(newConnection, &ClientConnection::destroyed, this, [this](QObject* o)
{
m_connections.remove(static_cast<ClientConnection*>(o));
});
if (!newConnection->init())
{
ERROR_LOG(m_logger) << "Failed to initialise new client";
delete newConnection;
newConnection = nullptr;
continue;
}
newConnection->start();
connect(newConnection, &ClientConnection::newTextMessage, this, &ConnectionHandler::newTextMessageFromClient);
connect(newConnection, &ClientConnection::disconnected, this, &ConnectionHandler::clientDisconnected);
connect(newConnection, &ClientConnection::finished, newConnection, &ClientConnection::deleteLater);
m_connections.insert(newConnection);
emit clientConnected(newConnection);
INFO_LOG(m_logger) << "New client connected ip: " << socket->localAddress().toString() << ":" << socket->localPort();
}
}
+88
View File
@@ -0,0 +1,88 @@
/**
* @RDE_HEADER@
* The ConnectionHandler class functions as a WebSocket server and manages incoming client connections.
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 2016-08-25
*/
#pragma once
#include <QtNetwork/QHostAddress>
#include <QtWebSockets/qwebsocketprotocol.h>
#include <QtWebSockets/QWebSocketServer>
#include "logger.h"
namespace LtsWebSocket
{
class MessageHandler;
class ClientConnection;
class ReplyMessage;
/**
* @brief The ConnectionHandler class functions as a WebSocket server and manages incoming client connections
*/
class ConnectionHandler
: public QObject
{
Q_OBJECT
public:
/**
* @brief ConnectionHandler
* @param parent
*/
explicit ConnectionHandler(QObject *parent = nullptr);
~ConnectionHandler();
/**
* @brief init Starts listening for incoming connections and connects internal signals and slots
* @param serviceName Is Used to name the server (RDE-"servicename")
* @param port Port the server is listening, if 0 the port number will be random
* @return True on success, otherwise false
*/
bool init(const QString &serviceName, unsigned short port);
/**
* @brief sendToAll Sends the given msg to all connected Clients
* @param message Message to be send
*/
void sendToAllClients(ReplyMessage *message);
/**
* @brief webSocketPort returns the listen port
* @return listen port
*/
unsigned short webSocketPort() { return m_server->serverPort(); }
signals:
/**
* @brief newTextMessageFromClient Emitted when a message from any client is received
* @param client Client where replies are written to
* @param message Message from the client
*/
void newTextMessageFromClient(ClientConnection *client, const QString &message);
/**
* @brief clientDisconnected Emitted when a client disconnects
* @param client The client
*/
void clientDisconnected(ClientConnection *client);
/**
* @brief clientConnected Emitted when a client connects
* @param client The client
*/
void clientConnected(ClientConnection *client);
private slots:
void onServerError(QWebSocketProtocol::CloseCode closeCode);
void onNewConnection();
private: // attributes
QWebSocketServer *m_server;
QSet<ClientConnection *> m_connections;
LOGGER_T m_logger;
};
} // namespace LtsWebSocket
+61
View File
@@ -0,0 +1,61 @@
/**
* @RDE_HEADER@
* The DataManager class is a convenience class for managing structure data
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-12-12
*/
#pragma once
#include <memory>
#include <NGLLogger/INGLLogger.h>
#include "reply_message.h"
namespace LtsWebSocket
{
/**
* @brief The DataManager class is a convenience class for managing structure data from db
* T needs to be of namspace::DatabaseInterface
*/
template<typename T>
class DataManagerBaseDl
{
protected: // methods
/**
* @brief DataManager Constructor
* @param dbInterface Interface for requesting db-structs data
*/
explicit DataManagerBaseDl(const std::shared_ptr<T> &dbInterface, NGL::INGLLogger &logger)
: m_logger(logger)
, m_dbInterface(dbInterface)
{}
/**
* @brief toBaseDataMessageFromDb selects the DbType data from the db and converts it to JSON
* @param uuid of the base DL request
* @return message send to the requester
*/
template<typename DbType, typename MessageType>
ReplyMessage *toBaseDataMessageFromDb(const QString &uuid) const
{
QVector<DbType> dataVec;
// read all data from database
if (!m_dbInterface->select(dataVec))
{
const QString errorMessage = QStringLiteral("Error reading data from database!");
ERROR_LOG(m_logger) << errorMessage;
ErrorMessage::ErrorData errData;
errData.errorMessage = errorMessage;
errData.uuid = uuid;
return new ErrorMessage(errData);
}
return new MessageType (m_dbInterface->template toJson<DbType>(dataVec), uuid);
}
protected: // attributes
NGL::INGLLogger &m_logger;
std::shared_ptr<T> m_dbInterface;
}; // class DataManagerBaseDl
} // namespace LtsWebSocket
+33
View File
@@ -0,0 +1,33 @@
/**
* @RDE_HEADER@
* Header file which holds basic JSON keys
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLSt, 2017-11-28
*/
#pragma once
#include <QtCore/QString>
namespace LtsWebSocket
{
/**
* @brief Namespace which holds all occuring JSON keys as const QString objects
*/
namespace JsonKeys
{
// root keys
inline QString DATA() { return QStringLiteral("data"); }
inline QString UUID_() { return QStringLiteral("uuid"); } // Windows: typedef GUID UUUID; ...
inline QString TYPE() { return QStringLiteral("type"); }
inline QString TYPEID() { return QStringLiteral("typeId"); }
// types for server --> client messages
inline QString ERROR_() { return QStringLiteral("error"); }
// data keys
inline QString ERROR_MESSAGE() { return QStringLiteral("errorMessage"); }
inline QString ERROR_CLASS() { return QStringLiteral("errorClass"); }
} // namespace JsonKeys
} // namespace LtsWebSocket
+38
View File
@@ -0,0 +1,38 @@
/**
* @RDE_HEADER@
* LTS web socket server - logging facility
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#include "logger.h"
#include <NGLLogger/NGLLoggerBootstrap.h>
namespace LtsWebSocket
{
LOGGER_T getLogger(const char *name)
{
static const char ns[] = "Lts.WebSocket.";
std::string loggerName;
const size_t len = strlen(name);
loggerName.reserve(sizeof(ns) + len);
loggerName.append(ns, sizeof(ns) - 1);
loggerName.append(name, len);
try
{
return NGL::NGLLoggerBootstrap::getNGLRootLogger().
getNGLLogger(loggerName);
}
catch (const std::exception &ex)
{
fprintf(stderr, "Error retrieving logger: %s", ex.what());
abort();
}
}
} // namespace LtsWebSocketServer
+24
View File
@@ -0,0 +1,24 @@
/**
* @RDE_HEADER@
* LTS web socket server - logging facility
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#pragma once
#include <LtsWebSocket/ltswebsocketcommon_export.h>
#include <NGLLogger/INGLLogger.h>
namespace LtsWebSocket
{
typedef NGL::INGLLogger &LOGGER_T;
LTSWEBSOCKETCOMMON_EXPORT LOGGER_T getLogger(const char *name);
inline LOGGER_T getLogger(const QString &loggerName)
{
return getLogger(loggerName.toUtf8().constData());
}
} // namespace LtsWebSocket
+208
View File
@@ -0,0 +1,208 @@
/**
* @RDE_HEADER@
* The LtsWebSocketClient handles a websocket connection to a server
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, NLST, 2018-03-19
*/
#include "lts_web_socket_client.h"
#include <QThread>
#include <QUrl>
#include <QWebSocket>
#include "client_connection.h"
namespace LtsWebSocket
{
class WebSocketHelper : public QObject
{
Q_OBJECT
public:
WebSocketHelper(AbstractClientMessageFactory *messageFactory,
LtsWebSocketClient *webSocketClient,
NGL::INGLLogger &logger);
~WebSocketHelper();
public Q_SLOTS:
void doOpen(const QString &hostname, const unsigned short port);
void doClose();
void doSendTextMessage(const QString &msg);
void onConnected();
void onDisconnected();
void onError(QAbstractSocket::SocketError error);
void parseMessage(const QString &message);
Q_SIGNALS:
void messageParsed(AbstractClientMessage *clientMessage);
void connected();
void disconnected();
void dataReceived();
void error(QAbstractSocket::SocketError);
private:
AbstractClientMessageFactory *m_messageFactory;
LtsWebSocketClient *m_webSocketClient;
QWebSocket *m_socket;
NGL::INGLLogger &m_logger;
};
WebSocketHelper::WebSocketHelper(AbstractClientMessageFactory *messageFactory,
LtsWebSocketClient *webSocketClient,
NGL::INGLLogger &logger)
: QObject() // no parent, is moved to another thread
, m_messageFactory(messageFactory)
, m_webSocketClient(webSocketClient)
, m_socket(new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this))
, m_logger(logger)
{
connect(m_socket, &QWebSocket::textMessageReceived, this, &WebSocketHelper::parseMessage);
connect(m_socket, &QWebSocket::textFrameReceived, this, &WebSocketHelper::dataReceived);
connect(m_socket, &QWebSocket::connected, this, &WebSocketHelper::onConnected);
connect(m_socket, &QWebSocket::disconnected, this, &WebSocketHelper::onDisconnected);
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, &WebSocketHelper::error);
}
WebSocketHelper::~WebSocketHelper()
{
m_socket->close();
delete m_socket;
}
void WebSocketHelper::doOpen(const QString &hostname, const unsigned short port)
{
const QUrl url(QStringLiteral("ws://%1:%2/").arg(hostname).arg(port));
if (m_socket->state() == QAbstractSocket::SocketState::ConnectedState)
{
if (m_socket->requestUrl() != url)
doClose();
else
{
INFO_LOG(m_logger) << "Web socket to url: " << url.toDisplayString() << " is already open.";
return;
}
}
INFO_LOG(m_logger) << "Opening web socket to url: " << url.toDisplayString();
m_socket->open(url);
}
void WebSocketHelper::doClose()
{
INFO_LOG(m_logger) << "Closing web socket to url:" << m_socket->requestUrl().toDisplayString();
m_socket->close();
}
void WebSocketHelper::doSendTextMessage(const QString &msg)
{
if (!m_socket || m_socket->state() != QAbstractSocket::ConnectedState)
{
ERROR_LOG(m_logger) << "Message could not be send, socket is not connected.";
return;
}
DEBUG_LOG(m_logger) << "Sending message: " << msg;
m_socket->sendTextMessage(msg);
}
void WebSocketHelper::onConnected()
{
INFO_LOG(m_logger) << "Connected to server url: " << m_socket->requestUrl().toDisplayString()
<< " using local port: " << m_socket->localPort();
emit connected();
}
void WebSocketHelper::onDisconnected()
{
INFO_LOG(m_logger) << "Disconnected from server url: " << m_socket->requestUrl().toDisplayString();
emit disconnected();
}
void WebSocketHelper::onError(QAbstractSocket::SocketError error)
{
ERROR_LOG(m_logger) << "Error occured: " << error << " closing socket.";
m_socket->close();
}
void WebSocketHelper::parseMessage(const QString &message)
{
const QString pingMsg(QStringLiteral("ping"));
if (message.startsWith(pingMsg))
{
doSendTextMessage(PongMessage(message.mid(pingMsg.size())).toJsonString());
return;
}
AbstractClientMessage *clientMessage = nullptr;
auto result = m_messageFactory->parseMessage(message);
if (QJsonParseError::NoError == result.error)
clientMessage = m_messageFactory->createMessage(m_webSocketClient, message);
else
{
WARNING_LOG(m_logger) << "Could not parse incoming message: '" << message
<< "', error: " << result.errorString();
clientMessage = new InvalidMessage(m_webSocketClient, QString(),
QStringLiteral("Got message which is not JSON conform (%1)").arg(result.errorString()));
}
if (clientMessage)
emit messageParsed(clientMessage);
}
//
// LtsWebSocketClient
//
LtsWebSocketClient::LtsWebSocketClient(AbstractClientMessageFactory *messageFactory, QObject *parent)
: QObject(parent)
, m_messageFactory(messageFactory)
, m_webSocketHelperThread(new QThread(this))
, m_logger(getLogger("LtsWebSocketClient"))
{
m_webSocketHelper = new WebSocketHelper(m_messageFactory, this, m_logger);
m_webSocketHelper->moveToThread(m_webSocketHelperThread);
m_webSocketHelperThread->start();
connect(m_webSocketHelper, &WebSocketHelper::connected, this, &LtsWebSocketClient::connected);
connect(m_webSocketHelper, &WebSocketHelper::disconnected, this, &LtsWebSocketClient::disconnected);
connect(m_webSocketHelper, &WebSocketHelper::dataReceived, this, &LtsWebSocketClient::dataReceived);
connect(m_webSocketHelper, &WebSocketHelper::messageParsed, this, &LtsWebSocketClient::onNewClientMessage);
connect(this, &LtsWebSocketClient::onOpen, m_webSocketHelper, &WebSocketHelper::doOpen);
connect(this, &LtsWebSocketClient::onClose, m_webSocketHelper, &WebSocketHelper::doClose);
connect(this, &LtsWebSocketClient::onSendTextMessage, m_webSocketHelper, &WebSocketHelper::doSendTextMessage);
}
LtsWebSocketClient::~LtsWebSocketClient()
{
m_webSocketHelper->disconnect(this);
m_webSocketHelperThread->quit();
m_webSocketHelperThread->wait();
delete m_webSocketHelper;
}
void LtsWebSocketClient::open(const QString &hostname, const unsigned short port)
{
emit onOpen(hostname, port);
}
void LtsWebSocketClient::close()
{
emit onClose();
}
void LtsWebSocketClient::addReply(ReplyMessage *message)
{
const QString msg(message->toJsonString());
emit onSendTextMessage(msg);
delete message;
}
void LtsWebSocketClient::onNewClientMessage(AbstractClientMessage *clientMessage)
{
if (!clientMessage)
return;
clientMessage->process();
delete clientMessage;
}
}
#include "lts_web_socket_client.moc"
+84
View File
@@ -0,0 +1,84 @@
/**
* @RDE_HEADER@
* The LtsWebSocketClient handles a websocket connection to a server
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, NLST, 2018-03-19
*/
#pragma once
#include <QObject>
#include <QAbstractSocket>
#include <LtsWebSocket/ltswebsocketclient_export.h>
#include "client_message_factory.h"
#include "logger.h"
class QThread;
namespace LtsWebSocket
{
class WebSocketHelper;
/**
* @brief The LtsWebSocketClient class processes messages from a client connection and starts the parsing of them
*/
class LTSWEBSOCKETCLIENT_EXPORT LtsWebSocketClient
: public QObject
, public ReplyHandler
{
Q_OBJECT
public:
/**
* @brief LtsWebSocketClient Constructor
* @param messageFactory Convenience class for creating ClientMessage
* @param parent QObject parent
*/
explicit LtsWebSocketClient(AbstractClientMessageFactory *messageFactory, QObject *parent = nullptr);
~LtsWebSocketClient();
/**
* @brief open Opens a websocket with the given hostname and port
* @param hostname Hostname
* @param port Port of the server
*/
void open(const QString &hostname, const unsigned short port);
/**
* @brief close
*/
void close();
/**
* @brief addReply Sends the message to the server
* @param message Message to be send
*/
void addReply(ReplyMessage *message) override;
signals:
void connected();
void disconnected();
void dataReceived();
private slots:
/**
* @brief called when the thread finsihed parsing the message so it can be processed by the main thread
*/
void onNewClientMessage(AbstractClientMessage *clientMessage);
signals:
void onOpen(const QString &hostname, const unsigned short port);
void onClose();
void onSendTextMessage(const QString &msg);
private:
AbstractClientMessageFactory *m_messageFactory;
QThread *m_webSocketHelperThread;
WebSocketHelper *m_webSocketHelper;
LOGGER_T m_logger;
};
} // namespace LtsWebSocketClient
+125
View File
@@ -0,0 +1,125 @@
/**
* @RDE_HEADER@
* The LtsWebSocketServer class processes messages from the client
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 2016-08-25
*/
#include "lts_web_socket_server.h"
#include "connection_handler.h"
#include "client_connection.h"
#include <QTimer>
namespace LtsWebSocket
{
LtsWebSocketServer::LtsWebSocketServer(AbstractClientMessageFactory *messageFactory, QObject *parent)
: QObject(parent)
, m_queueTimer(new QTimer(this))
, m_messageFactory(messageFactory)
, m_connectionHandler(nullptr)
, m_logger(getLogger("LtsWebSocketServer"))
{
connect(m_queueTimer, &QTimer::timeout,
this, &LtsWebSocketServer::onQueueTimerTimeout);
m_queueTimer->setSingleShot(true);
}
LtsWebSocketServer::~LtsWebSocketServer() = default;
bool LtsWebSocketServer::init(const QString &serviceName, const unsigned short port)
{
m_connectionHandler.reset(new ConnectionHandler(this));
if (!m_connectionHandler->init(serviceName, port))
{
ERROR_LOG(m_logger) << "Could not init m_connectionHandler with servicename: " << serviceName << ((port == 0) ? QStringLiteral(" random port") : QStringLiteral(" port: ") + QString::number(port));
m_connectionHandler.reset();
return false;
}
connect(m_connectionHandler.get(), &ConnectionHandler::newTextMessageFromClient, this, &LtsWebSocketServer::onNewTextMessage);
connect(m_connectionHandler.get(), &ConnectionHandler::clientDisconnected, this, &LtsWebSocketServer::clientDisconnected);
connect(m_connectionHandler.get(), &ConnectionHandler::clientConnected, this, &LtsWebSocketServer::clientConnected);
m_queueTimer->start(m_messageDelay);
return true;
}
bool LtsWebSocketServer::init(const QString &serviceName, const QPair<quint16, quint16> &portRange)
{
if (portRange.first < 1025 || portRange.first > portRange.second) // "portRange.second > 65535" check is superfluous since 65535 ist max quint16
return init(serviceName);
for (quint16 currentPort = portRange.first; currentPort <= portRange.second; ++ currentPort)
{
if (init(serviceName, currentPort))
return true;
}
return init(serviceName);
}
void LtsWebSocketServer::sendToAllClients(ReplyMessage *message)
{
DEBUG_LOG(m_logger) << "Sending message to all clients: " << message->toJsonString();
m_connectionHandler->sendToAllClients(message);
}
unsigned short LtsWebSocketServer::webSocketPort() const
{
return m_connectionHandler->webSocketPort();
}
void LtsWebSocketServer::setMessageDelay(qint32 delay)
{
m_messageDelay = delay;
}
void LtsWebSocketServer::onNewTextMessage(ReplyHandler *client, const QString &message)
{
const QString pingMsg(QStringLiteral("ping"));
if (message.startsWith(pingMsg))
{
client->addReply(new PongMessage(message.mid(pingMsg.size())));
return;
}
DEBUG_LOG(m_logger) << "Got new message: " << message;
m_messageQueue.append({client, message});
if (!m_queueTimer->isActive())
m_queueTimer->start(m_messageDelay);
}
void LtsWebSocketServer::onQueueTimerTimeout()
{
if (m_messageQueue.isEmpty())
return;
MessageCredentials credentials = m_messageQueue.takeFirst();
AbstractClientMessage *clientMessage = nullptr;
auto result = m_messageFactory->parseMessage(credentials.message);
if (QJsonParseError::NoError == result.error)
clientMessage = m_messageFactory->createMessage(credentials.client, credentials.message);
else
{
WARNING_LOG(m_logger) << "Could not parse incoming message: '" << credentials.message << "', error: " << result.errorString();
clientMessage = new InvalidMessage(credentials.client, QString(), QStringLiteral("Got message which is not JSON conform"));
}
if (clientMessage)
clientMessage->process();
delete clientMessage;
m_queueTimer->start(m_messageDelay);
}
}
+120
View File
@@ -0,0 +1,120 @@
/**
* @RDE_HEADER@
* The LtsWebSocketServer class processes messages from the client
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 2016-08-25
*/
#pragma once
#include <QtCore/QObject>
#include <LtsWebSocket/ltswebsocketserver_export.h>
#include "client_message_factory.h"
#include "logger.h"
#include <memory>
#include <QQueue>
class QTimer;
namespace LtsWebSocket
{
class ConnectionHandler;
class ClientConnection;
/**
* @brief The LtsWebSocketServer class processes messages from a client connection and starts the parsing of them
*/
class LTSWEBSOCKETSERVER_EXPORT LtsWebSocketServer
: public QObject
{
Q_OBJECT
public:
/**
* @brief LtsWebSocketServer Constructor
* @param messageFactory Convenience class for creating ClientMessage
* @param parent QObject parent
*/
explicit LtsWebSocketServer(AbstractClientMessageFactory *messageFactory, QObject *parent = nullptr);
virtual ~LtsWebSocketServer();
/**
* @brief init Initialises the LtsWebSocketServer to work properly
* @param serviceName Is Used to name the server (RDE-servicename)
* @param port Port the server is listening, if 0 the port number will be random
* @return True on success, otherwise false
*/
bool init(const QString &serviceName, const unsigned short port = 0);
/**
* @brief init Initialises the LtsWebSocketServer to work properly
* @param serviceName Is Used to name the server (RDE-servicename)
* @param portRange Range where to look at for a port. If invalid (valid range is 1025-65535) a random port is chosen
* @return True on success, otherwise false
*/
bool init(const QString &serviceName, const QPair<quint16, quint16> &portRange);
/**
* @brief sendToAll Sends the given msg to all connected Clients
* @param message Message to be send
*/
void sendToAllClients(ReplyMessage *message);
/**
* @brief webSocketPort returns the listen port
* @return listen port
*/
unsigned short webSocketPort() const;
/**
* @brief setMessageDelay Sets the delay between two messages to be processed
* @param delay Delay in milliseconds
*/
void setMessageDelay(qint32 delay);
signals:
/**
* @brief disconnected Emitted when a client disconnects
* @param client The client
*/
void clientDisconnected(ClientConnection *client);
/**
* @brief clientConnected Emitted when a client connects
* @param client The client
*/
void clientConnected(ClientConnection *client);
private slots:
/**
* @brief onNewTextMessage Slot to react to new messages from clients
* @param client Client to handle a reply
* @param message Message from a client
*/
void onNewTextMessage(ReplyHandler *client, const QString &message);
/**
* @brief onQueueTimerTimeout Timeout for messageprocessing
*/
void onQueueTimerTimeout();
private:
struct MessageCredentials
{
ReplyHandler *client {nullptr};
QString message;
MessageCredentials(ReplyHandler *client, const QString &message)
: client(client)
, message(message)
{}
};
QQueue<MessageCredentials> m_messageQueue;
QTimer *m_queueTimer { nullptr };
AbstractClientMessageFactory *m_messageFactory { nullptr };
std::unique_ptr<ConnectionHandler> m_connectionHandler;
qint32 m_messageDelay { 0 };
LOGGER_T m_logger;
};
} // namespace LtsWebSocket
+26
View File
@@ -0,0 +1,26 @@
/**
* @RDE_HEADER@
* The MessageTransmitter Interface offers an extremely lightweight API for a class to just write textmessages to
* $Id$
* $HeadURL$
* $Author$
* Created by Hannes Baeuerle, SLSE, 2016-08-25
*/
namespace LtsWebSocketServer
{
class ReplyMessage;
/**
* @brief The MessageHandler Interface offers an extremely lightweight API for a class to just write textmessages to
*/
class MessageHandler
{
public:
/**
* @brief writeTextMessage Method to write a textmessage via this MessageHandler
* @param message Message to send
*/
virtual void addMessage(ReplyMessage *message) = 0;
};
} // namespace LtsWebSocket
+47
View File
@@ -0,0 +1,47 @@
/**
* @RDE_HEADER@
* LTS web socket server - Abstract Reply Message and Reply Handler
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#include "reply_message.h"
#include "json_keys.h"
#include <QUuid>
namespace LtsWebSocket
{
// ######## ReplyMessage begin ########
QString ReplyMessage::toJsonString() const
{
return QString::fromUtf8(QJsonDocument(m_jsonRepresentation).toJson(QJsonDocument::Compact));
}
ReplyMessage::ReplyMessage(const QString &type, const int typeId, const QString &uuid)
{
m_jsonRepresentation.insert(JsonKeys::TYPE(), type);
m_jsonRepresentation.insert(JsonKeys::TYPEID(), typeId);
if (!uuid.isEmpty())
m_jsonRepresentation.insert(JsonKeys::UUID_(), uuid);
}
// ######## ReplyMessage end ########
// ######## ErrorMessage begin ########
ErrorMessage::ErrorMessage(const ErrorMessage::ErrorData &data)
: ReplyMessage(JsonKeys::ERROR_(), -1, QUuid::createUuid().toString())
{
m_jsonRepresentation.insert(JsonKeys::DATA(),
QJsonObject({{JsonKeys::UUID_(), data.uuid},
{JsonKeys::ERROR_CLASS(), data.errorClass},
{JsonKeys::ERROR_MESSAGE(), data.errorMessage}
}));
}
// ######## ErrorMessage end ########
} // namespace LtsWebSocketServer
+87
View File
@@ -0,0 +1,87 @@
/**
* @RDE_HEADER@
* LTS web socket server - Abstract Reply Message and Reply Handler
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Dassow, SLST, 2017-11-28
*/
#pragma once
#include <LtsWebSocket/ltswebsocketcommon_export.h>
#include <QString>
#include <QJsonObject>
#include <QJsonParseError>
namespace LtsWebSocket
{
/**
* @brief Base class for reply messages
*/
class LTSWEBSOCKETCOMMON_EXPORT ReplyMessage
{
public:
/**
* @brief ~ReplyMessage Destructor
*/
virtual ~ReplyMessage() = default;
/**
* @brief toJsonString Converts this message into a valid JSON string
* @return QString JSON representation of this class
*/
virtual QString toJsonString() const;
QJsonObject json() const { return m_jsonRepresentation; }
protected: // methods
ReplyMessage() = default;
ReplyMessage(const QString &type, const int typeId, const QString &uuid);
protected: // attributes
QJsonObject m_jsonRepresentation;
}; // class ReplyMessage
/**
* @brief The ReplyHandler Interface offers an extremely lightweight API for a class to just write textmessages to
*/
class LTSWEBSOCKETCOMMON_EXPORT ReplyHandler
{
public:
virtual ~ReplyHandler() = default;
/**
* @brief addReply Method to write a textmessage via this MessageTransmitter
* @param message Message to send
*/
virtual void addReply(ReplyMessage *message) = 0;
}; // class ReplyHandler
// #################### basic error messages ####################
/**
* @brief The ErrorMessage class is sent when any data from a client could not be processed
*/
class LTSWEBSOCKETCOMMON_EXPORT ErrorMessage final
: public ReplyMessage
{
public:
/**
* @brief The ErrorData struct represents the data part of an ErrorMessage object
*/
struct ErrorData
{
int errorClass = 0;
QString uuid;
QString errorMessage;
};
/**
* @brief ErrorMessage Constructor
* @param data Data part of an ErrorMessage object
*/
explicit ErrorMessage(const ErrorData &data);
}; // class ErrorMessage
} // namespace LtsWebSocket