Client Websockets (#3545)

* Websockets

* Add setting to get websocket IP from header

* Add QT version guard

* Minor cleanup

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* - Make QWebSocket required
- Remove QWEBSOCEKT_LIB guards
- Only TCP on port 4747
- Fix peerName lookup

* fix check

Signed-off-by: Zach Halpern <ZaHalpern+github@gmail.com>

* Update CMakeLists.txt

* Update CMakeLists.txt
This commit is contained in:
Rob Blanckaert 2019-02-03 02:43:22 -08:00 committed by ctrlaltca
parent 5e38214675
commit 9a8c81cf5e
8 changed files with 130 additions and 77 deletions

View file

@ -151,8 +151,8 @@ if(APPLE)
ENDIF(APPLE)
# Qt5
find_package(Qt5 COMPONENTS Concurrent Multimedia Network PrintSupport Svg Widgets REQUIRED)
set(COCKATRICE_QT_MODULES Qt5::Concurrent Qt5::Multimedia Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Widgets)
find_package(Qt5 COMPONENTS Concurrent Multimedia Network PrintSupport Svg WebSockets Widgets REQUIRED)
set(COCKATRICE_QT_MODULES Qt5::Concurrent Qt5::Multimedia Qt5::Network Qt5::PrintSupport Qt5::Svg Qt5::Widgets Qt5::WebSockets)
# Qt5LinguistTools
find_package(Qt5LinguistTools)

View file

@ -18,12 +18,13 @@
#include <QList>
#include <QThread>
#include <QTimer>
#include <QWebSocket>
static const unsigned int protocolVersion = 14;
RemoteClient::RemoteClient(QObject *parent)
: AbstractClient(parent), timeRunning(0), lastDataReceived(0), messageInProgress(false), handshakeStarted(false),
messageLength(0)
usingWebSocket(false), messageLength(0)
{
clearNewClientFeatures();
@ -38,6 +39,13 @@ RemoteClient::RemoteClient(QObject *parent)
connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(slotSocketError(QAbstractSocket::SocketError)));
websocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this);
connect(websocket, &QWebSocket::binaryMessageReceived, this, &RemoteClient::websocketMessageReceived);
connect(websocket, &QWebSocket::connected, this, &RemoteClient::slotConnected);
connect(websocket, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(slotWebSocketError(QAbstractSocket::SocketError)));
connect(this, SIGNAL(serverIdentificationEventReceived(const Event_ServerIdentification &)), this,
SLOT(processServerIdentificationEvent(const Event_ServerIdentification &)));
connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this,
@ -69,15 +77,25 @@ void RemoteClient::slotSocketError(QAbstractSocket::SocketError /*error*/)
emit socketError(errorString);
}
void RemoteClient::slotWebSocketError(QAbstractSocket::SocketError /*error*/)
{
QString errorString = websocket->errorString();
doDisconnectFromServer();
emit socketError(errorString);
}
void RemoteClient::slotConnected()
{
timeRunning = lastDataReceived = 0;
timer->start();
if (!usingWebSocket) {
// dirty hack to be compatible with v14 server
sendCommandContainer(CommandContainer());
getNewCmdId();
// end of hack
}
}
void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event)
@ -217,7 +235,7 @@ void RemoteClient::loginResponse(const Response &response)
missingFeatures << QString::fromStdString(resp.missing_features(i));
}
emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
resp.denied_end_time(), missingFeatures);
static_cast<quint32>(resp.denied_end_time()), missingFeatures);
setStatus(StatusDisconnecting);
}
}
@ -236,7 +254,7 @@ void RemoteClient::registerResponse(const Response &response)
break;
default:
emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()),
resp.denied_end_time());
static_cast<quint32>(resp.denied_end_time()));
setStatus(StatusDisconnecting);
doDisconnectFromServer();
break;
@ -301,13 +319,31 @@ void RemoteClient::readData()
} while (!inputBuffer.isEmpty());
}
void RemoteClient::websocketMessageReceived(const QByteArray &message)
{
lastDataReceived = timeRunning;
ServerMessage newServerMessage;
newServerMessage.ParseFromArray(message.data(), message.length());
#ifdef QT_DEBUG
qDebug() << "IN" << messageLength << QString::fromStdString(newServerMessage.ShortDebugString());
#endif
processProtocolItem(newServerMessage);
}
void RemoteClient::sendCommandContainer(const CommandContainer &cont)
{
QByteArray buf;
unsigned int size = cont.ByteSize();
auto size = static_cast<unsigned int>(cont.ByteSize());
#ifdef QT_DEBUG
qDebug() << "OUT" << size << QString::fromStdString(cont.ShortDebugString());
#endif
QByteArray buf;
if (usingWebSocket) {
buf.resize(size);
cont.SerializeToArray(buf.data(), size);
websocket->sendBinaryMessage(buf);
} else {
buf.resize(size + 4);
cont.SerializeToArray(buf.data() + 4, size);
buf.data()[3] = (unsigned char)size;
@ -316,6 +352,18 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont)
buf.data()[0] = (unsigned char)(size >> 24);
socket->write(buf);
}
}
void RemoteClient::connectToHost(const QString &hostname, unsigned int port)
{
usingWebSocket = port == 443 || port == 80 || port == 4748 || port == 8080;
if (usingWebSocket) {
QUrl url(QString("%1://%2:%3").arg(port == 443 ? "wss" : "ws").arg(hostname).arg(port));
websocket->open(url);
} else {
socket->connectToHost(hostname, static_cast<quint16>(port));
}
}
void RemoteClient::doConnectToServer(const QString &hostname,
@ -330,7 +378,7 @@ void RemoteClient::doConnectToServer(const QString &hostname,
lastHostname = hostname;
lastPort = port;
socket->connectToHost(hostname, port);
connectToHost(hostname, port);
setStatus(StatusConnecting);
}
@ -354,7 +402,7 @@ void RemoteClient::doRegisterToServer(const QString &hostname,
lastHostname = hostname;
lastPort = port;
socket->connectToHost(hostname, port);
connectToHost(hostname, port);
setStatus(StatusRegistering);
}
@ -364,7 +412,7 @@ void RemoteClient::doActivateToServer(const QString &_token)
token = _token;
socket->connectToHost(lastHostname, lastPort);
connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusActivating);
}
@ -377,17 +425,19 @@ void RemoteClient::doDisconnectFromServer()
messageLength = 0;
QList<PendingCommand *> pc = pendingCommands.values();
for (int i = 0; i < pc.size(); i++) {
for (const auto &i : pc) {
Response response;
response.set_response_code(Response::RespNotConnected);
response.set_cmd_id(pc[i]->getCommandContainer().cmd_id());
pc[i]->processResponse(response);
response.set_cmd_id(i->getCommandContainer().cmd_id());
i->processResponse(response);
delete pc[i];
delete i;
}
pendingCommands.clear();
setStatus(StatusDisconnected);
if (websocket->isValid())
websocket->close();
socket->close();
}
@ -508,7 +558,7 @@ void RemoteClient::doRequestForgotPasswordToServer(const QString &hostname, unsi
lastHostname = hostname;
lastPort = port;
socket->connectToHost(lastHostname, lastPort);
connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusRequestingForgotPassword);
}
@ -540,7 +590,7 @@ void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname,
token = _token;
password = _newpassword;
socket->connectToHost(lastHostname, lastPort);
connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusSubmitForgotPasswordReset);
}
@ -574,7 +624,7 @@ void RemoteClient::doSubmitForgotPasswordChallengeToServer(const QString &hostna
lastPort = port;
email = _email;
socket->connectToHost(lastHostname, lastPort);
connectToHost(lastHostname, static_cast<unsigned int>(lastPort));
setStatus(StatusSubmitForgotPasswordChallenge);
}

