/** * @file MessageServer.cpp * @brief Implementation of the external client message server */ #include "MessageServer.h" #include #include Q_DECLARE_LOGGING_CATEGORY(messageserver_js8) MessageServer::MessageServer(QObject *parent) : QTcpServer(parent) {} MessageServer::~MessageServer() { stop(); } bool MessageServer::start() { if (isListening()) { qCDebug(messageserver_js8) << "MessageServer already listening:" << m_host << m_port; return false; } auto address = QHostAddress(); if (m_host.isEmpty() || !address.setAddress(m_host)) { qCDebug(messageserver_js8) << "MessageServer address invalid:" << m_host << m_port; return false; } if (m_port <= 0) { qCDebug(messageserver_js8) << "MessageServer port invalid:" << m_host << m_port; return false; } bool listening = listen(address, m_port); qCDebug(messageserver_js8) << "MessageServer listening:" << listening << m_host << m_port; return listening; } void MessageServer::stop() { // disconnect all clients foreach (auto client, m_clients) { client->close(); } // then close the server close(); } void MessageServer::setServer(QString host, quint16 port) { bool listening = isListening(); if (listening && (m_host != host || m_port != port)) { stop(); } m_host = host; m_port = port; if (listening) { start(); } } void MessageServer::setPause(bool paused) { m_paused = paused; if (paused) { pauseAccepting(); } else { resumeAccepting(); } } void MessageServer::setMaxConnections(int n) { // set the maximum number of connections allowed m_maxConnections = n; // then, prune old ones greater than the max (fifo) pruneConnections(); } int MessageServer::activeConnections() { int i = 0; foreach (auto client, m_clients) { if (client->isConnected()) i++; } return i; } void MessageServer::pruneConnections() { // keep only the n most recent connections (fifo) if (m_maxConnections && m_maxConnections < activeConnections()) { for (int i = m_maxConnections; i < activeConnections(); i++) { auto client = m_clients.first(); client->close(); m_clients.removeFirst(); } } } void MessageServer::send(const Message &message) { foreach (auto client, m_clients) { if (!client->awaitingResponse(message.id())) { continue; } client->send(message); } } void MessageServer::incomingConnection(qintptr handle) { qCDebug(messageserver_js8) << "MessageServer incomingConnection" << handle; auto client = new Client(this, this); client->setSocket(handle); #if JS8_MESSAGESERVER_IS_SINGLE_CLIENT while (!m_clients.isEmpty()) { auto client = m_clients.first(); client->close(); m_clients.removeFirst(); } #endif if (m_maxConnections && m_maxConnections <= activeConnections()) { qCDebug(messageserver_js8) << "MessageServer connections full, dropping incoming connection"; client->send(Message("API.ERROR", "Connections Full")); client->close(); return; } m_clients.append(client); } Client::Client(MessageServer *server, QObject *parent) : QObject(parent), m_server{server} { setConnected(true); } void Client::setSocket(qintptr handle) { m_socket = new QTcpSocket(this); connect(m_socket, &QTcpSocket::disconnected, this, &Client::onDisconnected); connect(m_socket, &QTcpSocket::readyRead, this, &Client::readyRead); m_socket->setSocketDescriptor(handle); } void Client::setConnected(bool connected) { m_connected = connected; } void Client::close() { if (!m_socket) { return; } m_socket->close(); m_socket = nullptr; } void Client::send(const Message &message) { if (!isConnected()) { return; } if (!m_socket) { return; } if (!m_socket->isOpen()) { qCDebug(messageserver_js8) << "client socket isn't open"; return; } qCDebug(messageserver_js8) << "client writing" << message.toJson(); m_socket->write(message.toJson()); m_socket->write("\n"); m_socket->flush(); // remove if needed if (m_requests.contains(message.id())) { m_requests.remove(message.id()); } } void Client::onDisconnected() { qCDebug(messageserver_js8) << "MessageServer client disconnected"; setConnected(false); } void Client::readyRead() { qCDebug(messageserver_js8) << "MessageServer client readyRead"; while (m_socket->canReadLine()) { auto const msg = m_socket->readLine().trimmed(); qCDebug(messageserver_js8) << "-> Client" << m_socket->socketDescriptor() << msg; if (msg.isEmpty()) return; try { auto m = Message::fromJson(msg); m_requests[m.ensureId()] = m; emit m_server->message(m); } catch (std::exception const &e) { send({"API.ERROR", e.what()}); return; } } } Q_LOGGING_CATEGORY(messageserver_js8, "messageserver.js8", QtWarningMsg)