Merge pull request #464 from G10h4ck/libresapilocal

ApiServerLocal run on it's own thread to avoid stopping UI
This commit is contained in:
Cyril Soler 2016-08-08 20:14:05 +02:00 committed by GitHub
commit c0c7ede97b
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 "ApiServerLocal.h"
#include "JsonStream.h" #include "JsonStream.h"
namespace resource_api{ namespace resource_api{
ApiServerLocal::ApiServerLocal(ApiServer* server) : QThread(), ApiServerLocal::ApiServerLocal(ApiServer* server, QObject *parent) :
mApiServer(server), mLocalServer(this) 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()); mLocalServer.removeServer(serverName());
#if QT_VERSION >= 0x050000 #if QT_VERSION >= 0x050000
mLocalServer.setSocketOptions(QLocalServer::UserAccessOption); mLocalServer.setSocketOptions(QLocalServer::UserAccessOption);
@ -15,56 +42,72 @@ ApiServerLocal::ApiServerLocal(ApiServer* server) : QThread(),
mLocalServer.listen(serverName()); mLocalServer.listen(serverName());
} }
ApiServerLocal::~ApiServerLocal() void ApiLocalListener::handleConnection()
{ {
mLocalServer.close(); new ApiLocalConnectionHandler(mApiServer,
quit(); 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) : ApiLocalConnectionHandler::~ApiLocalConnectionHandler()
QThread(), mApiServer(apiServer), mLocalSocket(sock)
{ {
sock->moveToThread(this); mLocalSocket->close();
connect(this, SIGNAL(finished()), this, SLOT(deleteLater())); delete mLocalSocket;
connect(mLocalSocket, SIGNAL(disconnected()), this, SLOT(quit()));
connect(sock, SIGNAL(readyRead()), this, SLOT(handleRequest()));
start();
} }
void ApiLocalConnectionHandler::handleRequest() void ApiLocalConnectionHandler::handlePendingRequests()
{ {
char path[1024]; switch(mState)
if(mLocalSocket->readLine(path, 1023) > 0)
{ {
reqPath = path; case WAITING_PATH:
char jsonData[20480]; {
if(mLocalSocket->read(jsonData, 20479) > 0) 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; resource_api::JsonStream reqJson;
reqJson.setJsonString(std::string(jsonData)); reqJson.setJsonString(std::string(mLocalSocket->readLine().constData()));
resource_api::Request req(reqJson); resource_api::Request req(reqJson);
req.setPath(reqPath); req.setPath(reqPath);
std::string resultString = mApiServer->handleRequest(req); std::string resultString = mApiServer->handleRequest(req);
mLocalSocket->write(resultString.c_str(), resultString.length()); mLocalSocket->write(resultString.c_str(), resultString.length());
mLocalSocket->write("\n\0"); 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 } // namespace resource_api

View file

@ -1,4 +1,21 @@
#pragma once #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 <QLocalServer>
#include <QString> #include <QString>
@ -11,22 +28,14 @@
namespace resource_api namespace resource_api
{ {
class ApiServer;
class ApiServerLocal : public QThread class ApiLocalListener : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
ApiServerLocal(ApiServer* server); ApiLocalListener(ApiServer* server, QObject *parent=0);
~ApiServerLocal(); ~ApiLocalListener() { mLocalServer.close(); }
public slots:
void handleConnection();
private:
ApiServer* mApiServer;
QLocalServer mLocalServer;
const static QString& serverName() const static QString& serverName()
{ {
@ -34,24 +43,45 @@ private:
.append("/libresapi.sock").c_str()); .append("/libresapi.sock").c_str());
return sockPath; return sockPath;
} }
public slots:
void handleConnection();
private:
ApiServer* mApiServer;
QLocalServer mLocalServer;
}; };
class ApiLocalConnectionHandler : public QThread class ApiServerLocal : public QObject
{ {
Q_OBJECT Q_OBJECT
public: 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(); ~ApiLocalConnectionHandler();
enum State {WAITING_PATH, WAITING_DATA};
public slots: public slots:
void handleRequest(); void handlePendingRequests();
private: private:
ApiServer* mApiServer; ApiServer* mApiServer;
QLocalSocket* mLocalSocket; QLocalSocket* mLocalSocket;
State mState;
std::string reqPath; std::string reqPath;
void _die();
}; };
} // namespace resource_api } // namespace resource_api