View file

@ -3,6 +3,7 @@
#include "abstractclient.h"
#include <QTcpSocket>
#include <QWebSocket>
class QTimer;
@ -17,7 +18,6 @@ signals:
void activateError();
void socketError(const QString &errorString);
void protocolVersionMismatch(int clientVersion, int serverVersion);
void protocolError();
void
sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
void sigRegisterToServer(const QString &hostname,
@ -25,7 +25,7 @@ signals:
const QString &_userName,
const QString &_password,
const QString &_email,
const int _gender,
int _gender,
const QString &_country,
const QString &_realname);
void sigActivateToServer(const QString &_token);
@ -48,7 +48,9 @@ signals:
private slots:
void slotConnected();
void readData();
void websocketMessageReceived(const QByteArray &message);
void slotSocketError(QAbstractSocket::SocketError error);
void slotWebSocketError(QAbstractSocket::SocketError error);
void ping();
void processServerIdentificationEvent(const Event_ServerIdentification &event);
void processConnectionClosedEvent(const Event_ConnectionClosed &event);
@ -62,7 +64,7 @@ private slots:
const QString &_userName,
const QString &_password,
const QString &_email,
const int _gender,
int _gender,
const QString &_country,
const QString &_realname);
void doLogin();
@ -85,29 +87,36 @@ private slots:
private:
static const int maxTimeout = 10;
int timeRunning, lastDataReceived;
QByteArray inputBuffer;
bool messageInProgress;
bool handshakeStarted;
bool newMissingFeatureFound(QString _serversMissingFeatures);
void clearNewClientFeatures();
bool usingWebSocket;
int messageLength;
QTimer *timer;
QTcpSocket *socket;
QWebSocket *websocket;
QString lastHostname;
int lastPort;
QString getSrvClientID(const QString _hostname);
QString getSrvClientID(QString _hostname);
bool newMissingFeatureFound(QString _serversMissingFeatures);
void clearNewClientFeatures();
void connectToHost(const QString &hostname, unsigned int port);
protected slots:
void sendCommandContainer(const CommandContainer &cont);
void sendCommandContainer(const CommandContainer &cont) override;
public:
RemoteClient(QObject *parent = 0);
~RemoteClient();
explicit RemoteClient(QObject *parent = nullptr);
~RemoteClient() override;
QString peerName() const
{
if (usingWebSocket) {
return websocket->peerName();
} else {
return socket->peerName();
}
}
void
connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password);
void registerToServer(const QString &hostname,
@ -115,7 +124,7 @@ public:
const QString &_userName,
const QString &_password,
const QString &_email,
const int _gender,
int _gender,
const QString &_country,
const QString &_realname);
void activateToServer(const QString &_token);

