ApiServerLocal run on it's own thread to avoid stopping UI

ApiLocalConnectionHandler can now handle both buffered (QLocalSocket
based) and unbuffered (like ncat or simple scripts) clients
This commit is contained in:
Gio 2016-08-08 14:02:01 +02:00
parent 70228ee405
commit 183ac22aba
2 changed files with 121 additions and 48 deletions

View File

@ -1,12 +1,39 @@
/*
* libresapi local socket server
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ApiServerLocal.h"
#include "JsonStream.h"
namespace resource_api{
ApiServerLocal::ApiServerLocal(ApiServer* server) : QThread(),
mApiServer(server), mLocalServer(this)
ApiServerLocal::ApiServerLocal(ApiServer* server, QObject *parent) :
QObject(parent), serverThread(this),
localListener(server) // Must have no parent to be movable to other thread
{
localListener.moveToThread(&serverThread);
serverThread.start();
}
ApiServerLocal::~ApiServerLocal() { serverThread.quit(); }
ApiLocalListener::ApiLocalListener(ApiServer *server, QObject *parent) :
QObject(parent), mApiServer(server), mLocalServer(this)
{
start();
mLocalServer.removeServer(serverName());
#if QT_VERSION >= 0x050000
mLocalServer.setSocketOptions(QLocalServer::UserAccessOption);
@ -15,56 +42,72 @@ ApiServerLocal::ApiServerLocal(ApiServer* server) : QThread(),
mLocalServer.listen(serverName());
}
ApiServerLocal::~ApiServerLocal()
void ApiLocalListener::handleConnection()
{
mLocalServer.close();
quit();
new ApiLocalConnectionHandler(mApiServer,
mLocalServer.nextPendingConnection(), this);
}
void ApiServerLocal::handleConnection()
ApiLocalConnectionHandler::ApiLocalConnectionHandler(
ApiServer* apiServer, QLocalSocket* sock, QObject *parent) :
QObject(parent), mApiServer(apiServer), mLocalSocket(sock),
mState(WAITING_PATH)
{
new ApiLocalConnectionHandler(mApiServer, mLocalServer.nextPendingConnection());
connect(mLocalSocket, SIGNAL(disconnected()), this, SLOT(deleteLater()));
connect(sock, SIGNAL(readyRead()), this, SLOT(handlePendingRequests()));
}
ApiLocalConnectionHandler::ApiLocalConnectionHandler(ApiServer* apiServer, QLocalSocket* sock) :
QThread(), mApiServer(apiServer), mLocalSocket(sock)
ApiLocalConnectionHandler::~ApiLocalConnectionHandler()
{
sock->moveToThread(this);
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
connect(mLocalSocket, SIGNAL(disconnected()), this, SLOT(quit()));
connect(sock, SIGNAL(readyRead()), this, SLOT(handleRequest()));
start();
mLocalSocket->close();
delete mLocalSocket;
}
void ApiLocalConnectionHandler::handleRequest()
void ApiLocalConnectionHandler::handlePendingRequests()
{
char path[1024];
if(mLocalSocket->readLine(path, 1023) > 0)
switch(mState)
{
reqPath = path;
char jsonData[20480];
if(mLocalSocket->read(jsonData, 20479) > 0)
case WAITING_PATH:
{
if(mLocalSocket->canReadLine())
{
readPath:
reqPath = mLocalSocket->readLine().constData();
mState = WAITING_DATA;
/* Because QLocalSocket is SOCK_STREAM some clients implementations
* like the one based on QLocalSocket feel free to send the whole
* request (PATH + DATA) in a single write(), causing readyRead()
* signal being emitted only once, in that case we should continue
* processing without waiting for readyRead() being fired again, so
* we don't break here as there may be more lines to read */
}
}
case WAITING_DATA:
{
if(mLocalSocket->canReadLine())
{
resource_api::JsonStream reqJson;
reqJson.setJsonString(std::string(jsonData));
reqJson.setJsonString(std::string(mLocalSocket->readLine().constData()));
resource_api::Request req(reqJson);
req.setPath(reqPath);
std::string resultString = mApiServer->handleRequest(req);
mLocalSocket->write(resultString.c_str(), resultString.length());
mLocalSocket->write("\n\0");
mState = WAITING_PATH;
/* Because QLocalSocket is SOCK_STREAM some clients implementations
* like the one based on QLocalSocket feel free to coalesce multiple
* upper level write() into a single socket write(), causing
* readyRead() signal being emitted only once, in that case we should
* keep processing without waiting for readyRead() being fired again */
if(mLocalSocket->canReadLine()) goto readPath;
// Now there are no more requests to process we can break
break;
}
else mLocalSocket->write("\"{\"data\":null,\"debug_msg\":\"ApiLocalConnectionHandler::handleRequest() Error: timeout waiting for path.\\n\",\"returncode\":\"fail\"}\"\n\0");
}
else mLocalSocket->write("{\"data\":null,\"debug_msg\":\"ApiLocalConnectionHandler::handleRequest() Error: timeout waiting for JSON data.\\n\",\"returncode\":\"fail\"}\"\n\0");
quit();
}
ApiLocalConnectionHandler::~ApiLocalConnectionHandler()
{
mLocalSocket->flush();
mLocalSocket->close();
mLocalSocket->deleteLater();
}
}
} // namespace resource_api

View File

@ -1,4 +1,21 @@
#pragma once
/*
* libresapi local socket server
* Copyright (C) 2016 Gioacchino Mazzurco <gio@eigenlab.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QLocalServer>
#include <QString>
@ -11,22 +28,14 @@
namespace resource_api
{
class ApiServer;
class ApiServerLocal : public QThread
class ApiLocalListener : public QObject
{
Q_OBJECT
public:
ApiServerLocal(ApiServer* server);
~ApiServerLocal();
public slots:
void handleConnection();
private:
ApiServer* mApiServer;
QLocalServer mLocalServer;
ApiLocalListener(ApiServer* server, QObject *parent=0);
~ApiLocalListener() { mLocalServer.close(); }
const static QString& serverName()
{
@ -34,24 +43,45 @@ private:
.append("/libresapi.sock").c_str());
return sockPath;
}
public slots:
void handleConnection();
private:
ApiServer* mApiServer;
QLocalServer mLocalServer;
};
class ApiLocalConnectionHandler : public QThread
class ApiServerLocal : public QObject
{
Q_OBJECT
public:
ApiLocalConnectionHandler(ApiServer* apiServer, QLocalSocket* sock);
ApiServerLocal(ApiServer* server, QObject *parent=0);
~ApiServerLocal();
private:
QThread serverThread;
ApiLocalListener localListener;
};
class ApiLocalConnectionHandler : public QObject
{
Q_OBJECT
public:
ApiLocalConnectionHandler(ApiServer* apiServer, QLocalSocket* sock, QObject *parent = 0);
~ApiLocalConnectionHandler();
enum State {WAITING_PATH, WAITING_DATA};
public slots:
void handleRequest();
void handlePendingRequests();
private:
ApiServer* mApiServer;
QLocalSocket* mLocalSocket;
State mState;
std::string reqPath;
void _die();
};
} // namespace resource_api