add server proto

This commit is contained in:
2017-03-29 03:46:42 +03:00
parent 505c1e2b93
commit 511de6e2d0
23 changed files with 595 additions and 21 deletions

View File

@ -0,0 +1,13 @@
# set project name
set (SUBPROJECT "queued-server")
message (STATUS "Subproject ${SUBPROJECT}")
add_subdirectory ("src")
# build man
file (GLOB SUBPROJECT_MAN_IN "*.1")
file (RELATIVE_PATH SUBPROJECT_MAN "${CMAKE_SOURCE_DIR}" "${SUBPROJECT_MAN_IN}")
configure_file ("${SUBPROJECT_MAN_IN}" "${CMAKE_CURRENT_BINARY_DIR}/${SUBPROJECT_MAN}")
install (FILES "${CMAKE_CURRENT_BINARY_DIR}/${SUBPROJECT_MAN}" DESTINATION "${DATA_INSTALL_DIR}/man/man1")
install (FILES "bash-completions" DESTINATION "${DATA_INSTALL_DIR}/bash-completion/completions" RENAME "${SUBPROJECT}")
install (FILES "zsh-completions" DESTINATION "${DATA_INSTALL_DIR}/zsh/site-functions" RENAME "_${SUBPROJECT}")

View File

View File

View File