View file

@ -43,16 +43,8 @@ if(APPLE)
ENDIF(APPLE)
# Qt5
find_package(Qt5 COMPONENTS Network Sql REQUIRED)
set(SERVATRICE_QT_MODULES Qt5::Core Qt5::Network Qt5::Sql)
# Qt Websockets
find_package(Qt5WebSockets)
if(Qt5WebSockets_FOUND)
list(APPEND SERVATRICE_QT_MODULES Qt5::WebSockets)
else()
MESSAGE(WARNING "Qt5 websocket module not found")
endif()
find_package(Qt5 COMPONENTS Network Sql WebSockets REQUIRED)
set(SERVATRICE_QT_MODULES Qt5::Core Qt5::Network Qt5::Sql Qt5::WebSockets)
QT5_ADD_RESOURCES(servatrice_RESOURCES_RCC ${servatrice_RESOURCES})
SET(QT_DONT_USE_QTGUI TRUE)

View file

@ -104,7 +104,6 @@ Servatrice_ConnectionPool *Servatrice_GameServer::findLeastUsedConnectionPool()
return connectionPools[poolIndex];
}
#ifdef QT_WEBSOCKETS_LIB
#define WEBSOCKET_POOL_NUMBER 999
Servatrice_WebsocketGameServer::Servatrice_WebsocketGameServer(Servatrice *_server,
@ -163,7 +162,6 @@ Servatrice_ConnectionPool *Servatrice_WebsocketGameServer::findLeastUsedConnecti
qDebug() << "Pool utilisation:" << debugStr;
return connectionPools[poolIndex];
}
#endif
void Servatrice_IslServer::incomingConnection(qintptr socketDescriptor)
{
@ -431,7 +429,6 @@ bool Servatrice::initServer()
}
}
#ifdef QT_WEBSOCKETS_LIB
// WEBSOCKET SERVER
if (getNumberOfWebSocketPools() > 0) {
websocketGameServer = new Servatrice_WebsocketGameServer(this, getNumberOfWebSocketPools(),
@ -447,7 +444,6 @@ bool Servatrice::initServer()
return false;
}
}
#endif
if (getIdleClientTimeout() > 0) {
qDebug() << "Idle client timeout value: " << getIdleClientTimeout();

View file

@ -20,10 +20,6 @@
#ifndef SERVATRICE_H
#define SERVATRICE_H
#include <QTcpServer>
#ifdef QT_WEBSOCKETS_LIB
#include <QWebSocketServer>
#endif
#include "server.h"
#include <QHostAddress>
#include <QMetaType>
@ -32,6 +28,8 @@
#include <QSqlDatabase>
#include <QSslCertificate>
#include <QSslKey>
#include <QTcpServer>
#include <QWebSocketServer>
#include <utility>
Q_DECLARE_METATYPE(QSqlDatabase)
@ -66,7 +64,6 @@ protected:
Servatrice_ConnectionPool *findLeastUsedConnectionPool();
};
#ifdef QT_WEBSOCKETS_LIB
class Servatrice_WebsocketGameServer : public QWebSocketServer
{
Q_OBJECT
@ -86,7 +83,6 @@ protected:
protected slots:
void onNewConnection();
};
#endif
class Servatrice_IslServer : public QTcpServer
{
@ -158,9 +154,7 @@ private:
DatabaseType databaseType;
QTimer *pingClock, *statusUpdateClock;
Servatrice_GameServer *gameServer;
#ifdef QT_WEBSOCKETS_LIB
Servatrice_WebsocketGameServer *websocketGameServer;
#endif
Servatrice_IslServer *islServer;
mutable QMutex loginMessageMutex;
QString loginMessage;

View file

@ -1037,8 +1037,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C
return Response::RespUserAlreadyExists;
}
if (servatrice->getMaxAccountsPerEmail() &&
!(sqlInterface->checkNumberOfUserAccounts(emailAddress) < servatrice->getMaxAccountsPerEmail())) {
if (servatrice->getMaxAccountsPerEmail() > 0 &&
sqlInterface->checkNumberOfUserAccounts(emailAddress) >= servatrice->getMaxAccountsPerEmail()) {
if (servatrice->getEnableRegistrationAudit())
sqlInterface->addAuditRecord(QString::fromStdString(cmd.user_name()).simplified(), this->getAddress(),
QString::fromStdString(cmd.clientid()).simplified(), "REGISTER_ACCOUNT",
@ -1629,7 +1629,6 @@ bool TcpServerSocketInterface::initTcpSession()
return true;
}
#ifdef QT_WEBSOCKETS_LIB
WebsocketServerSocketInterface::WebsocketServerSocketInterface(Servatrice *_server,
Servatrice_DatabaseInterface *_databaseInterface,
QObject *parent)
@ -1647,6 +1646,22 @@ WebsocketServerSocketInterface::~WebsocketServerSocketInterface()
void WebsocketServerSocketInterface::initConnection(void *_socket)
{
socket = (QWebSocket *)_socket;
address = socket->peerAddress();
QByteArray websocketIPHeader = settingsCache->value("server/web_socket_ip_header", "").toByteArray();
if (websocketIPHeader.length() > 0) {
#if QT_VERSION >= 0x050600
if (socket->request().hasRawHeader(websocketIPHeader)) {
QString header(socket->request().rawHeader(websocketIPHeader));
QHostAddress parsed(header);
if (!parsed.isNull())
address = parsed;
}
#else
logger->logMessage(QString("Reading the websocket IP header is unsupported on this version of QT."));
#endif
}
connect(socket, SIGNAL(binaryMessageReceived(const QByteArray &)), this,
SLOT(binaryMessageReceived(const QByteArray &)));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
@ -1656,7 +1671,9 @@ void WebsocketServerSocketInterface::initConnection(void *_socket)
// Otherwise, in case a of a socket error, it could be removed from the list before it is added.
server->addClient(this);
logger->logMessage(QString("Incoming websocket connection: %1").arg(socket->peerAddress().toString()), this);
logger->logMessage(
QString("Incoming websocket connection: %1 (%2)").arg(address.toString()).arg(socket->peerAddress().toString()),
this);
if (!initWebsocketSession())
prepareDestroy();
@ -1746,5 +1763,3 @@ void WebsocketServerSocketInterface::binaryMessageReceived(const QByteArray &mes
processCommandContainer(newCommandContainer);
}
#endif

View file

@ -20,13 +20,11 @@
#ifndef SERVERSOCKETINTERFACE_H
#define SERVERSOCKETINTERFACE_H
#include <QTcpSocket>
#ifdef QT_WEBSOCKETS_LIB
#include <QWebSocket>
#endif
#include "server_protocolhandler.h"
#include <QHostAddress>
#include <QMutex>
#include <QTcpSocket>
#include <QWebSocket>
class Servatrice;
class Servatrice_DatabaseInterface;
@ -181,7 +179,6 @@ public slots:
void initConnection(int socketDescriptor);
};
#ifdef QT_WEBSOCKETS_LIB
class WebsocketServerSocketInterface : public AbstractServerSocketInterface
{
Q_OBJECT
@ -193,11 +190,11 @@ public:
QHostAddress getPeerAddress() const
{
return socket->peerAddress();
return address;
}
QString getAddress() const
{
return socket->peerAddress().toString();
return address.toString();
}
QString getConnectionType() const
{
@ -206,6 +203,7 @@ public:
private:
QWebSocket *socket;
QHostAddress address;
protected:
void writeToSocket(QByteArray &data)
@ -223,6 +221,5 @@ protected slots:
public slots:
void initConnection(void *_socket);
};
#endif
#endif