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:
parent
5b39b10c16
commit
1d24551ea1
@ -61,6 +61,7 @@ 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)
|
||||
- recurse 는 MIT 라이센스 입니다. [https://github.com/pkoretic/recurse](https://github.com/pkoretic/recurse)
|
||||
- Qt 는 LGPL v3 라이센스 또는 상업용 라이센스 입니다. [https://www.qt.io/](https://www.qt.io/)
|
||||
|
||||
## :email: 문의
|
||||
|
@ -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
46
WebServer/WebServer.pro
Normal 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
57
WebServer/context.hpp
Normal 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
167
WebServer/main.cpp
Normal 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
819
WebServer/recurse.hpp
Normal 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
249
WebServer/request.hpp
Normal 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
323
WebServer/response.hpp
Normal 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
BIN
WebServer/test.xlsx
Normal file
Binary file not shown.
6
WebServer/ws.qrc
Normal file
6
WebServer/ws.qrc
Normal file
@ -0,0 +1,6 @@
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource>
|
||||
<file>test.xlsx</file>
|
||||
</qresource>
|
||||
</RCC>
|
Loading…
x
Reference in New Issue
Block a user