@ -0,0 +1,18 @@
# set files
file (GLOB_RECURSE SUBPROJECT_SOURCES "*.cpp")
file (GLOB_RECURSE SUBPROJECT_HEADERS "*.h")
# include_path
include_directories ("${PROJECT_LIBRARY_DIR}/include"
"${CMAKE_CURRENT_BINARY_DIR}"
"${CMAKE_BINARY_DIR}"
"${PROJECT_TRDPARTY_DIR}"
"${Qt_INCLUDE}")
qt5_wrap_cpp (SUBPROJECT_MOC_SOURCES "${SUBPROJECT_HEADERS}")
add_executable ("${SUBPROJECT}" "${SUBPROJECT_HEADERS}" "${SUBPROJECT_SOURCES}"
"${SUBPROJECT_MOC_SOURCES}")
target_link_libraries ("${SUBPROJECT}" "${PROJECT_LIBRARY}" "${Qt_LIBRARIES}")
# install properties
install (TARGETS "${SUBPROJECT}" DESTINATION "${BIN_INSTALL_DIR}")

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2017 Evgeniy Alekseev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#include "QueuedServer.h"
#include <queued/Queued.h>
#include "QueuedTcpServer.h"
QueuedServer::QueuedServer(QObject *parent, const QVariantHash &args)
: QObject(parent)
, m_configuration(args)
{
qCDebug(LOG_SERV) << __PRETTY_FUNCTION__;
init();
}
QueuedServer::~QueuedServer()
{
qCDebug(LOG_SERV) << __PRETTY_FUNCTION__;
deinit();
}
void QueuedServer::deinit()
{
if (m_server)
delete m_server;
}
void QueuedServer::init()
{
deinit();
m_server = new QueuedTcpServer(this);
m_server->listen(
QHostAddress(QueuedCoreAdaptor::getOption("ServerAddress").toString()),
QueuedCoreAdaptor::getOption("ServerPort").toUInt());
m_server->setMaxPendingConnections(
QueuedCoreAdaptor::getOption("ServerMaxConnections").toInt());
qCInfo(LOG_SERV) << "Server listen on" << m_server->serverAddress()
<< m_server->serverPort();
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2017 Evgeniy Alekseev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#ifndef QUEUEDSERVER_H
#define QUEUEDSERVER_H
#include <QObject>
#include <QVariant>
class QueuedTcpServer;
class QueuedServer : public QObject
{
Q_OBJECT
public:
explicit QueuedServer(QObject *parent, const QVariantHash &args);
virtual ~QueuedServer();
void deinit();
void init();
private:
// backend
QueuedTcpServer *m_server = nullptr;
// configuration
QVariantHash m_configuration;
};
#endif /* QUEUEDSERVER_H */

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2017 Evgeniy Alekseev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#include "QueuedTcpServer.h"
#include <queued/Queued.h>
#include "QueuedTcpServerThread.h"
QueuedTcpServer::QueuedTcpServer(QObject *parent)
: QTcpServer(parent)
{
qCDebug(LOG_SERV) << __PRETTY_FUNCTION__;
}
QueuedTcpServer::~QueuedTcpServer()
{
qCDebug(LOG_SERV) << __PRETTY_FUNCTION__;
deinit();
}
void QueuedTcpServer::deinit()
{
}
void QueuedTcpServer::init()
{
deinit();
}
void QueuedTcpServer::incomingConnection(qintptr socketDescriptor)
{
QueuedTcpServerThread *thread
= new QueuedTcpServerThread(socketDescriptor, this);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2017 Evgeniy Alekseev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#ifndef QUEUEDTCPSERVER_H
#define QUEUEDTCPSERVER_H
#include <QTcpServer>
class QueuedTcpServer : public QTcpServer
{
Q_OBJECT
public:
explicit QueuedTcpServer(QObject *parent);
virtual ~QueuedTcpServer();
void deinit();
void init();
protected:
void incomingConnection(qintptr socketDescriptor) override;
private:
};
#endif /* QUEUEDTCPSERVER_H */

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2017 Evgeniy Alekseev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#include "QueuedTcpServerThread.h"
#include <QDataStream>
#include <QJsonDocument>
#include <QUrl>
#include <QUrlQuery>
#include <queued/Queued.h>
QueuedTcpServerThread::QueuedTcpServerThread(int socketDescriptor,
QObject *parent)
: QThread(parent)
, m_socketDescriptor(socketDescriptor)
{
qCDebug(LOG_SERV) << __PRETTY_FUNCTION__;
}
QueuedTcpServerThread::~QueuedTcpServerThread()
{
qCDebug(LOG_SERV) << __PRETTY_FUNCTION__;
}
QList<QByteArray> QueuedTcpServerThread::defaultResponse(const int code)
{
qCDebug(LOG_SERV) << "Build server response with code" << code;
QList<QByteArray> output;
output += "HTTP/1.1 " + QByteArray::number(code) + " OK\r\n";
output += "Server: QueuedServer/Qt" + QByteArray(qVersion()) + "\r\n";
output += "Date: "
+ QLocale::c()
.toString(QDateTime::currentDateTimeUtc(),
"ddd, d MMM yyyy HH:mm:dd t")
.toUtf8()
+ "\r\n";
output += "Content-Type: application/json\r\n";
output += "\r\n";
return output;
}
QueuedTcpServerThread::QueuedTcpServerRequest
QueuedTcpServerThread::getRequest(const QStringList &headers,
const QByteArray &body)
{
qCDebug(LOG_SERV) << "Get request object from headers" << headers
<< "and body" << body;
QueuedTcpServerThread::QueuedTcpServerRequest request;
request.valid = true;
// method
request.request = headers.first().split(' ').at(0);
// path
QUrl url(headers.first().split(' ').at(1));
request.path = url.path();
// body
QJsonParseError error;
auto jsonDoc = QJsonDocument::fromJson(body, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(LOG_SERV) << "Parse error" << error.errorString();
request.valid = false;
} else {
request.data = jsonDoc.object().toVariantHash();
}
// append from url if any
auto items = QUrlQuery(url.query()).queryItems();
for (auto &item : items) {
auto key = item.first;
auto value = item.second;
QVariantList values;
switch (request.data[key].type()) {
case QVariant::List:
values = request.data[key].toList();
break;
case QVariant::Invalid:
break;
default:
values = QVariantList({request.data[key]});
break;
}
values.append(key);
request.data[key] = values.count() == 1 ? values.first() : values;
}
return request;
}
void QueuedTcpServerThread::run()
{
m_socket = new QTcpSocket(this);
if (!m_socket->setSocketDescriptor(m_socketDescriptor)) {
emit(error(m_socket->error()));
return;
}
connect(m_socket, SIGNAL(readyRead()), this, SLOT(readyRead()),
Qt::DirectConnection);
connect(m_socket, SIGNAL(disconnected()), this, SLOT(disconnected()),
Qt::DirectConnection);
exec();
}
void QueuedTcpServerThread::disconnected()
{
m_socket->deleteLater();
exit(0);
}
void QueuedTcpServerThread::readyRead()
{
QStringList headers;
while (m_socket->canReadLine())
headers += m_socket->readLine().simplified();
// request body
auto body = m_socket->readAll().simplified();
// get request object
auto request = getRequest(headers, body);
if (!request.valid) {
emit(error(QTcpSocket::UnsupportedSocketOperationError));
return;
}
auto response = defaultResponse(200);
for (auto &resp : response) {
m_socket->write(resp);
m_socket->flush();
}
m_socket->waitForBytesWritten(3000);
m_socket->disconnectFromHost();
if (m_socket->state() != QAbstractSocket::UnconnectedState)
m_socket->waitForDisconnected();
}

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2017 Evgeniy Alekseev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#ifndef QUEUEDTCPSERVERTHREAD_H
#define QUEUEDTCPSERVERTHREAD_H
#include <QTcpSocket>
#include <QThread>
class QTcpSocket;
class QueuedTcpServerThread : public QThread
{
Q_OBJECT
public:
typedef struct {
QString request;
QString path;
QVariantHash data;
bool valid = false;
} QueuedTcpServerRequest;
explicit QueuedTcpServerThread(int socketDescriptor, QObject *parent);
virtual ~QueuedTcpServerThread();
static QList<QByteArray> defaultResponse(const int code);
static QueuedTcpServerRequest getRequest(const QStringList &headers,
const QByteArray &body);
QVariantHash response(const QueuedTcpServerRequest &request) const;
void run() override;
signals:
void error(QTcpSocket::SocketError socketError);
private slots:
void disconnected();
void readyRead();
private:
QTcpSocket *m_socket = nullptr;
int m_socketDescriptor;
};
#endif /* QUEUEDTCPSERVERTHREAD_H */

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2017 Evgeniy Alekseev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
*
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*/
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDBusConnection>
#include <QDBusMessage>
#include <queued/Queued.h>
#include <iostream>
#include "QueuedServer.h"
#include "version.h"
extern "C" {
#include <signal.h>
#include <unistd.h>
}
QueuedServer *instance = nullptr;
int main(int argc, char *argv[])
{
// HACK preparse arguments to find out if --daemon is set
for (int i = 0; i < argc; i++) {
if (std::string(argv[i]) != "--daemon")
continue;
::daemon(0, 0);
break;
}
QueuedDebug::applyLogFormat();
QCoreApplication app(argc, argv);
app.setApplicationName(NAME);
app.setApplicationVersion(VERSION);
// parser
QCommandLineParser parser;
parser.setApplicationDescription(
"Daemon for starting jobs to queue of calculations");
parser.addHelpOption();
parser.addVersionOption();
// info
QCommandLineOption infoOption(QStringList() << "i"
<< "info",
"Show additional info.");
parser.addOption(infoOption);
// debug mode
QCommandLineOption debugOption(QStringList() << "d"
<< "debug",
"Print debug information.");
parser.addOption(debugOption);
// daemon mode
QCommandLineOption daemonOption(QStringList() << "daemon",
"Start detached.");
parser.addOption(daemonOption);
parser.process(app);
// show info and exit
if (parser.isSet(infoOption)) {
auto metadata = QueuedDebug::getBuildData();
for (auto &string : metadata)
std::cout << qPrintable(string) << std::endl;
return 0;
}
// enable debug
if (parser.isSet(debugOption))
QueuedDebug::enableDebug();
QVariantHash arguments;
// start application
instance = new QueuedServer(nullptr, arguments);
// catch SIGHUP
signal(SIGHUP, [](int sig) -> void {
qCInfo(LOG_SERV) << "Received SIGHUP signal, reinit components";
instance->init();
});
return app.exec();
}

View File