1
0
mirror of https://github.com/QtExcel/QXlsx.git synced 2025-01-30 05:02:52 +08:00

add webserver example

This commit is contained in:
Jay Two 2018-11-14 14:01:22 +09:00
parent 5b39b10c16
commit 1d24551ea1
10 changed files with 1670 additions and 1 deletions

View File

@ -61,7 +61,8 @@ qDebug() << var; // 값 표시
- QXlsx 는 MIT 라이센스 입니다. [https://github.com/j2doll/QXlsx](https://github.com/j2doll/QXlsx)
- QtXlsx 는 MIT 라이센스 입니다. [https://github.com/dbzhang800/QtXlsxWriter](https://github.com/dbzhang800/QtXlsxWriter)
- Qt-Table-Printer 는 BSD 3-Clause 라이센스 입니다. [https://github.com/T0ny0/Qt-Table-Printer](https://github.com/T0ny0/Qt-Table-Printer)
- Qt 는 LGPL v3 라이센스 또는 상업용 라이센스 입니다. [https://www.qt.io/](https://www.qt.io/)
- recurse 는 MIT 라이센스 입니다. [https://github.com/pkoretic/recurse](https://github.com/pkoretic/recurse)
- Qt 는 LGPL v3 라이센스 또는 상업용 라이센스 입니다. [https://www.qt.io/](https://www.qt.io/)
## :email: 문의
- 이슈를 남겨 주세요. [https://github.com/j2doll/QXlsx/issues](https://github.com/j2doll/QXlsx/issues)

View File

@ -61,6 +61,7 @@ qDebug() << var; // display value
- QXlsx is under MIT license. [https://github.com/j2doll/QXlsx](https://github.com/j2doll/QXlsx)
- QtXlsx is under MIT license. [https://github.com/dbzhang800/QtXlsxWriter](https://github.com/dbzhang800/QtXlsxWriter)
- Qt-Table-Printer is under BSD 3-Clause license. [https://github.com/T0ny0/Qt-Table-Printer](https://github.com/T0ny0/Qt-Table-Printer)
- recurse is under MIT license. [https://github.com/pkoretic/recurse](https://github.com/pkoretic/recurse)
- Qt is under LGPL v3 license or Commercial license. [https://www.qt.io/](https://www.qt.io/)
## :email: Contact

46
WebServer/WebServer.pro Normal file
View File

@ -0,0 +1,46 @@
#
# WebServer.pro
#
# QXlsx https://github.com/j2doll/QXlsx
# recurse https://github.com/pkoretic/recurse
TARGET = WebServer
TEMPLATE = app
QT += core
QT += network
QT -= gui
CONFIG += console
CONFIG += c++14
CONFIG -= app_bundle
QMAKE_CXXFLAGS += -std=c++14
macx {
QMAKE_CXXFLAGS += -stdlib=libc++
}
# NOTE: You can fix value of QXlsx path of source code.
# QXLSX_PARENTPATH=./
# QXLSX_HEADERPATH=./header/
# QXLSX_SOURCEPATH=./source/
include(../QXlsx/QXlsx.pri)
# source code
RESOURCES += \
ws.qrc
HEADERS += \
recurse.hpp \
request.hpp \
response.hpp \
context.hpp
INCLUDEPATH += .
SOURCES += \
main.cpp

57
WebServer/context.hpp Normal file
View File

@ -0,0 +1,57 @@
#ifndef RECURSE_CONTEXT_HPP
#define RECURSE_CONTEXT_HPP
#include <QVariant>
#include <QHash>
#include "request.hpp"
#include "response.hpp"
class Context
{
public:
Request request;
Response response;
//!
//! \brief set
//! Set data into context that can be passed around
//!
//! \param QString key of the data
//! \param QString value of the data
//! \return Context chainable
//!
Context &set(const QString &key, const QVariant &value)
{
m_data[key] = value;
return *this;
}
//!
//! \brief get
//! Get data from context
//!
//! \param QString key of the data
//! \return QString value of the data
//!
QVariant get(const QString &key) const
{
return m_data[key];
}
//!
//! \brief data
//! expose key/value data of *void pointer to allow any type of data
//!
QHash<QString, void *> data;
private:
//!
//! \brief m_data
//! Context data holder
//!
QHash<QString, QVariant> m_data;
};
#endif

167
WebServer/main.cpp Normal file
View File

@ -0,0 +1,167 @@
// main.cpp
#include <iostream>
#include <QtGlobal>
#include <QObject>
#include <QVector>
#include <QList>
#include "recurse.hpp"
#include "xlsxdocument.h"
#include "xlsxchartsheet.h"
#include "xlsxcellrange.h"
#include "xlsxchart.h"
#include "xlsxrichstring.h"
#include "xlsxworkbook.h"
#include "xlsxabstractsheet.h"
#include "xlsxcelllocation.h"
#include "xlsxcell.h"
using namespace QXlsx;
QString getHtml(QString strFilename);
bool loadXlsx(QString fileName, QString& strHtml);
QString g_htmlDoc;
int main(int argc, char *argv[])
{
g_htmlDoc = getHtml(":/test.xlsx"); // convert from xlsx to html
Recurse::Application app(argc, argv);
app.use([](auto &ctx)
{
ctx.response.send(g_htmlDoc);
});
quint16 listenPort = 3001;
auto result = app.listen( listenPort );
if ( result.error() )
{
qDebug() << "error upon listening:" << result.lastError();
return (-1);
}
std::cout << " listening port: " << listenPort << std::endl;
return 0;
}
QString getHtml(QString strFilename)
{
QString ret;
ret = ret + QString("<html>\n");
ret = ret + QString("<head>\n");
ret = ret + QString("<title>") + strFilename + QString("</title>\n");
ret = ret + QString("<meta http-equiv='Content-Type' content='text/html;charset=UTF-8'>\n" );
ret = ret + QString("</head>\n");
ret = ret + QString("<body>\n");
QString strTableStyle = \
"<style>\n"\
" table { border-collapse: collapse; } \n"\
" td, th { border: 1px solid black; } \n"\
"</style>\n";
ret = ret + strTableStyle;
if (!loadXlsx(strFilename, ret))
return QString("");
ret = ret + QString("</body>\n");
ret = ret + QString("</html>\n");
qDebug() << ret << "\n";
return ret;
}
bool loadXlsx(QString fileName, QString& strHtml)
{
// tried to load xlsx using temporary document
QXlsx::Document xlsxTmp( fileName );
if ( !xlsxTmp.isLoadPackage() )
{
return false; // failed to load
}
// load new xlsx using new document
QXlsx::Document xlsxDoc( fileName );
xlsxDoc.isLoadPackage();
int sheetIndexNumber = 0;
foreach( QString curretnSheetName, xlsxDoc.sheetNames() )
{
QXlsx::AbstractSheet* currentSheet = xlsxDoc.sheet( curretnSheetName );
if ( NULL == currentSheet )
continue;
// get full cells of sheet
int maxRow = -1;
int maxCol = -1;
currentSheet->workbook()->setActiveSheet( sheetIndexNumber );
Worksheet* wsheet = (Worksheet*) currentSheet->workbook()->activeSheet();
if ( NULL == wsheet )
continue;
QString strSheetName = wsheet->sheetName(); // sheet name
strHtml = strHtml + QString("<b>") + strSheetName + QString("</b><br>\n"); // UTF-8
strHtml = strHtml + QString("<table>");
QVector<CellLocation> clList = wsheet->getFullCells( &maxRow, &maxCol );
QVector< QVector<QString> > cellValues;
for (int rc = 0; rc < maxRow; rc++)
{
QVector<QString> tempValue;
for (int cc = 0; cc < maxCol; cc++)
{
tempValue.push_back(QString(""));
}
cellValues.push_back(tempValue);
}
for ( int ic = 0; ic < clList.size(); ++ic )
{
// cell location
CellLocation cl = clList.at(ic);
int row = cl.row - 1;
int col = cl.col - 1;
////////////////////////////////////////////////////////////////////
// cell pointer
QSharedPointer<Cell> ptrCell = cl.cell;
///////////////////////////////////////////////////////////////////
// value of cell
QVariant var = cl.cell.data()->value();
QString str = var.toString();
cellValues[row][col] = str;
}
QString strTableRecord;
for (int rc = 0; rc < maxRow; rc++)
{
strTableRecord = strTableRecord + QString("<tr>");
for (int cc = 0; cc < maxCol; cc++)
{
QString strTemp = cellValues[rc][cc];
strTableRecord = strTableRecord + QString("<td>");
strTableRecord = strTableRecord + strTemp; // UTF-8
strTableRecord = strTableRecord + QString("</td>");
}
strTableRecord = strTableRecord + QString("</tr>\n");
}
strHtml = strHtml + strTableRecord;
strHtml = strHtml + QString("</table>\n");
sheetIndexNumber++;
}
return true;
}

819
WebServer/recurse.hpp Normal file
View File

@ -0,0 +1,819 @@
#ifndef RECURSE_HPP
#define RECURSE_HPP
#include <QCoreApplication>
#include <QFile>
#include <QHostAddress>
#include <QObject>
#include <QPointer>
#include <QProcessEnvironment>
#include <QSslCertificate>
#include <QSslConfiguration>
#include <QSslKey>
#include <QSslSocket>
#include <QStringBuilder>
#include <QTcpServer>
#include <QTcpSocket>
#include <QVector>
#include <functional>
#include <iostream>
#include "request.hpp"
#include "response.hpp"
#include "context.hpp"
namespace Recurse
{
//!
//! \brief The Returns class
//! Generic exit code and response value returning class
//!
class Returns
{
private:
quint16 m_last_error = 0;
QString m_result;
QHash<quint16, QString> codes{
{ 100, "Failed to start listening on port" },
{ 101, "No pending connections available" },
{ 200, "Generic app->exec() error" },
{ 201, "Another generic app->exec() error" },
{ 301, "SSL private key open error" },
{ 302, "SSL certificate open error" }
};
public:
QString lastError()
{
if (m_last_error == 0)
return "No error";
else
return codes[m_last_error];
}
void setErrorCode(quint16 error_code)
{
m_last_error = error_code;
}
quint16 errorCode()
{
return m_last_error;
}
bool error()
{
if (m_last_error == 0)
return false;
else
return true;
}
};
//!
//! \brief The SslTcpServer class
//! Recurse ssl server implementation used for Application::HttpsServer
//!
class SslTcpServer : public QTcpServer
{
Q_OBJECT
Q_DISABLE_COPY(SslTcpServer)
typedef void (QSslSocket::*RSslErrors)(const QList<QSslError> &);
public:
SslTcpServer(QObject *parent = NULL);
~SslTcpServer();
QSslSocket *nextPendingConnection();
void setSslConfiguration(const QSslConfiguration &sslConfiguration);
Q_SIGNALS : void connectionEncrypted();
void sslErrors(const QList<QSslError> &errors);
void peerVerifyError(const QSslError &error);
protected:
//!
//! \brief overridden incomingConnection from QTcpServer
//!
virtual void incomingConnection(qintptr socket_descriptor)
{
auto socket = new QSslSocket();
socket->setSslConfiguration(m_ssl_configuration);
socket->setSocketDescriptor(socket_descriptor);
connect(socket, &QSslSocket::encrypted, this, &SslTcpServer::connectionEncrypted);
connect(socket, static_cast<RSslErrors>(&QSslSocket::sslErrors), this, &SslTcpServer::sslErrors);
connect(socket, &QSslSocket::peerVerifyError, this, &SslTcpServer::peerVerifyError);
addPendingConnection(socket);
socket->startServerEncryption();
}
private:
QSslConfiguration m_ssl_configuration;
};
inline SslTcpServer::SslTcpServer(QObject *parent)
{
Q_UNUSED(parent);
}
inline SslTcpServer::~SslTcpServer()
{
}
//!
//! \brief SslTcpServer::setSslConfiguration
//! set ssl socket configuration
//!
//! \param sslConfiguration ssl socket configuration
//!
inline void SslTcpServer::setSslConfiguration(const QSslConfiguration &sslConfiguration)
{
m_ssl_configuration = sslConfiguration;
}
inline QSslSocket *SslTcpServer::nextPendingConnection()
{
return static_cast<QSslSocket *>(QTcpServer::nextPendingConnection());
}
//!
//! \brief The HttpServer class
//! Http (unsecure) server class
//!
class HttpServer : public QObject
{
Q_OBJECT
public:
HttpServer(QObject *parent = NULL);
~HttpServer();
Returns compose(quint16 port, QHostAddress address = QHostAddress::Any);
private:
QTcpServer m_tcp_server;
quint16 m_port;
QHostAddress m_address;
Returns ret;
signals:
void socketReady(QTcpSocket *socket);
};
inline HttpServer::HttpServer(QObject *parent)
{
Q_UNUSED(parent);
}
inline HttpServer::~HttpServer()
{
}
//!
//! \brief HttpServer::compose
//! prepare http server for request forwarding
//!
//! \param port tcp server port
//! \param address tcp server listening address
//!
inline Returns HttpServer::compose(quint16 port, QHostAddress address)
{
m_port = port;
m_address = address;
if (!m_tcp_server.listen(address, port))
{
ret.setErrorCode(100);
return ret;
}
connect(&m_tcp_server, &QTcpServer::newConnection, [this]
{
QTcpSocket *socket = m_tcp_server.nextPendingConnection();
if (socket == 0)
{
delete socket;
// FIXME: send signal instead of only setting an error and
// erroneously (?) returning
ret.setErrorCode(101);
return ret;
}
emit socketReady(socket);
ret.setErrorCode(0);
return ret;
});
ret.setErrorCode(0);
return ret;
}
//!
//! \brief The HttpsServer class
//! Https (secure) server class
//!
class HttpsServer : public QObject
{
Q_OBJECT
public:
HttpsServer(QObject *parent = NULL);
~HttpsServer();
Returns compose(quint16 port, QHostAddress address = QHostAddress::Any);
Returns compose(const QHash<QString, QVariant> &options);
private:
SslTcpServer m_tcp_server;
quint16 m_port;
QHostAddress m_address;
Returns ret;
signals:
void socketReady(QTcpSocket *socket);
};
inline HttpsServer::HttpsServer(QObject *parent)
{
Q_UNUSED(parent);
}
inline HttpsServer::~HttpsServer()
{
}
//!
//! \brief HttpsServer::compose
//! prepare https server for request forwarding
//!
//! \param port tcp server port
//! \param address tcp server listening address
//!
//! \return Returns return execution status code
//!
inline Returns HttpsServer::compose(quint16 port, QHostAddress address)
{
m_port = port;
m_address = address;
if (!m_tcp_server.listen(address, port))
{
ret.setErrorCode(100);
return ret;
}
connect(&m_tcp_server, &SslTcpServer::connectionEncrypted, [this]
{
QTcpSocket *socket = m_tcp_server.nextPendingConnection();
if (socket == 0)
{
delete socket;
// FIXME: send signal instead of throwing
ret.setErrorCode(101);
return ret;
}
emit socketReady(socket);
ret.setErrorCode(0);
return ret;
});
ret.setErrorCode(0);
return ret;
}
//!
//! \brief HttpsServer::compose
//! overloaded function,
//! prepare https server for request forwarding
//!
//! \param options QHash options of <QString, QVariant>
//!
//! \return Returns return execution status code
//!
inline Returns HttpsServer::compose(const QHash<QString, QVariant> &options)
{
QByteArray priv_key;
QFile priv_key_file(options.value("private_key").toString());
if (!priv_key_file.open(QIODevice::ReadOnly))
{
ret.setErrorCode(301);
return ret;
}
priv_key = priv_key_file.readAll();
priv_key_file.close();
if (priv_key.isEmpty())
{
ret.setErrorCode(301);
return ret;
}
QSslKey ssl_key(priv_key, QSsl::Rsa);
QByteArray cert_key;
QFile cert_key_file(options.value("certificate").toString());
if (!cert_key_file.open(QIODevice::ReadOnly))
{
ret.setErrorCode(302);
return ret;
}
cert_key = cert_key_file.readAll();
cert_key_file.close();
if (cert_key.isEmpty())
{
ret.setErrorCode(302);
return ret;
}
QSslCertificate ssl_cert(cert_key);
QSslConfiguration ssl_configuration;
ssl_configuration.setPrivateKey(ssl_key);
ssl_configuration.setLocalCertificate(ssl_cert);
m_tcp_server.setSslConfiguration(ssl_configuration);
if (!options.contains("port"))
m_port = 0;
else
m_port = options.value("port").toUInt();
if (!options.contains("host"))
m_address = QHostAddress::LocalHost;
else
m_address = QHostAddress(options.value("host").toString());
auto r = compose(m_port, m_address);
if (r.error())
{
ret.setErrorCode(r.errorCode());
return ret;
}
ret.setErrorCode(0);
return ret;
}
using void_f = std::function<void()>;
using Prev = void_f;
using Next = void_f;
using NextPrev = std::function<void(Prev prev)>;
using DownstreamUpstream = std::function<void(Context &ctx, NextPrev next, Prev prev)>;
using Downstream = std::function<void(Context &ctx, Next next)>;
using Final = std::function<void(Context &ctx)>;
//!
//! \brief The Recurse class
//! main class of the app
//!
class Application : public QObject
{
Q_OBJECT
public:
Application(QCoreApplication *core_inst);
Application(int &argc, char **argv, QObject *parent = NULL);
~Application();
void http_server(quint16 port, QHostAddress address = QHostAddress::Any);
void http_server(const QHash<QString, QVariant> &options);
void https_server(const QHash<QString, QVariant> &options);
Returns listen(quint16 port, QHostAddress address = QHostAddress::Any);
Returns listen();
void use(Downstream next);
void use(DownstreamUpstream next);
void use(Final next);
public slots:
bool handleConnection(QTcpSocket *socket);
private:
QPointer<QCoreApplication> app;
QPointer<HttpServer> http;
QPointer<HttpsServer> https;
Returns ret;
QVector<DownstreamUpstream> m_middleware_next;
bool m_http_set = false;
bool m_https_set = false;
quint16 m_http_port;
QHostAddress m_http_address;
QHash<QString, QVariant> m_https_options;
bool m_debug = false;
bool m_int_core = false;
void m_start_upstream(Context *ctx, QVector<Prev> *middleware_prev);
void m_send_response(Context *ctx);
void m_call_next(Prev prev, Context *ctx, int current_middleware, QVector<Prev> *middleware_prev);
quint16 appExitHandler(quint16 code);
void debug(QString message);
};
inline Application::Application(QCoreApplication *core_inst)
: app(core_inst)
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QRegExp debug_strings("(recurse|development)");
if (debug_strings.indexIn(env.value("DEBUG")) != -1)
m_debug = true;
}
inline Application::Application(int &argc, char **argv, QObject *parent)
: Application(new QCoreApplication(argc, argv))
{
Q_UNUSED(parent);
m_int_core = true;
}
inline Application::~Application()
{
if (app)
delete app;
if (http)
delete http;
if (https)
delete https;
}
//!
//! \brief Application::debug
//! Console debugging output wrapper based on RECURSE_DEBUG environment variable
//!
inline void Application::debug(QString message)
{
if (m_debug)
std::cout << "(recurse debug) " << message.toStdString() << std::endl;
}
//!
//! \brief Application::end
//! final function to be called for creating/sending response
//! \param request
//! \param response
//!
inline void Application::m_start_upstream(Context *ctx, QVector<void_f> *middleware_prev)
{
debug("start upstream: " + QString::number(middleware_prev->size()));
// if there are no upstream middlewares send response directly
if (!middleware_prev->size())
m_send_response(ctx);
else
middleware_prev->at(middleware_prev->size() - 1)();
}
//!
//! \brief Application::m_send_response
//! used as last middleware (upstream) to be called
//! sends response to client
//! \param ctx
//!
inline void Application::m_send_response(Context *ctx)
{
debug("end upstream");
auto request = ctx->request;
auto response = ctx->response;
response.method = request.method;
response.protocol = request.protocol;
QString reply = response.create_reply();
// send response to the client
request.socket->write(reply.toUtf8());
request.socket->disconnectFromHost();
}
//!
//! \brief Application::m_call_next
//! call next middleware
//!
inline void Application::m_call_next(Prev prev, Context *ctx, int current_middleware, QVector<Prev> *middleware_prev)
{
debug("calling next: " + QString::number(current_middleware) + " num: " + QString::number(m_middleware_next.size()));
++current_middleware;
// save previous middleware function
middleware_prev->push_back(std::move(prev));
// call next function with current prev
m_middleware_next[current_middleware](*ctx, std::bind(&Application::m_call_next, this, std::placeholders::_1, ctx, current_middleware, middleware_prev), prev);
}
//!
//! \brief Application::use
//! add new middleware
//!
//! \param f middleware function that will be called later
//!
//!
//!
inline void Application::use(DownstreamUpstream f)
{
m_middleware_next.push_back(std::move(f));
}
//!
//! \brief Application::use
//! overload function, next middleware only, no upstream
//!
//! \param f
//!
inline void Application::use(Downstream f)
{
m_middleware_next.push_back([g = std::move(f)](Context &ctx, NextPrev next, Prev prev)
{
g(ctx, [next, prev]()
{
next([prev]()
{
prev();
});
});
});
}
//!
//! \brief Application::use
//! overloaded function,
//! final middleware that doesn't call next, used for returning response
//!
//! \param f final middleware function that will be called last
//!
inline void Application::use(Final f)
{
m_middleware_next.push_back([g = std::move(f)](Context &ctx, NextPrev /* next */, Prev /* prev */)
{
g(ctx);
});
}
//!
//! \brief Application::handleConnection
//! creates new recurse context for a tcp session
//!
//! \param pointer to the socket sent from http/https server
//!
//! \return Returns return execution status code
//!
inline bool Application::handleConnection(QTcpSocket *socket)
{
debug("handling new connection");
auto middleware_prev = QSharedPointer<QVector<Prev>>(new QVector<Prev>);
middleware_prev->reserve(m_middleware_next.count());
auto ctx = QSharedPointer<Context>(new Context);
ctx->request.socket = socket;
connect(socket, &QTcpSocket::readyRead, [this, ctx, middleware_prev, socket]
{
QString data(socket->readAll());
ctx->request.parse(data);
if (ctx->request.length < ctx->request.getHeader("content-length").toLongLong())
return;
ctx->response.end = std::bind(&Application::m_start_upstream, this, ctx.data(), middleware_prev.data());
m_middleware_next[0](
*ctx,
std::bind(&Application::m_call_next, this, std::placeholders::_1, ctx.data(), 0, middleware_prev.data()),
std::bind(&Application::m_send_response, this, ctx.data()));
});
connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater);
return true;
}
//!
//! \brief Application::appExitHandler
//! acts according to the provided application event loop exit code
//!
//! \param code app->exec()'s exit code
//!
//! \return quint16 error code
//!
inline quint16 Application::appExitHandler(quint16 code)
{
if (code == 1)
return 201;
return 200;
}
//!
//! \brief Application::http_server
//! http server initialization
//!
//! \param port tcp server port
//! \param address tcp server listening address
//!
inline void Application::http_server(quint16 port, QHostAddress address)
{
http = new HttpServer();
m_http_port = port;
m_http_address = address;
m_http_set = true;
debug("http server setup done");
}
//!
//! \brief Application::http_server
//! overloaded function,
//! http server initialization
//!
//! \param options QHash options of <QString, QVariant>
//!
inline void Application::http_server(const QHash<QString, QVariant> &options)
{
http = new HttpServer();
if (!options.contains("port"))
m_http_port = 0;
else
m_http_port = options.value("port").toUInt();
if (!options.contains("host"))
m_http_address = QHostAddress::Any;
else
m_http_address = QHostAddress(options.value("host").toString());
m_http_set = true;
std::bind(&Application::debug, std::placeholders::_1, "http");
debug("http server setup done");
}
//!
//! \brief Application::https_server
//! https (secure) server initialization
//!
//! \param options QHash options of <QString, QVariant>
//!
inline void Application::https_server(const QHash<QString, QVariant> &options)
{
https = new HttpsServer(this);
m_https_options = options;
m_https_set = true;
debug("https server setup done");
}
//!
//! \brief Application::listen
//! listen for tcp requests
//!
//! \param port tcp server port
//! \param address tcp server listening address
//!
//! \return Returns return execution status code
//!
inline Returns Application::listen(quint16 port, QHostAddress address)
{
use([](auto &ctx)
{
ctx.response.status(404).send("Not Found");
});
// if this function is called and m_http_set is true, ignore new values
if (m_http_set)
return listen();
// if this function is called and m_http_set is false
// set HttpServer instance and prepare an http connection
http = new HttpServer();
auto r = http->compose(port, address);
if (r.error())
{
ret.setErrorCode(r.errorCode());
debug("Application::listen http->compose error: " + ret.lastError());
app->exit(1);
return ret;
}
// connect HttpServer signal 'socketReady' to this class' 'handleConnection' slot
connect(http, &HttpServer::socketReady, this, &Application::handleConnection);
if (m_int_core)
{
auto ok = app->exec();
if (!ok)
{
ret.setErrorCode(200);
debug("Application::listen exec error: " + ret.lastError());
app->exit(1);
return ret;
}
debug("main loop exited");
}
ret.setErrorCode(0);
return ret;
}
//!
//! \brief Application::listen
//! overloaded function,
//! listen for tcp requests
//!
//! \return Returns return execution status code
//!
inline Returns Application::listen()
{
use([](auto &ctx)
{
ctx.response.status(404).send("Not Found");
});
if (m_http_set)
{
auto r = http->compose(m_http_port, m_http_address);
if (r.error())
{
ret.setErrorCode(r.errorCode());
debug("Application::listen http->compose error: " + ret.lastError());
app->exit(1);
return ret;
}
connect(http, &HttpServer::socketReady, this, &Application::handleConnection);
}
if (m_https_set)
{
auto r = https->compose(m_https_options);
if (r.error())
{
ret.setErrorCode(r.errorCode());
debug("Application::listen https->compose error: " + ret.lastError());
app->exit(1);
return ret;
}
connect(https, &HttpsServer::socketReady, this, &Application::handleConnection);
}
if (!m_http_set && !m_https_set)
return listen(0);
if (m_int_core)
{
auto exit_code = app->exec();
if (exit_code != 0)
{
// TODO: set error code according to app.quit() or app->exit() method's code
ret.setErrorCode(appExitHandler(exit_code));
debug("Application::listen app->exec() return error: " + ret.lastError());
return ret;
}
debug("main loop exited");
}
ret.setErrorCode(0);
return ret;
}
}
#endif

249
WebServer/request.hpp Normal file
View File

@ -0,0 +1,249 @@
#ifndef RECURSE_REQUEST_HPP
#define RECURSE_REQUEST_HPP
#include <QTcpSocket>
#include <QHash>
#include <QUrl>
#include <QUrlQuery>
class Request
{
public:
//!
//! \brief data
//! client request buffer data
//!
QString data;
//!
//! \brief socket
//! underlying client socket
//!
QTcpSocket *socket;
//!
//! \brief body_parsed
//! Data to be filled by body parsing middleware
//!
QHash<QString, QVariant> body_parsed;
//!
//! \brief body
//!
QString body;
//!
//! \brief method
//! HTTP method, eg: GET
//!
QString method;
//!
//! \brief protocol
//! Request protocol, eg: HTTP
//!
QString protocol;
//!
//! \brief secure
//! Shorthand for protocol == "HTTPS" to check if a requet was issued via TLS
//!
bool secure = protocol == "HTTPS";
//!
//! \brief url
//! HTTP request url, eg: /helloworld
//!
QUrl url;
//!
//! \brief query
//! query strings
//!
QUrlQuery query;
//!
//! \brief params
//!r
//! request parameters that can be filled by router middlewares
//! it's easier to provide container here (which doesn't have to be used)
QHash<QString, QString> params;
//!
//! \brief length
//! HTTP request Content-Length
//!
qint64 length = 0;
//!
//! \brief ip
//! Client ip address
//!
QHostAddress ip;
//!
//! \brief hostname
//! HTTP hostname from "Host" HTTP header
//!
QString hostname;
//!
//! \brief getHeader
//! return header value, keys are saved in lowercase
//! \param key QString
//! \return QString header value
//!
QString getHeader(const QString &key)
{
return m_headers[key];
}
//!
//! \brief getRawHeader
//! return original header name as sent by client
//! \param key QString case-sensitive key of the header
//! \return QString header value as sent by client
//!
QHash<QString, QString> getRawHeaders()
{
return m_headers;
}
//!
//! \brief getCookie
//! return cookie value with lowercase name
//! \param key case-insensitive cookie name
//! \return
//!
QString getCookie(const QString &key)
{
return m_cookies[key.toLower()];
}
//!
//! \brief getRawCookie
//! return cookie name as sent by client
//! \param key case-sensitive cookie name
//! \return
//!
QString getRawCookie(const QString &key)
{
return m_cookies[key];
}
//!
//! \brief getParam
//! return params value
//! \param key of the param, eg: name
//! \return value of the param, eg: johnny
//!
QString getParam(const QString &key)
{
return params.value(key);
}
//!
//! \brief parse
//! parse data from request
//!
//! \param QString request
//! \return true on success, false otherwise, considered bad request
//!
bool parse(QString request);
private:
//!
//! \brief header
//! HTTP request headers, eg: header["content-type"] = "text/plain"
//!
QHash<QString, QString> m_headers;
//!
//! \brief cookies
//! HTTP cookies in key/value form
//!
QHash<QString, QString> m_cookies;
//!
//! \brief httpRx
//! match HTTP request line
//!
QRegExp httpRx = QRegExp("^(?=[A-Z]).* \\/.* HTTP\\/[0-9]\\.[0-9]\\r\\n");
};
inline bool Request::parse(QString request)
{
// buffer all data
this->data += request;
// Save client ip address
this->ip = this->socket->peerAddress();
// if no header is present, just append all data to request.body
if (!this->data.contains(httpRx))
{
this->body.append(this->data);
return true;
}
auto data_list = this->data.splitRef("\r\n");
bool is_body = false;
for (int i = 0; i < data_list.size(); ++i)
{
if (is_body)
{
this->body.append(data_list.at(i));
this->length += this->body.size();
continue;
}
auto entity_item = data_list.at(i).split(":");
if (entity_item.length() < 2 && entity_item.at(0).size() < 1 && !is_body)
{
is_body = true;
continue;
}
else if (i == 0 && entity_item.length() < 2)
{
auto first_line = entity_item.at(0).split(" ");
this->method = first_line.at(0).toString();
this->url = first_line.at(1).toString();
this->query.setQuery(this->url.query());
this->protocol = first_line.at(2).toString();
continue;
}
m_headers[entity_item.at(0).toString().toLower()] = entity_item.at(1).toString();
}
if (m_headers.contains("host"))
this->hostname = m_headers["host"];
// extract cookies
// eg: USER_TOKEN=Yes;test=val
if (m_headers.contains("cookie"))
{
for (const auto &cookie : m_headers["cookie"].splitRef(";"))
{
int split = cookie.indexOf("=");
if (split == -1)
continue;
auto key = cookie.left(split);
if (!key.size())
continue;
auto value = cookie.mid(split + 1);
m_cookies[key.toString().toLower()] = value.toString();
}
}
return true;
}
#endif

323
WebServer/response.hpp Normal file
View File

@ -0,0 +1,323 @@
#ifndef RECURSE_RESPONSE_HPP
#define RECURSE_RESPONSE_HPP
#include <QHash>
#include <QJsonDocument>
#include <functional>
class Response
{
public:
//!
//! \brief get
//! Returns the HTTP response header specified by key
//!
//! \param QString case-insensitive key of the header
//! \return QString header
//!
QString getHeader(const QString &key)
{
return m_headers[key.toLower()];
}
//!
//! \brief set
//! Sets the response HTTP header to value.
//!
//! \param QString key of the header
//! \param QString value for the header
//! \return Response chainable
//!
Response &setHeader(const QString &key, const QString &value)
{
m_headers[key] = value;
return *this;
}
//!
//! \brief status
//! Get HTTP response status
//!
//! \return quint16 status
//!
quint16 status() const
{
return m_status;
}
//!
//! \brief status
//! Set HTTP response status
//!
//! \param quint16 status
//! \return Response chainable
//!
Response &status(quint16 status)
{
m_status = status;
return *this;
}
//!
//! \brief type
//! Get the Content-Type HTTP header
//!
//! \return QString MIME content-type
//!
QString type() const
{
return m_headers["content-type"];
}
//!
//! \brief type
//! Sets Content-Type HTTP header
//!
//! \param QString MIME type
//! \return Response chainable
//!
Response &type(const QString &type)
{
m_headers["content-type"] = type;
return *this;
}
//!
//! \brief body
//! Get current response body data content, useful for upstream middleware
//!
//! \return QString response content
//!
QString body() const
{
return m_body;
}
//!
//! \brief body
//! Set response content, overrides existing
//!
//! \param QString body
//! \return Response chainable
//!
Response &body(const QString &body)
{
m_body = body;
return *this;
}
//!
//! \brief write
//! Appends data to existing content (set by write() or body())
//!
//! \param QString data to be added
//! \return Response chainable
//!
Response &write(const QString &data)
{
m_body += data;
return *this;
}
//!
//! \brief send
//! Sends actual data to client
//!
//! \param QString body optional, if provided this is sent instead of current data in buffer
//!
void send(const QString &body = "")
{
if (body.size())
m_body = body;
end();
}
//!
//! \brief send
//! Overloaded function, allows sending QJsonDocument
//!
//! \param body
//!
void send(const QJsonDocument &body)
{
type("application/json");
m_body = body.toJson(QJsonDocument::Compact);
end();
}
//! \brief redirect
//! Perform a 302 redirect to `url`.
//!
//! the string "back" is used to provide Referrer support
//! when Referrer is not present `alt` is used
//!
//! Examples:
//!
//! redirect('back');
//! redirect('back', '/index.html');
//! redirect('/login');
//! redirect('http://google.com');
//!
//! To override status or body set them before calling redirect
//!
//! status(301).body("Redirecting...").redirect("http://www.google.com")
//!
//! \param url to redirect to
//! \param alt used when referrer is not present, "/" by default
void redirect(const QString &url, const QString &alt = "/")
{
// set location
if (url == "back")
{
const QString &referrer = getHeader("referrer");
if (!referrer.isEmpty())
setHeader("Location", referrer);
else
setHeader("Location", alt);
}
else
{
setHeader("Location", url);
}
// set redirect status if not set
// https://tools.ietf.org/html/rfc7231#section-6.4
if (status() < 300 || status() > 308)
status(302);
// set body if not set
if (body().isEmpty())
body("This page has moved to " % url);
end();
}
//!
//! \brief end
//! final function responsible for sending data
//! this is bound to Recurse::end function, from recurse.hpp
//! ctx->response.end = std::bind(&Recurse::end, this, ctx);
//!
std::function<void()> end;
//!
//! \brief method
//! Response method, eg: GET
//!
QString method;
//!
//! \brief protocol
//! Response protocol, eg: HTTP
//!
QString protocol;
//!
//! \brief http codes
//!
QHash<quint16, QString> http_codes{
{ 100, "Continue" },
{ 101, "Switching Protocols" },
{ 200, "OK" },
{ 201, "Created" },
{ 202, "Accepted" },
{ 203, "Non-Authoritative Information" },
{ 204, "No Content" },
{ 205, "Reset Content" },
{ 206, "Partial Content" },
{ 300, "Multiple Choices" },
{ 301, "Moved Permanently" },
{ 302, "Found" },
{ 303, "See Other" },
{ 304, "Not Modified" },
{ 305, "Use Proxy" },
{ 307, "Temporary Redirect" },
{ 400, "Bad Request" },
{ 401, "Unauthorized" },
{ 402, "Payment Required" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
{ 405, "Method Not Allowed" },
{ 406, "Not Acceptable" },
{ 407, "Proxy Authentication Required" },
{ 408, "Request Time-out" },
{ 409, "Conflict" },
{ 410, "Gone" },
{ 411, "Length Required" },
{ 412, "Precondition Failed" },
{ 413, "Request Entity Too Large" },
{ 414, "Request-URI Too Large" },
{ 415, "Unsupported Media Type" },
{ 416, "Requested range not satisfiable" },
{ 417, "Expectation Failed" },
{ 500, "Internal Server Error" },
{ 501, "Not Implemented" },
{ 502, "Bad Gateway" },
{ 503, "Service Unavailable" },
{ 504, "Gateway Time-out" },
{ 505, "HTTP Version not supported" }
};
//!
//! \brief create_reply
//! create reply for sending to client
//!
//! \return QString reply to be sent
//!
QString create_reply();
private:
//!
//! \brief m_status
//! HTTP response status
//!
quint16 m_status = 200;
//!
//! \brief header
//! holds all header data as key/value
//!
QHash<QString, QString> m_headers;
//!
//! \brief m_body
//! HTTP response content
//!
QString m_body;
};
// https://tools.ietf.org/html/rfc7230#page-19
inline QString Response::create_reply()
{
QString reply = this->protocol % " " % QString::number(this->status()) % " "
% this->http_codes[this->status()] % "\r\n";
// set content length
m_headers["content-length"] = QString::number(this->body().size());
// set content type if not set
if (!m_headers.contains("content-type"))
{
// Fixed by j2doll.
// m_headers["content-type"] = "text/plain";
m_headers["content-type"] = "text/html";
}
// set custom header fields
for (auto i = m_headers.constBegin(); i != m_headers.constEnd(); ++i)
reply = reply % i.key() % ": " % i.value() % "\r\n";
reply += "\r\n";
if (this->body().size())
reply += this->body();
return reply;
}
#endif

BIN
WebServer/test.xlsx Normal file

Binary file not shown.

6
WebServer/ws.qrc Normal file
View File

@ -0,0 +1,6 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource>
<file>test.xlsx</file>
</qresource>
</RCC>