mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
Details coming soon.
This commit is contained in:
26
libsrc/webserver/CMakeLists.txt
Normal file
26
libsrc/webserver/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
# Define the current source locations
|
||||
set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/webserver)
|
||||
set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/webserver)
|
||||
|
||||
FILE ( GLOB WebConfig_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
|
||||
FILE ( GLOB_RECURSE webFiles RELATIVE ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/assets/webconfig/* )
|
||||
|
||||
FOREACH( f ${webFiles} )
|
||||
STRING ( REPLACE "../assets/webconfig/" "" fname ${f})
|
||||
SET(HYPERION_WEBCONFIG_RES "${HYPERION_WEBCONFIG_RES}\n\t\t<file alias=\"/webconfig/${fname}\">${f}</file>")
|
||||
ENDFOREACH()
|
||||
CONFIGURE_FILE(${CURRENT_SOURCE_DIR}/WebConfig.qrc.in ${CMAKE_BINARY_DIR}/WebConfig.qrc )
|
||||
SET(WebConfig_RESOURCES ${CMAKE_BINARY_DIR}/WebConfig.qrc)
|
||||
|
||||
add_library(webserver
|
||||
${WebConfig_SOURCES}
|
||||
${WebConfig_RESOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(webserver
|
||||
hyperion
|
||||
hyperion-utils
|
||||
hyperion-api
|
||||
Qt5::Network
|
||||
)
|
107
libsrc/webserver/CgiHandler.cpp
Normal file
107
libsrc/webserver/CgiHandler.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include <QStringBuilder>
|
||||
#include <QUrlQuery>
|
||||
#include <QFile>
|
||||
#include <QByteArray>
|
||||
#include <QStringList>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
|
||||
#include "CgiHandler.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include <utils/FileUtils.h>
|
||||
#include <utils/Process.h>
|
||||
#include <utils/jsonschema/QJsonFactory.h>
|
||||
|
||||
CgiHandler::CgiHandler (Hyperion * hyperion, QObject * parent)
|
||||
: QObject(parent)
|
||||
, _hyperion(hyperion)
|
||||
, _args(QStringList())
|
||||
, _hyperionConfig(_hyperion->getQJsonConfig())
|
||||
, _baseUrl()
|
||||
, _log(Logger::getInstance("WEBSERVER"))
|
||||
{
|
||||
}
|
||||
|
||||
CgiHandler::~CgiHandler()
|
||||
{
|
||||
}
|
||||
|
||||
void CgiHandler::setBaseUrl(const QString& url)
|
||||
{
|
||||
_baseUrl = url;
|
||||
}
|
||||
|
||||
void CgiHandler::exec(const QStringList & args, QtHttpRequest * request, QtHttpReply * reply)
|
||||
{
|
||||
try
|
||||
{
|
||||
// QByteArray header = reply->getHeader(QtHttpHeader::Host);
|
||||
// QtHttpRequest::ClientInfo info = request->getClientInfo();
|
||||
_args = args;
|
||||
_request = request;
|
||||
_reply = reply;
|
||||
cmd_cfg_jsonserver();
|
||||
// cmd_cfg_set();
|
||||
cmd_runscript();
|
||||
throw 1;
|
||||
}
|
||||
catch(int e)
|
||||
{
|
||||
if (e != 0) throw 1;
|
||||
}
|
||||
}
|
||||
|
||||
void CgiHandler::cmd_cfg_jsonserver()
|
||||
{
|
||||
if ( _args.at(0) == "cfg_jsonserver" )
|
||||
{
|
||||
quint16 jsonPort = 19444;
|
||||
if (_hyperionConfig.contains("jsonServer"))
|
||||
{
|
||||
const QJsonObject jsonConfig = _hyperionConfig["jsonServer"].toObject();
|
||||
jsonPort = jsonConfig["port"].toInt(jsonPort);
|
||||
}
|
||||
|
||||
// send result as reply
|
||||
_reply->addHeader ("Content-Type", "text/plain" );
|
||||
_reply->appendRawData (QByteArrayLiteral(":") % QString::number(jsonPort).toUtf8() );
|
||||
|
||||
throw 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CgiHandler::cmd_runscript()
|
||||
{
|
||||
if ( _args.at(0) == "run" )
|
||||
{
|
||||
QStringList scriptFilePathList(_args);
|
||||
scriptFilePathList.removeAt(0);
|
||||
|
||||
QString scriptFilePath = scriptFilePathList.join('/');
|
||||
// relative path not allowed
|
||||
if (scriptFilePath.indexOf("..") >=0)
|
||||
{
|
||||
Error( _log, "relative path not allowed (%s)", scriptFilePath.toStdString().c_str());
|
||||
throw 1;
|
||||
}
|
||||
|
||||
scriptFilePath = _baseUrl+"/server_scripts/"+scriptFilePath;
|
||||
|
||||
if (QFile::exists(scriptFilePath) && scriptFilePath.endsWith(".py") )
|
||||
{
|
||||
QtHttpPostData postData = _request->getPostData();
|
||||
QByteArray inputData; // should be filled with post data
|
||||
QByteArray data = Process::command_exec("python " + scriptFilePath, inputData);
|
||||
_reply->addHeader ("Content-Type", "text/plain");
|
||||
_reply->appendRawData (data);
|
||||
throw 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Error( _log, "script %s doesn't exists or is no python file", scriptFilePath.toStdString().c_str());
|
||||
}
|
||||
|
||||
throw 1;
|
||||
}
|
||||
}
|
38
libsrc/webserver/CgiHandler.h
Normal file
38
libsrc/webserver/CgiHandler.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef CGIHANDLER_H
|
||||
#define CGIHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <hyperion/Hyperion.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpRequest.h"
|
||||
|
||||
class CgiHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CgiHandler (Hyperion * hyperion, QObject * parent = NULL);
|
||||
virtual ~CgiHandler (void);
|
||||
|
||||
void setBaseUrl(const QString& url);
|
||||
void exec(const QStringList & args,QtHttpRequest * request, QtHttpReply * reply);
|
||||
|
||||
// cgi commands
|
||||
void cmd_cfg_jsonserver();
|
||||
void cmd_runscript ();
|
||||
|
||||
private:
|
||||
Hyperion* _hyperion;
|
||||
QtHttpReply * _reply;
|
||||
QtHttpRequest * _request;
|
||||
QStringList _args;
|
||||
const QJsonObject & _hyperionConfig;
|
||||
QString _baseUrl;
|
||||
Logger * _log;
|
||||
};
|
||||
|
||||
#endif // CGIHANDLER_H
|
263
libsrc/webserver/QtHttpClientWrapper.cpp
Normal file
263
libsrc/webserver/QtHttpClientWrapper.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
|
||||
#include "QtHttpClientWrapper.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpServer.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include "WebSocketClient.h"
|
||||
#include "WebJsonRpc.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QTcpSocket>
|
||||
#include <QStringBuilder>
|
||||
#include <QStringList>
|
||||
#include <QDateTime>
|
||||
#include <QHostAddress>
|
||||
|
||||
const QByteArray & QtHttpClientWrapper::CRLF = QByteArrayLiteral ("\r\n");
|
||||
|
||||
QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent)
|
||||
: QObject (parent)
|
||||
, m_guid ("")
|
||||
, m_parsingStatus (AwaitingRequest)
|
||||
, m_sockClient (sock)
|
||||
, m_currentRequest (Q_NULLPTR)
|
||||
, m_serverHandle (parent)
|
||||
{
|
||||
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
|
||||
}
|
||||
|
||||
QString QtHttpClientWrapper::getGuid (void) {
|
||||
if (m_guid.isEmpty ()) {
|
||||
m_guid = QString::fromLocal8Bit (
|
||||
QCryptographicHash::hash (
|
||||
QByteArray::number ((quint64) (this)),
|
||||
QCryptographicHash::Md5
|
||||
).toHex ()
|
||||
);
|
||||
}
|
||||
return m_guid;
|
||||
}
|
||||
|
||||
void QtHttpClientWrapper::onClientDataReceived (void) {
|
||||
if (m_sockClient != Q_NULLPTR) {
|
||||
while (m_sockClient->bytesAvailable ()) {
|
||||
QByteArray line = m_sockClient->readLine ();
|
||||
switch (m_parsingStatus) { // handle parsing steps
|
||||
case AwaitingRequest: { // "command url version" × 1
|
||||
QString str = QString::fromUtf8 (line).trimmed ();
|
||||
QStringList parts = str.split (SPACE, QString::SkipEmptyParts);
|
||||
if (parts.size () == 3) {
|
||||
QString command = parts.at (0);
|
||||
QString url = parts.at (1);
|
||||
QString version = parts.at (2);
|
||||
if (version == QtHttpServer::HTTP_VERSION) {
|
||||
m_currentRequest = new QtHttpRequest (this, m_serverHandle);
|
||||
m_currentRequest->setClientInfo(m_sockClient->localAddress(), m_sockClient->peerAddress());
|
||||
m_currentRequest->setUrl (QUrl (url));
|
||||
m_currentRequest->setCommand (command);
|
||||
m_parsingStatus = AwaitingHeaders;
|
||||
}
|
||||
else {
|
||||
m_parsingStatus = ParsingError;
|
||||
//qWarning () << "Error : unhandled HTTP version :" << version;
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_parsingStatus = ParsingError;
|
||||
//qWarning () << "Error : incorrect HTTP command line :" << line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AwaitingHeaders: { // "header: value" × N (until empty line)
|
||||
QByteArray raw = line.trimmed ();
|
||||
if (!raw.isEmpty ()) { // parse headers
|
||||
int pos = raw.indexOf (COLON);
|
||||
if (pos > 0) {
|
||||
QByteArray header = raw.left (pos).trimmed ();
|
||||
QByteArray value = raw.mid (pos +1).trimmed ();
|
||||
m_currentRequest->addHeader (header, value);
|
||||
if (header == QtHttpHeader::ContentLength) {
|
||||
bool ok = false;
|
||||
const int len = value.toInt (&ok, 10);
|
||||
if (ok) {
|
||||
m_currentRequest->addHeader (QtHttpHeader::ContentLength, QByteArray::number (len));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_parsingStatus = ParsingError;
|
||||
qWarning () << "Error : incorrect HTTP headers line :" << line;
|
||||
}
|
||||
}
|
||||
else { // end of headers
|
||||
if (m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt () > 0) {
|
||||
m_parsingStatus = AwaitingContent;
|
||||
}
|
||||
else {
|
||||
m_parsingStatus = RequestParsed;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AwaitingContent: { // raw data × N (until EOF ??)
|
||||
m_currentRequest->appendRawData (line);
|
||||
if (m_currentRequest->getRawDataSize () == m_currentRequest->getHeader (QtHttpHeader::ContentLength).toInt ()) {
|
||||
m_parsingStatus = RequestParsed;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: { break; }
|
||||
}
|
||||
switch (m_parsingStatus) { // handle parsing status end/error
|
||||
case RequestParsed: { // a valid request has ben fully parsed
|
||||
// Catch websocket header "Upgrade"
|
||||
if(m_currentRequest->getHeader(QtHttpHeader::Upgrade) == "websocket")
|
||||
{
|
||||
if(m_websocketClient == Q_NULLPTR)
|
||||
{
|
||||
// disconnect this slot from socket for further requests
|
||||
disconnect(m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
|
||||
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// add post data to request and catch /jsonrpc subroute url
|
||||
if ( m_currentRequest->getCommand() == "POST")
|
||||
{
|
||||
QtHttpPostData postData;
|
||||
QByteArray data = m_currentRequest->getRawData();
|
||||
QList<QByteArray> parts = data.split('&');
|
||||
for (int i = 0; i < parts.size(); ++i)
|
||||
{
|
||||
QList<QByteArray> keyValue = parts.at(i).split('=');
|
||||
QByteArray value;
|
||||
if (keyValue.size()>1)
|
||||
{
|
||||
value = QByteArray::fromPercentEncoding(keyValue.at(1));
|
||||
}
|
||||
postData.insert(QString::fromUtf8(keyValue.at(0)),value);
|
||||
}
|
||||
m_currentRequest->setPostData(postData);
|
||||
|
||||
// catch /jsonrpc in url, we need async callback, StaticFileServing is sync
|
||||
QString path = m_currentRequest->getUrl ().path ();
|
||||
QStringList uri_parts = path.split('/', QString::SkipEmptyParts);
|
||||
if ( ! uri_parts.empty() && uri_parts.at(0) == "json-rpc" )
|
||||
{
|
||||
if(m_webJsonRpc == Q_NULLPTR)
|
||||
{
|
||||
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, this);
|
||||
}
|
||||
m_webJsonRpc->handleMessage(m_currentRequest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QtHttpReply reply (m_serverHandle);
|
||||
connect (&reply, &QtHttpReply::requestSendHeaders,
|
||||
this, &QtHttpClientWrapper::onReplySendHeadersRequested);
|
||||
connect (&reply, &QtHttpReply::requestSendData,
|
||||
this, &QtHttpClientWrapper::onReplySendDataRequested);
|
||||
emit m_serverHandle->requestNeedsReply (m_currentRequest, &reply); // allow app to handle request
|
||||
m_parsingStatus = sendReplyToClient (&reply);
|
||||
break;
|
||||
}
|
||||
case ParsingError: { // there was an error durin one of parsing steps
|
||||
m_sockClient->readAll (); // clear remaining buffer to ignore content
|
||||
QtHttpReply reply (m_serverHandle);
|
||||
reply.setStatusCode (QtHttpReply::BadRequest);
|
||||
reply.appendRawData (QByteArrayLiteral ("<h1>Bad Request (HTTP parsing error) !</h1>"));
|
||||
reply.appendRawData (CRLF);
|
||||
m_parsingStatus = sendReplyToClient (&reply);
|
||||
break;
|
||||
}
|
||||
default: { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpClientWrapper::onReplySendHeadersRequested (void) {
|
||||
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
|
||||
if (reply != Q_NULLPTR) {
|
||||
QByteArray data;
|
||||
// HTTP Version + Status Code + Status Msg
|
||||
data.append (QtHttpServer::HTTP_VERSION);
|
||||
data.append (SPACE);
|
||||
data.append (QByteArray::number (reply->getStatusCode ()));
|
||||
data.append (SPACE);
|
||||
data.append (QtHttpReply::getStatusTextForCode (reply->getStatusCode ()));
|
||||
data.append (CRLF);
|
||||
// Header name: header value
|
||||
if (reply->useChunked ()) {
|
||||
static const QByteArray & CHUNKED = QByteArrayLiteral ("chunked");
|
||||
reply->addHeader (QtHttpHeader::TransferEncoding, CHUNKED);
|
||||
}
|
||||
else {
|
||||
reply->addHeader (QtHttpHeader::ContentLength, QByteArray::number (reply->getRawDataSize ()));
|
||||
}
|
||||
const QList<QByteArray> & headersList = reply->getHeadersList ();
|
||||
foreach (const QByteArray & header, headersList) {
|
||||
data.append (header);
|
||||
data.append (COLON);
|
||||
data.append (SPACE);
|
||||
data.append (reply->getHeader (header));
|
||||
data.append (CRLF);
|
||||
}
|
||||
// empty line
|
||||
data.append (CRLF);
|
||||
m_sockClient->write (data);
|
||||
m_sockClient->flush ();
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpClientWrapper::onReplySendDataRequested (void) {
|
||||
QtHttpReply * reply = qobject_cast<QtHttpReply *> (sender ());
|
||||
if (reply != Q_NULLPTR) {
|
||||
// content raw data
|
||||
QByteArray data = reply->getRawData ();
|
||||
if (reply->useChunked ()) {
|
||||
data.prepend (QByteArray::number (data.size (), 16) % CRLF);
|
||||
data.append (CRLF);
|
||||
reply->resetRawData ();
|
||||
}
|
||||
// write to socket
|
||||
m_sockClient->write (data);
|
||||
m_sockClient->flush ();
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpClientWrapper::sendToClientWithReply(QtHttpReply * reply) {
|
||||
connect (reply, &QtHttpReply::requestSendHeaders,
|
||||
this, &QtHttpClientWrapper::onReplySendHeadersRequested);
|
||||
connect (reply, &QtHttpReply::requestSendData,
|
||||
this, &QtHttpClientWrapper::onReplySendDataRequested);
|
||||
m_parsingStatus = sendReplyToClient (reply);
|
||||
}
|
||||
|
||||
QtHttpClientWrapper::ParsingStatus QtHttpClientWrapper::sendReplyToClient (QtHttpReply * reply) {
|
||||
if (reply != Q_NULLPTR) {
|
||||
if (!reply->useChunked ()) {
|
||||
//reply->appendRawData (CRLF);
|
||||
// send all headers and all data in one shot
|
||||
reply->requestSendHeaders ();
|
||||
reply->requestSendData ();
|
||||
}
|
||||
else {
|
||||
// last chunk
|
||||
m_sockClient->write ("0" % CRLF % CRLF);
|
||||
m_sockClient->flush ();
|
||||
}
|
||||
if (m_currentRequest != Q_NULLPTR) {
|
||||
static const QByteArray & CLOSE = QByteArrayLiteral ("close");
|
||||
if (m_currentRequest->getHeader (QtHttpHeader::Connection).toLower () == CLOSE) {
|
||||
// must close connection after this request
|
||||
m_sockClient->close ();
|
||||
}
|
||||
m_currentRequest->deleteLater ();
|
||||
m_currentRequest = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
return AwaitingRequest;
|
||||
}
|
57
libsrc/webserver/QtHttpClientWrapper.h
Normal file
57
libsrc/webserver/QtHttpClientWrapper.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef QTHTTPCLIENTWRAPPER_H
|
||||
#define QTHTTPCLIENTWRAPPER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QtHttpRequest;
|
||||
class QtHttpReply;
|
||||
class QtHttpServer;
|
||||
class WebSocketClient;
|
||||
class WebJsonRpc;
|
||||
|
||||
class QtHttpClientWrapper : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * parent);
|
||||
|
||||
static const char SPACE = ' ';
|
||||
static const char COLON = ':';
|
||||
static const QByteArray & CRLF;
|
||||
|
||||
enum ParsingStatus {
|
||||
ParsingError = -1,
|
||||
AwaitingRequest = 0,
|
||||
AwaitingHeaders = 1,
|
||||
AwaitingContent = 2,
|
||||
RequestParsed = 3
|
||||
};
|
||||
|
||||
QString getGuid (void);
|
||||
/// @brief Wrapper for sendReplyToClient(), handles m_parsingStatus and signal connect
|
||||
void sendToClientWithReply (QtHttpReply * reply);
|
||||
|
||||
private slots:
|
||||
void onClientDataReceived (void);
|
||||
|
||||
protected:
|
||||
ParsingStatus sendReplyToClient (QtHttpReply * reply);
|
||||
|
||||
protected slots:
|
||||
void onReplySendHeadersRequested (void);
|
||||
void onReplySendDataRequested (void);
|
||||
|
||||
private:
|
||||
QString m_guid;
|
||||
ParsingStatus m_parsingStatus;
|
||||
QTcpSocket * m_sockClient;
|
||||
QtHttpRequest * m_currentRequest;
|
||||
QtHttpServer * m_serverHandle;
|
||||
WebSocketClient * m_websocketClient = nullptr;
|
||||
WebJsonRpc * m_webJsonRpc = nullptr;
|
||||
};
|
||||
|
||||
#endif // QTHTTPCLIENTWRAPPER_H
|
36
libsrc/webserver/QtHttpHeader.cpp
Normal file
36
libsrc/webserver/QtHttpHeader.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
#include "QtHttpHeader.h"
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
const QByteArray & QtHttpHeader::Server = QByteArrayLiteral ("Server");
|
||||
const QByteArray & QtHttpHeader::Date = QByteArrayLiteral ("Date");
|
||||
const QByteArray & QtHttpHeader::Host = QByteArrayLiteral ("Host");
|
||||
const QByteArray & QtHttpHeader::Accept = QByteArrayLiteral ("Accept");
|
||||
const QByteArray & QtHttpHeader::Cookie = QByteArrayLiteral ("Cookie");
|
||||
const QByteArray & QtHttpHeader::ContentType = QByteArrayLiteral ("Content-Type");
|
||||
const QByteArray & QtHttpHeader::ContentLength = QByteArrayLiteral ("Content-Length");
|
||||
const QByteArray & QtHttpHeader::Connection = QByteArrayLiteral ("Connection");
|
||||
const QByteArray & QtHttpHeader::UserAgent = QByteArrayLiteral ("User-Agent");
|
||||
const QByteArray & QtHttpHeader::AcceptCharset = QByteArrayLiteral ("Accept-Charset");
|
||||
const QByteArray & QtHttpHeader::AcceptEncoding = QByteArrayLiteral ("Accept-Encoding");
|
||||
const QByteArray & QtHttpHeader::AcceptLanguage = QByteArrayLiteral ("Accept-Language");
|
||||
const QByteArray & QtHttpHeader::Authorization = QByteArrayLiteral ("Authorization");
|
||||
const QByteArray & QtHttpHeader::CacheControl = QByteArrayLiteral ("Cache-Control");
|
||||
const QByteArray & QtHttpHeader::ContentMD5 = QByteArrayLiteral ("Content-MD5");
|
||||
const QByteArray & QtHttpHeader::ProxyAuthorization = QByteArrayLiteral ("Proxy-Authorization");
|
||||
const QByteArray & QtHttpHeader::Range = QByteArrayLiteral ("Range");
|
||||
const QByteArray & QtHttpHeader::ContentEncoding = QByteArrayLiteral ("Content-Encoding");
|
||||
const QByteArray & QtHttpHeader::ContentLanguage = QByteArrayLiteral ("Content-Language");
|
||||
const QByteArray & QtHttpHeader::ContentLocation = QByteArrayLiteral ("Content-Location");
|
||||
const QByteArray & QtHttpHeader::ContentRange = QByteArrayLiteral ("Content-Range");
|
||||
const QByteArray & QtHttpHeader::Expires = QByteArrayLiteral ("Expires");
|
||||
const QByteArray & QtHttpHeader::LastModified = QByteArrayLiteral ("Last-Modified");
|
||||
const QByteArray & QtHttpHeader::Location = QByteArrayLiteral ("Location");
|
||||
const QByteArray & QtHttpHeader::SetCookie = QByteArrayLiteral ("Set-Cookie");
|
||||
const QByteArray & QtHttpHeader::TransferEncoding = QByteArrayLiteral ("Transfer-Encoding");
|
||||
const QByteArray & QtHttpHeader::ContentDisposition = QByteArrayLiteral ("Content-Disposition");
|
||||
const QByteArray & QtHttpHeader::Upgrade = QByteArrayLiteral ("Upgrade");
|
||||
const QByteArray & QtHttpHeader::SecWebSocketKey = QByteArrayLiteral ("Sec-WebSocket-Key");
|
||||
const QByteArray & QtHttpHeader::SecWebSocketProtocol = QByteArrayLiteral ("Sec-WebSocket-Protocol");
|
||||
const QByteArray & QtHttpHeader::SecWebSocketVersion = QByteArrayLiteral ("Sec-WebSocket-Version");
|
42
libsrc/webserver/QtHttpHeader.h
Normal file
42
libsrc/webserver/QtHttpHeader.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef QTHTTPHEADER_H
|
||||
#define QTHTTPHEADER_H
|
||||
|
||||
class QByteArray;
|
||||
|
||||
class QtHttpHeader {
|
||||
public:
|
||||
static const QByteArray & Server;
|
||||
static const QByteArray & Date;
|
||||
static const QByteArray & Host;
|
||||
static const QByteArray & Accept;
|
||||
static const QByteArray & ContentType;
|
||||
static const QByteArray & ContentLength;
|
||||
static const QByteArray & Connection;
|
||||
static const QByteArray & Cookie;
|
||||
static const QByteArray & UserAgent;
|
||||
static const QByteArray & AcceptCharset;
|
||||
static const QByteArray & AcceptEncoding;
|
||||
static const QByteArray & AcceptLanguage;
|
||||
static const QByteArray & Authorization;
|
||||
static const QByteArray & CacheControl;
|
||||
static const QByteArray & ContentMD5;
|
||||
static const QByteArray & ProxyAuthorization;
|
||||
static const QByteArray & Range;
|
||||
static const QByteArray & ContentEncoding;
|
||||
static const QByteArray & ContentLanguage;
|
||||
static const QByteArray & ContentLocation;
|
||||
static const QByteArray & ContentRange;
|
||||
static const QByteArray & Expires;
|
||||
static const QByteArray & LastModified;
|
||||
static const QByteArray & Location;
|
||||
static const QByteArray & SetCookie;
|
||||
static const QByteArray & TransferEncoding;
|
||||
static const QByteArray & ContentDisposition;
|
||||
// Websocket specific headers
|
||||
static const QByteArray & Upgrade;
|
||||
static const QByteArray & SecWebSocketKey;
|
||||
static const QByteArray & SecWebSocketProtocol;
|
||||
static const QByteArray & SecWebSocketVersion;
|
||||
};
|
||||
|
||||
#endif // QTHTTPHEADER_H
|
75
libsrc/webserver/QtHttpReply.cpp
Normal file
75
libsrc/webserver/QtHttpReply.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include "QtHttpServer.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
QtHttpReply::QtHttpReply (QtHttpServer * parent)
|
||||
: QObject (parent)
|
||||
, m_useChunked (false)
|
||||
, m_statusCode (Ok)
|
||||
, m_data (QByteArray ())
|
||||
, m_serverHandle (parent)
|
||||
{
|
||||
// set some additional headers
|
||||
addHeader (QtHttpHeader::Date, QDateTime::currentDateTimeUtc ().toString ("ddd, dd MMM yyyy hh:mm:ss t").toUtf8 ());
|
||||
addHeader (QtHttpHeader::Server, m_serverHandle->getServerName ().toUtf8 ());
|
||||
}
|
||||
|
||||
int QtHttpReply::getRawDataSize (void) const {
|
||||
return m_data.size ();
|
||||
}
|
||||
|
||||
bool QtHttpReply::useChunked (void) const {
|
||||
return m_useChunked;
|
||||
}
|
||||
|
||||
QtHttpReply::StatusCode QtHttpReply::getStatusCode (void) const {
|
||||
return m_statusCode;
|
||||
}
|
||||
|
||||
QByteArray QtHttpReply::getRawData (void) const {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
QList<QByteArray> QtHttpReply::getHeadersList (void) const {
|
||||
return m_headersHash.keys ();
|
||||
}
|
||||
|
||||
QByteArray QtHttpReply::getHeader (const QByteArray & header) const {
|
||||
return m_headersHash.value (header, QByteArray ());
|
||||
}
|
||||
|
||||
const QByteArray QtHttpReply::getStatusTextForCode (QtHttpReply::StatusCode statusCode) {
|
||||
switch (statusCode) {
|
||||
case Ok: return QByteArrayLiteral ("OK.");
|
||||
case BadRequest: return QByteArrayLiteral ("Bad request !");
|
||||
case Forbidden: return QByteArrayLiteral ("Forbidden !");
|
||||
case NotFound: return QByteArrayLiteral ("Not found !");
|
||||
default: return QByteArrayLiteral ("");
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpReply::setUseChunked (bool chunked){
|
||||
m_useChunked = chunked;
|
||||
}
|
||||
|
||||
void QtHttpReply::setStatusCode (QtHttpReply::StatusCode statusCode) {
|
||||
m_statusCode = statusCode;
|
||||
}
|
||||
|
||||
void QtHttpReply::appendRawData (const QByteArray & data) {
|
||||
m_data.append (data);
|
||||
}
|
||||
|
||||
void QtHttpReply::addHeader (const QByteArray & header, const QByteArray & value) {
|
||||
QByteArray key = header.trimmed ();
|
||||
if (!key.isEmpty ()) {
|
||||
m_headersHash.insert (key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpReply::resetRawData (void) {
|
||||
m_data.clear ();
|
||||
}
|
60
libsrc/webserver/QtHttpReply.h
Normal file
60
libsrc/webserver/QtHttpReply.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef QTHTTPREPLY_H
|
||||
#define QTHTTPREPLY_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
|
||||
class QtHttpServer;
|
||||
|
||||
class QtHttpReply : public QObject {
|
||||
Q_OBJECT
|
||||
Q_ENUMS (StatusCode)
|
||||
|
||||
public:
|
||||
explicit QtHttpReply (QtHttpServer * parent);
|
||||
|
||||
enum StatusCode {
|
||||
Ok = 200,
|
||||
SeeOther = 303,
|
||||
BadRequest = 400,
|
||||
Forbidden = 403,
|
||||
NotFound = 404,
|
||||
MethodNotAllowed = 405,
|
||||
InternalError = 500,
|
||||
NotImplemented = 501,
|
||||
BadGateway = 502,
|
||||
ServiceUnavailable = 503,
|
||||
};
|
||||
|
||||
int getRawDataSize (void) const;
|
||||
bool useChunked (void) const;
|
||||
StatusCode getStatusCode (void) const;
|
||||
QByteArray getRawData (void) const;
|
||||
QList<QByteArray> getHeadersList (void) const;
|
||||
|
||||
QByteArray getHeader (const QByteArray & header) const;
|
||||
|
||||
static const QByteArray getStatusTextForCode (StatusCode statusCode);
|
||||
|
||||
public slots:
|
||||
void setUseChunked (bool chunked = false);
|
||||
void setStatusCode (StatusCode statusCode);
|
||||
void appendRawData (const QByteArray & data);
|
||||
void addHeader (const QByteArray & header, const QByteArray & value);
|
||||
void resetRawData (void);
|
||||
|
||||
signals:
|
||||
void requestSendHeaders (void);
|
||||
void requestSendData (void);
|
||||
|
||||
private:
|
||||
bool m_useChunked;
|
||||
StatusCode m_statusCode;
|
||||
QByteArray m_data;
|
||||
QtHttpServer * m_serverHandle;
|
||||
QHash<QByteArray, QByteArray> m_headersHash;
|
||||
};
|
||||
|
||||
#endif // QTHTTPREPLY_H
|
83
libsrc/webserver/QtHttpRequest.cpp
Normal file
83
libsrc/webserver/QtHttpRequest.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include "QtHttpServer.h"
|
||||
|
||||
QtHttpRequest::QtHttpRequest (QtHttpClientWrapper * client, QtHttpServer * parent)
|
||||
: QObject (parent)
|
||||
, m_url (QUrl ())
|
||||
, m_command (QString ())
|
||||
, m_data (QByteArray ())
|
||||
, m_serverHandle (parent)
|
||||
, m_clientHandle (client)
|
||||
, m_postData (QtHttpPostData())
|
||||
{
|
||||
// set some additional headers
|
||||
addHeader (QtHttpHeader::ContentLength, QByteArrayLiteral ("0"));
|
||||
addHeader (QtHttpHeader::Connection, QByteArrayLiteral ("Keep-Alive"));
|
||||
}
|
||||
|
||||
QUrl QtHttpRequest::getUrl (void) const {
|
||||
return m_url;
|
||||
}
|
||||
|
||||
QString QtHttpRequest::getCommand (void) const {
|
||||
return m_command;
|
||||
}
|
||||
|
||||
QtHttpRequest::ClientInfo QtHttpRequest::getClientInfo (void) const {
|
||||
return m_clientInfo;
|
||||
}
|
||||
|
||||
int QtHttpRequest::getRawDataSize (void) const {
|
||||
return m_data.size ();
|
||||
}
|
||||
|
||||
|
||||
QByteArray QtHttpRequest::getRawData (void) const {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
QtHttpPostData QtHttpRequest::getPostData (void) const {
|
||||
return m_postData;
|
||||
}
|
||||
|
||||
QList<QByteArray> QtHttpRequest::getHeadersList (void) const {
|
||||
return m_headersHash.keys ();
|
||||
}
|
||||
|
||||
QtHttpClientWrapper * QtHttpRequest::getClient (void) const {
|
||||
return m_clientHandle;
|
||||
}
|
||||
|
||||
QByteArray QtHttpRequest::getHeader (const QByteArray & header) const {
|
||||
return m_headersHash.value (header, QByteArray ());
|
||||
}
|
||||
|
||||
void QtHttpRequest::setUrl (const QUrl & url) {
|
||||
m_url = url;
|
||||
}
|
||||
|
||||
void QtHttpRequest::setCommand (const QString & command) {
|
||||
m_command = command;
|
||||
}
|
||||
|
||||
void QtHttpRequest::setClientInfo (const QHostAddress & server, const QHostAddress & client) {
|
||||
m_clientInfo.serverAddress = server;
|
||||
m_clientInfo.clientAddress = client;
|
||||
}
|
||||
|
||||
void QtHttpRequest::addHeader (const QByteArray & header, const QByteArray & value) {
|
||||
QByteArray key = header.trimmed ();
|
||||
if (!key.isEmpty ()) {
|
||||
m_headersHash.insert (key, value);
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpRequest::appendRawData (const QByteArray & data) {
|
||||
m_data.append (data);
|
||||
}
|
||||
|
||||
void QtHttpRequest::setPostData (const QtHttpPostData & data) {
|
||||
m_postData = data;
|
||||
}
|
59
libsrc/webserver/QtHttpRequest.h
Normal file
59
libsrc/webserver/QtHttpRequest.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef QTHTTPREQUEST_H
|
||||
#define QTHTTPREQUEST_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QUrl>
|
||||
#include <QHostAddress>
|
||||
#include <QMap>
|
||||
|
||||
class QtHttpServer;
|
||||
class QtHttpClientWrapper;
|
||||
|
||||
using QtHttpPostData = QMap<QString,QByteArray>;
|
||||
|
||||
class QtHttpRequest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpRequest (QtHttpClientWrapper * client, QtHttpServer * parent);
|
||||
|
||||
struct ClientInfo {
|
||||
QHostAddress serverAddress;
|
||||
QHostAddress clientAddress;
|
||||
};
|
||||
|
||||
int getRawDataSize (void) const;
|
||||
QUrl getUrl (void) const;
|
||||
QString getCommand (void) const;
|
||||
QByteArray getRawData (void) const;
|
||||
QList<QByteArray> getHeadersList (void) const;
|
||||
QtHttpClientWrapper * getClient (void) const;
|
||||
|
||||
QByteArray getHeader (const QByteArray & header) const;
|
||||
QtHttpPostData getPostData (void) const;
|
||||
|
||||
ClientInfo getClientInfo (void) const;
|
||||
|
||||
public slots:
|
||||
void setUrl (const QUrl & url);
|
||||
void setCommand (const QString & command);
|
||||
void setClientInfo (const QHostAddress & server, const QHostAddress & client);
|
||||
void addHeader (const QByteArray & header, const QByteArray & value);
|
||||
void appendRawData (const QByteArray & data);
|
||||
void setPostData (const QtHttpPostData & data);
|
||||
|
||||
private:
|
||||
QUrl m_url;
|
||||
QString m_command;
|
||||
QByteArray m_data;
|
||||
QtHttpServer * m_serverHandle;
|
||||
QtHttpClientWrapper * m_clientHandle;
|
||||
QHash<QByteArray, QByteArray> m_headersHash;
|
||||
ClientInfo m_clientInfo;
|
||||
QtHttpPostData m_postData;
|
||||
};
|
||||
|
||||
#endif // QTHTTPREQUEST_H
|
136
libsrc/webserver/QtHttpServer.cpp
Normal file
136
libsrc/webserver/QtHttpServer.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
|
||||
#include "QtHttpServer.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpClientWrapper.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
const QString & QtHttpServer::HTTP_VERSION = QStringLiteral ("HTTP/1.1");
|
||||
|
||||
QtHttpServerWrapper::QtHttpServerWrapper (QObject * parent)
|
||||
: QTcpServer (parent)
|
||||
, m_useSsl (false)
|
||||
{ }
|
||||
|
||||
QtHttpServerWrapper::~QtHttpServerWrapper (void) { }
|
||||
|
||||
void QtHttpServerWrapper::setUseSecure (const bool ssl) {
|
||||
m_useSsl = ssl;
|
||||
}
|
||||
|
||||
void QtHttpServerWrapper::incomingConnection (qintptr handle) {
|
||||
QTcpSocket * sock = (m_useSsl
|
||||
? new QSslSocket (this)
|
||||
: new QTcpSocket (this));
|
||||
if (sock->setSocketDescriptor (handle)) {
|
||||
addPendingConnection (sock);
|
||||
}
|
||||
else {
|
||||
delete sock;
|
||||
}
|
||||
}
|
||||
|
||||
QtHttpServer::QtHttpServer (QObject * parent)
|
||||
: QObject (parent)
|
||||
, m_useSsl (false)
|
||||
, m_serverName (QStringLiteral ("The Qt5 HTTP Server"))
|
||||
{
|
||||
m_sockServer = new QtHttpServerWrapper (this);
|
||||
connect (m_sockServer, &QtHttpServerWrapper::newConnection, this, &QtHttpServer::onClientConnected);
|
||||
}
|
||||
|
||||
const QString & QtHttpServer::getServerName (void) const {
|
||||
return m_serverName;
|
||||
}
|
||||
|
||||
quint16 QtHttpServer::getServerPort (void) const {
|
||||
return m_sockServer->serverPort ();
|
||||
}
|
||||
|
||||
QString QtHttpServer::getErrorString (void) const {
|
||||
return m_sockServer->errorString ();
|
||||
}
|
||||
|
||||
void QtHttpServer::start (quint16 port) {
|
||||
if(!m_sockServer->isListening())
|
||||
{
|
||||
if (m_sockServer->listen (QHostAddress::Any, port)) {
|
||||
emit started (m_sockServer->serverPort ());
|
||||
}
|
||||
else {
|
||||
emit error (m_sockServer->errorString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::stop (void) {
|
||||
if (m_sockServer->isListening ()) {
|
||||
m_sockServer->close ();
|
||||
emit stopped ();
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::setServerName (const QString & serverName) {
|
||||
m_serverName = serverName;
|
||||
}
|
||||
|
||||
void QtHttpServer::setUseSecure (const bool ssl) {
|
||||
m_useSsl = ssl;
|
||||
m_sockServer->setUseSecure (m_useSsl);
|
||||
}
|
||||
|
||||
void QtHttpServer::setPrivateKey (const QSslKey & key) {
|
||||
m_sslKey = key;
|
||||
}
|
||||
|
||||
void QtHttpServer::setCertificates (const QList<QSslCertificate> & certs) {
|
||||
m_sslCerts = certs;
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientConnected (void) {
|
||||
while (m_sockServer->hasPendingConnections ()) {
|
||||
if (QTcpSocket * sock = m_sockServer->nextPendingConnection ()) {
|
||||
connect (sock, &QTcpSocket::disconnected, this, &QtHttpServer::onClientDisconnected);
|
||||
if (m_useSsl) {
|
||||
if (QSslSocket * ssl = qobject_cast<QSslSocket *> (sock)) {
|
||||
connect (ssl, SslErrorSignal (&QSslSocket::sslErrors), this, &QtHttpServer::onClientSslErrors);
|
||||
connect (ssl, &QSslSocket::encrypted, this, &QtHttpServer::onClientSslEncrypted);
|
||||
connect (ssl, &QSslSocket::peerVerifyError, this, &QtHttpServer::onClientSslPeerVerifyError);
|
||||
connect (ssl, &QSslSocket::modeChanged, this, &QtHttpServer::onClientSslModeChanged);
|
||||
ssl->setLocalCertificateChain (m_sslCerts);
|
||||
ssl->setPrivateKey (m_sslKey);
|
||||
ssl->setPeerVerifyMode (QSslSocket::AutoVerifyPeer);
|
||||
ssl->startServerEncryption ();
|
||||
}
|
||||
}
|
||||
QtHttpClientWrapper * wrapper = new QtHttpClientWrapper (sock, this);
|
||||
m_socksClientsHash.insert (sock, wrapper);
|
||||
emit clientConnected (wrapper->getGuid ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslEncrypted (void) { }
|
||||
|
||||
void QtHttpServer::onClientSslPeerVerifyError (const QSslError & err) {
|
||||
Q_UNUSED (err)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslErrors (const QList<QSslError> & errors) {
|
||||
Q_UNUSED (errors)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientSslModeChanged (QSslSocket::SslMode mode) {
|
||||
Q_UNUSED (mode)
|
||||
}
|
||||
|
||||
void QtHttpServer::onClientDisconnected (void) {
|
||||
if (QTcpSocket * sockClient = qobject_cast<QTcpSocket *> (sender ())) {
|
||||
if (QtHttpClientWrapper * wrapper = m_socksClientsHash.value (sockClient, Q_NULLPTR)) {
|
||||
emit clientDisconnected (wrapper->getGuid ());
|
||||
wrapper->deleteLater ();
|
||||
m_socksClientsHash.remove (sockClient);
|
||||
}
|
||||
}
|
||||
}
|
86
libsrc/webserver/QtHttpServer.h
Normal file
86
libsrc/webserver/QtHttpServer.h
Normal file
@@ -0,0 +1,86 @@
|
||||
#ifndef QTHTTPSERVER_H
|
||||
#define QTHTTPSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSslSocket>
|
||||
#include <QHostAddress>
|
||||
|
||||
class QTcpSocket;
|
||||
class QTcpServer;
|
||||
|
||||
class QtHttpRequest;
|
||||
class QtHttpReply;
|
||||
class QtHttpClientWrapper;
|
||||
|
||||
class QtHttpServerWrapper : public QTcpServer {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpServerWrapper (QObject * parent = Q_NULLPTR);
|
||||
virtual ~QtHttpServerWrapper (void);
|
||||
|
||||
void setUseSecure (const bool ssl = true);
|
||||
|
||||
protected:
|
||||
void incomingConnection (qintptr handle) Q_DECL_OVERRIDE;
|
||||
|
||||
private:
|
||||
bool m_useSsl;
|
||||
};
|
||||
|
||||
class QtHttpServer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtHttpServer (QObject * parent = Q_NULLPTR);
|
||||
|
||||
static const QString & HTTP_VERSION;
|
||||
|
||||
typedef void (QSslSocket::* SslErrorSignal) (const QList<QSslError> &);
|
||||
|
||||
const QString & getServerName (void) const;
|
||||
|
||||
quint16 getServerPort (void) const;
|
||||
QString getErrorString (void) const;
|
||||
|
||||
public slots:
|
||||
void start (quint16 port = 0);
|
||||
void stop (void);
|
||||
void setServerName (const QString & serverName);
|
||||
void setUseSecure (const bool ssl = true);
|
||||
void setPrivateKey (const QSslKey & key);
|
||||
void setCertificates (const QList<QSslCertificate> & certs);
|
||||
|
||||
signals:
|
||||
void started (quint16 port);
|
||||
void stopped (void);
|
||||
void error (const QString & msg);
|
||||
void clientConnected (const QString & guid);
|
||||
void clientDisconnected (const QString & guid);
|
||||
void requestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
|
||||
|
||||
private slots:
|
||||
void onClientConnected (void);
|
||||
void onClientDisconnected (void);
|
||||
void onClientSslEncrypted (void);
|
||||
void onClientSslPeerVerifyError (const QSslError & err);
|
||||
void onClientSslErrors (const QList<QSslError> & errors);
|
||||
void onClientSslModeChanged (QSslSocket::SslMode mode);
|
||||
|
||||
private:
|
||||
bool m_useSsl;
|
||||
QSslKey m_sslKey;
|
||||
QList<QSslCertificate> m_sslCerts;
|
||||
QString m_serverName;
|
||||
QtHttpServerWrapper * m_sockServer;
|
||||
QHash<QTcpSocket *, QtHttpClientWrapper *> m_socksClientsHash;
|
||||
};
|
||||
|
||||
#endif // QTHTTPSERVER_H
|
||||
|
139
libsrc/webserver/StaticFileServing.cpp
Normal file
139
libsrc/webserver/StaticFileServing.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
|
||||
#include "StaticFileServing.h"
|
||||
|
||||
#include <QStringBuilder>
|
||||
#include <QUrlQuery>
|
||||
#include <QList>
|
||||
#include <QPair>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QResource>
|
||||
#include <exception>
|
||||
|
||||
StaticFileServing::StaticFileServing (Hyperion *hyperion, QObject * parent)
|
||||
: QObject (parent)
|
||||
, _hyperion(hyperion)
|
||||
, _baseUrl ()
|
||||
, _cgi(hyperion, this)
|
||||
, _log(Logger::getInstance("WEBSERVER"))
|
||||
{
|
||||
Q_INIT_RESOURCE(WebConfig);
|
||||
|
||||
_mimeDb = new QMimeDatabase;
|
||||
}
|
||||
|
||||
StaticFileServing::~StaticFileServing ()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StaticFileServing::setBaseUrl(const QString& url)
|
||||
{
|
||||
_baseUrl = url;
|
||||
_cgi.setBaseUrl(url);
|
||||
}
|
||||
|
||||
void StaticFileServing::printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage)
|
||||
{
|
||||
reply->setStatusCode(code);
|
||||
reply->addHeader ("Content-Type", QByteArrayLiteral ("text/html"));
|
||||
QFile errorPageHeader(_baseUrl % "/errorpages/header.html" );
|
||||
QFile errorPageFooter(_baseUrl % "/errorpages/footer.html" );
|
||||
QFile errorPage (_baseUrl % "/errorpages/" % QString::number((int)code) % ".html" );
|
||||
|
||||
if (errorPageHeader.open (QFile::ReadOnly))
|
||||
{
|
||||
QByteArray data = errorPageHeader.readAll();
|
||||
reply->appendRawData (data);
|
||||
errorPageHeader.close ();
|
||||
}
|
||||
|
||||
if (errorPage.open (QFile::ReadOnly))
|
||||
{
|
||||
QByteArray data = errorPage.readAll();
|
||||
data = data.replace("{MESSAGE}", errorMessage.toLocal8Bit() );
|
||||
reply->appendRawData (data);
|
||||
errorPage.close ();
|
||||
}
|
||||
else
|
||||
{
|
||||
reply->appendRawData (QString(QString::number(code) + " - " +errorMessage).toLocal8Bit());
|
||||
}
|
||||
|
||||
if (errorPageFooter.open (QFile::ReadOnly))
|
||||
{
|
||||
QByteArray data = errorPageFooter.readAll ();
|
||||
reply->appendRawData (data);
|
||||
errorPageFooter.close ();
|
||||
}
|
||||
}
|
||||
|
||||
void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply)
|
||||
{
|
||||
QString command = request->getCommand ();
|
||||
if (command == QStringLiteral ("GET"))
|
||||
{
|
||||
QString path = request->getUrl ().path ();
|
||||
QStringList uri_parts = path.split('/', QString::SkipEmptyParts);
|
||||
|
||||
// special uri handling for server commands
|
||||
if ( ! uri_parts.empty() && uri_parts.at(0) == "cgi" )
|
||||
{
|
||||
uri_parts.removeAt(0);
|
||||
try
|
||||
{
|
||||
_cgi.exec(uri_parts, request, reply);
|
||||
}
|
||||
catch(int err)
|
||||
{
|
||||
Error(_log,"Exception while executing cgi %s : %d", path.toStdString().c_str(), err);
|
||||
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
Error(_log,"Exception while executing cgi %s : %s", path.toStdString().c_str(), e.what());
|
||||
printErrorToReply (reply, QtHttpReply::InternalError, "script failed (" % path % ")");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo info(_baseUrl % "/" % path);
|
||||
if ( path == "/" || path.isEmpty() )
|
||||
{
|
||||
path = "index.html";
|
||||
}
|
||||
else if (info.isDir() && path.endsWith("/") )
|
||||
{
|
||||
path += "index.html";
|
||||
}
|
||||
else if (info.isDir() && ! path.endsWith("/") )
|
||||
{
|
||||
path += "/index.html";
|
||||
}
|
||||
|
||||
// get static files
|
||||
QFile file(_baseUrl % "/" % path);
|
||||
if (file.exists())
|
||||
{
|
||||
QMimeType mime = _mimeDb->mimeTypeForFile (file.fileName ());
|
||||
if (file.open (QFile::ReadOnly)) {
|
||||
QByteArray data = file.readAll ();
|
||||
reply->addHeader ("Content-Type", mime.name ().toLocal8Bit ());
|
||||
reply->appendRawData (data);
|
||||
file.close ();
|
||||
}
|
||||
else
|
||||
{
|
||||
printErrorToReply (reply, QtHttpReply::Forbidden ,"Requested file: " % path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printErrorToReply (reply, QtHttpReply::NotFound, "Requested file: " % path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printErrorToReply (reply, QtHttpReply::MethodNotAllowed,"Unhandled HTTP/1.1 method " % command);
|
||||
}
|
||||
}
|
38
libsrc/webserver/StaticFileServing.h
Normal file
38
libsrc/webserver/StaticFileServing.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef STATICFILESERVING_H
|
||||
#define STATICFILESERVING_H
|
||||
|
||||
#include <QMimeDatabase>
|
||||
|
||||
//#include "QtHttpServer.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpHeader.h"
|
||||
#include "CgiHandler.h"
|
||||
|
||||
#include <hyperion/Hyperion.h>
|
||||
#include <utils/Logger.h>
|
||||
|
||||
class StaticFileServing : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit StaticFileServing (Hyperion *hyperion, QObject * parent = nullptr);
|
||||
virtual ~StaticFileServing (void);
|
||||
|
||||
void setBaseUrl(const QString& url);
|
||||
|
||||
public slots:
|
||||
void onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply);
|
||||
|
||||
private:
|
||||
Hyperion * _hyperion;
|
||||
QString _baseUrl;
|
||||
QMimeDatabase * _mimeDb;
|
||||
CgiHandler _cgi;
|
||||
Logger * _log;
|
||||
|
||||
void printErrorToReply (QtHttpReply * reply, QtHttpReply::StatusCode code, QString errorMessage);
|
||||
|
||||
};
|
||||
|
||||
#endif // STATICFILESERVING_H
|
5
libsrc/webserver/WebConfig.qrc.in
Normal file
5
libsrc/webserver/WebConfig.qrc.in
Normal file
@@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
${HYPERION_WEBCONFIG_RES}
|
||||
</qresource>
|
||||
</RCC>
|
38
libsrc/webserver/WebJsonRpc.cpp
Normal file
38
libsrc/webserver/WebJsonRpc.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "WebJsonRpc.h"
|
||||
#include "QtHttpReply.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpServer.h"
|
||||
#include "QtHttpClientWrapper.h"
|
||||
|
||||
#include <api/JsonAPI.h>
|
||||
|
||||
WebJsonRpc::WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent)
|
||||
: QObject(parent)
|
||||
, _server(server)
|
||||
, _wrapper(parent)
|
||||
, _log(Logger::getInstance("HTTPJSONRPC"))
|
||||
{
|
||||
const QString client = request->getClientInfo().clientAddress.toString();
|
||||
_jsonAPI = new JsonAPI(client, _log, this, true);
|
||||
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebJsonRpc::handleCallback);
|
||||
}
|
||||
|
||||
void WebJsonRpc::handleMessage(QtHttpRequest* request)
|
||||
{
|
||||
QByteArray data = request->getRawData();
|
||||
_unlocked = true;
|
||||
_jsonAPI->handleMessage(data);
|
||||
}
|
||||
|
||||
void WebJsonRpc::handleCallback(QJsonObject obj)
|
||||
{
|
||||
// guard against wrong callbacks; TODO: Remove when JSONAPI is more solid
|
||||
if(!_unlocked) return;
|
||||
_unlocked = false;
|
||||
// construct reply with headers timestamp and server name
|
||||
QtHttpReply reply(_server);
|
||||
QJsonDocument doc(obj);
|
||||
reply.addHeader ("Content-Type", "application/json");
|
||||
reply.appendRawData (doc.toJson());
|
||||
_wrapper->sendToClientWithReply(&reply);
|
||||
}
|
27
libsrc/webserver/WebJsonRpc.h
Normal file
27
libsrc/webserver/WebJsonRpc.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/Logger.h>
|
||||
|
||||
class QtHttpServer;
|
||||
class QtHttpRequest;
|
||||
class QtHttpClientWrapper;
|
||||
class JsonAPI;
|
||||
|
||||
class WebJsonRpc : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebJsonRpc(QtHttpRequest* request, QtHttpServer* server, QtHttpClientWrapper* parent);
|
||||
|
||||
void handleMessage(QtHttpRequest* request);
|
||||
|
||||
private:
|
||||
QtHttpServer* _server;
|
||||
QtHttpClientWrapper* _wrapper;
|
||||
Logger* _log;
|
||||
JsonAPI* _jsonAPI;
|
||||
|
||||
bool _unlocked = false;
|
||||
|
||||
private slots:
|
||||
void handleCallback(QJsonObject obj);
|
||||
};
|
99
libsrc/webserver/WebServer.cpp
Normal file
99
libsrc/webserver/WebServer.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "webserver/WebServer.h"
|
||||
#include "StaticFileServing.h"
|
||||
#include "QtHttpServer.h"
|
||||
|
||||
// bonjour
|
||||
#include <bonjour/bonjourserviceregister.h>
|
||||
#include <bonjour/bonjourrecord.h>
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
WebServer::WebServer(const QJsonDocument& config, QObject * parent)
|
||||
: QObject(parent)
|
||||
, _log(Logger::getInstance("WEBSERVER"))
|
||||
, _hyperion(Hyperion::getInstance())
|
||||
, _server(new QtHttpServer (this))
|
||||
{
|
||||
_server->setServerName (QStringLiteral ("Hyperion Webserver"));
|
||||
|
||||
connect (_server, &QtHttpServer::started, this, &WebServer::onServerStarted);
|
||||
connect (_server, &QtHttpServer::stopped, this, &WebServer::onServerStopped);
|
||||
connect (_server, &QtHttpServer::error, this, &WebServer::onServerError);
|
||||
|
||||
// create StaticFileServing
|
||||
_staticFileServing = new StaticFileServing (_hyperion, this);
|
||||
connect(_server, &QtHttpServer::requestNeedsReply, _staticFileServing, &StaticFileServing::onRequestNeedsReply);
|
||||
|
||||
Debug(_log, "Instance created");
|
||||
// init
|
||||
handleSettingsUpdate(settings::WEBSERVER, config);
|
||||
}
|
||||
|
||||
WebServer::~WebServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void WebServer::onServerStarted (quint16 port)
|
||||
{
|
||||
Info(_log, "Started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str());
|
||||
|
||||
BonjourServiceRegister *bonjourRegister_http = new BonjourServiceRegister();
|
||||
bonjourRegister_http->registerService("_hyperiond-http._tcp", port);
|
||||
}
|
||||
|
||||
void WebServer::onServerStopped () {
|
||||
Info(_log, "Stopped %s", _server->getServerName().toStdString().c_str());
|
||||
}
|
||||
|
||||
void WebServer::onServerError (QString msg)
|
||||
{
|
||||
Error(_log, "%s", msg.toStdString().c_str());
|
||||
}
|
||||
|
||||
void WebServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
|
||||
{
|
||||
if(type == settings::WEBSERVER)
|
||||
{
|
||||
const QJsonObject& obj = config.object();
|
||||
|
||||
bool webconfigEnable = obj["enable"].toBool(true);
|
||||
_baseUrl = obj["document_root"].toString(WEBSERVER_DEFAULT_PATH);
|
||||
|
||||
|
||||
if ( (_baseUrl != ":/webconfig") && !_baseUrl.trimmed().isEmpty())
|
||||
{
|
||||
QFileInfo info(_baseUrl);
|
||||
if (!info.exists() || !info.isDir())
|
||||
{
|
||||
Error(_log, "document_root '%s' is invalid", _baseUrl.toUtf8().constData());
|
||||
_baseUrl = WEBSERVER_DEFAULT_PATH;
|
||||
}
|
||||
}
|
||||
else
|
||||
_baseUrl = WEBSERVER_DEFAULT_PATH;
|
||||
|
||||
Debug(_log, "Set document root to: %s", _baseUrl.toUtf8().constData());
|
||||
_staticFileServing->setBaseUrl(_baseUrl);
|
||||
|
||||
if(_port != obj["port"].toInt(WEBSERVER_DEFAULT_PORT))
|
||||
{
|
||||
_port = obj["port"].toInt(WEBSERVER_DEFAULT_PORT);
|
||||
stop();
|
||||
}
|
||||
if ( webconfigEnable )
|
||||
{
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebServer::start()
|
||||
{
|
||||
_server->start(_port);
|
||||
}
|
||||
|
||||
void WebServer::stop()
|
||||
{
|
||||
_server->stop();
|
||||
}
|
337
libsrc/webserver/WebSocketClient.cpp
Normal file
337
libsrc/webserver/WebSocketClient.cpp
Normal file
@@ -0,0 +1,337 @@
|
||||
#include "WebSocketClient.h"
|
||||
#include "QtHttpRequest.h"
|
||||
#include "QtHttpHeader.h"
|
||||
|
||||
#include <hyperion/Hyperion.h>
|
||||
#include <api/JsonAPI.h>
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QtEndian>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
WebSocketClient::WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent)
|
||||
: QObject(parent)
|
||||
, _socket(sock)
|
||||
, _log(Logger::getInstance("WEBSOCKET"))
|
||||
, _hyperion(Hyperion::getInstance())
|
||||
{
|
||||
// connect socket; disconnect handled from QtHttpServer
|
||||
connect(_socket, &QTcpSocket::readyRead , this, &WebSocketClient::handleWebSocketFrame);
|
||||
|
||||
// QtHttpRequest contains all headers for handshake
|
||||
QByteArray secWebSocketKey = request->getHeader(QtHttpHeader::SecWebSocketKey);
|
||||
const QString client = request->getClientInfo().clientAddress.toString();
|
||||
|
||||
// Json processor
|
||||
_jsonAPI = new JsonAPI(client, _log, this);
|
||||
connect(_jsonAPI, &JsonAPI::callbackMessage, this, &WebSocketClient::sendMessage);
|
||||
|
||||
Debug(_log, "New connection from %s", QSTRING_CSTR(client));
|
||||
|
||||
// do handshake
|
||||
secWebSocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
QByteArray hash = QCryptographicHash::hash(secWebSocketKey, QCryptographicHash::Sha1).toBase64();
|
||||
|
||||
QString data
|
||||
= QString("HTTP/1.1 101 Switching Protocols\r\n")
|
||||
+ QString("Upgrade: websocket\r\n")
|
||||
+ QString("Connection: Upgrade\r\n")
|
||||
+ QString("Sec-WebSocket-Accept: ")+QString(hash.data()) + "\r\n\r\n";
|
||||
|
||||
_socket->write(QSTRING_CSTR(data), data.size());
|
||||
_socket->flush();
|
||||
}
|
||||
|
||||
void WebSocketClient::handleWebSocketFrame(void)
|
||||
{
|
||||
// we are on no continious reading from socket from call before
|
||||
if (!_notEnoughData)
|
||||
{
|
||||
getWsFrameHeader(&_wsh);
|
||||
}
|
||||
|
||||
if(_socket->bytesAvailable() < (qint64)_wsh.payloadLength)
|
||||
{
|
||||
//printf("not enough data %llu %llu\n", _socket->bytesAvailable(), _wsh.payloadLength);
|
||||
_notEnoughData=true;
|
||||
return;
|
||||
}
|
||||
_notEnoughData = false;
|
||||
|
||||
QByteArray buf = _socket->read(_wsh.payloadLength);
|
||||
//printf("opcode %x payload bytes %llu avail: %llu\n", _wsh.opCode, _wsh.payloadLength, _socket->bytesAvailable());
|
||||
|
||||
if (OPCODE::invalid((OPCODE::value)_wsh.opCode))
|
||||
{
|
||||
sendClose(CLOSECODE::INV_TYPE, "invalid opcode");
|
||||
return;
|
||||
}
|
||||
|
||||
// check the type of data frame
|
||||
bool isContinuation=false;
|
||||
switch (_wsh.opCode)
|
||||
{
|
||||
case OPCODE::CONTINUATION:
|
||||
isContinuation = true;
|
||||
// no break here, just jump over to opcode text
|
||||
|
||||
case OPCODE::BINARY:
|
||||
case OPCODE::TEXT:
|
||||
{
|
||||
// check for protocal violations
|
||||
if (_onContinuation && !isContinuation)
|
||||
{
|
||||
sendClose(CLOSECODE::VIOLATION, "protocol violation, somebody sends frames in between continued frames");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_wsh.masked && _wsh.opCode == OPCODE::TEXT)
|
||||
{
|
||||
sendClose(CLOSECODE::VIOLATION, "protocol violation, unmasked text frames not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
// unmask data
|
||||
for (int i=0; i < buf.size(); i++)
|
||||
{
|
||||
buf[i] = buf[i] ^ _wsh.key[i % 4];
|
||||
}
|
||||
|
||||
_onContinuation = !_wsh.fin || isContinuation;
|
||||
|
||||
// frame contains text, extract it, append data if this is a continuation
|
||||
if (_wsh.fin && ! isContinuation) // one frame
|
||||
{
|
||||
_wsReceiveBuffer.clear();
|
||||
}
|
||||
_wsReceiveBuffer.append(buf);
|
||||
|
||||
// this is the final frame, decode and handle data
|
||||
if (_wsh.fin)
|
||||
{
|
||||
_onContinuation = false;
|
||||
if (_wsh.opCode == OPCODE::TEXT)
|
||||
{
|
||||
_jsonAPI->handleMessage(QString(_wsReceiveBuffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
handleBinaryMessage(_wsReceiveBuffer);
|
||||
}
|
||||
_wsReceiveBuffer.clear();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE::CLOSE:
|
||||
{
|
||||
sendClose(CLOSECODE::NORMAL);
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE::PING:
|
||||
{
|
||||
// ping received, send pong
|
||||
quint8 pong[] = {OPCODE::PONG, 0};
|
||||
_socket->write((const char*)pong, 2);
|
||||
_socket->flush();
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE::PONG:
|
||||
{
|
||||
Error(_log, "pong received, protocol violation!");
|
||||
}
|
||||
|
||||
default:
|
||||
Warning(_log, "strange %d\n%s\n", _wsh.opCode, QSTRING_CSTR(QString(buf)));
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketClient::getWsFrameHeader(WebSocketHeader* header)
|
||||
{
|
||||
char fin_rsv_opcode, mask_length;
|
||||
_socket->getChar(&fin_rsv_opcode);
|
||||
_socket->getChar(&mask_length);
|
||||
|
||||
header->fin = (fin_rsv_opcode & BHB0_FIN) == BHB0_FIN;
|
||||
header->opCode = fin_rsv_opcode & BHB0_OPCODE;
|
||||
header->masked = (mask_length & BHB1_MASK) == BHB1_MASK;
|
||||
header->payloadLength = mask_length & BHB1_PAYLOAD;
|
||||
|
||||
// get size of payload
|
||||
switch (header->payloadLength)
|
||||
{
|
||||
case payload_size_code_16bit:
|
||||
{
|
||||
QByteArray buf = _socket->read(2);
|
||||
header->payloadLength = ((buf.at(0) << 8) & 0xFF00) | (buf.at(1) & 0xFF);
|
||||
}
|
||||
break;
|
||||
|
||||
case payload_size_code_64bit:
|
||||
{
|
||||
QByteArray buf = _socket->read(8);
|
||||
header->payloadLength = 0;
|
||||
for (uint i=0; i < 8; i++)
|
||||
{
|
||||
header->payloadLength |= ((quint64)(buf.at(i) & 0xFF)) << (8*(7-i));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// if the data is masked we need to get the key for unmasking
|
||||
if (header->masked)
|
||||
{
|
||||
_socket->read(header->key, 4);
|
||||
}
|
||||
}
|
||||
|
||||
/// See http://tools.ietf.org/html/rfc6455#section-5.2 for more information
|
||||
void WebSocketClient::sendClose(int status, QString reason)
|
||||
{
|
||||
Debug(_log, "send close: %d %s", status, QSTRING_CSTR(reason));
|
||||
ErrorIf(!reason.isEmpty(), _log, QSTRING_CSTR(reason));
|
||||
_receiveBuffer.clear();
|
||||
QByteArray sendBuffer;
|
||||
|
||||
sendBuffer.append(136+(status-1000));
|
||||
int length = reason.size();
|
||||
if(length >= 126)
|
||||
{
|
||||
sendBuffer.append( (length > 0xffff) ? 127 : 126);
|
||||
int num_bytes = (length > 0xffff) ? 8 : 2;
|
||||
|
||||
for(int c = num_bytes - 1; c != -1; c--)
|
||||
{
|
||||
sendBuffer.append( quint8((static_cast<unsigned long long>(length) >> (8 * c)) % 256));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sendBuffer.append(quint8(length));
|
||||
}
|
||||
|
||||
sendBuffer.append(reason);
|
||||
|
||||
_socket->write(sendBuffer);
|
||||
_socket->flush();
|
||||
_socket->close();
|
||||
}
|
||||
|
||||
void WebSocketClient::handleBinaryMessage(QByteArray &data)
|
||||
{
|
||||
uint8_t priority = data.at(0);
|
||||
unsigned duration_s = data.at(1);
|
||||
unsigned imgSize = data.size() - 4;
|
||||
unsigned width = ((data.at(2) << 8) & 0xFF00) | (data.at(3) & 0xFF);
|
||||
unsigned height = imgSize / width;
|
||||
|
||||
if ( ! (imgSize) % width)
|
||||
{
|
||||
Error(_log, "data size is not multiple of width");
|
||||
return;
|
||||
}
|
||||
|
||||
Image<ColorRgb> image;
|
||||
image.resize(width, height);
|
||||
|
||||
memcpy(image.memptr(), data.data()+4, imgSize);
|
||||
//_hyperion->registerInput();
|
||||
_hyperion->setInputImage(priority, image, duration_s*1000);
|
||||
}
|
||||
|
||||
qint64 WebSocketClient::sendMessage(QJsonObject obj)
|
||||
{
|
||||
QJsonDocument writer(obj);
|
||||
QByteArray data = writer.toJson(QJsonDocument::Compact) + "\n";
|
||||
|
||||
if (!_socket || (_socket->state() != QAbstractSocket::ConnectedState)) return 0;
|
||||
|
||||
qint64 payloadWritten = 0;
|
||||
quint32 payloadSize = data.size();
|
||||
const char * payload = data.data();
|
||||
|
||||
qint32 numFrames = payloadSize / FRAME_SIZE_IN_BYTES + ((quint64(payloadSize) % FRAME_SIZE_IN_BYTES) > 0 ? 1 : 0);
|
||||
|
||||
for (int i = 0; i < numFrames; ++i)
|
||||
{
|
||||
const bool isLastFrame = (i == (numFrames - 1));
|
||||
|
||||
quint64 position = i * FRAME_SIZE_IN_BYTES;
|
||||
quint32 frameSize = (payloadSize-position >= FRAME_SIZE_IN_BYTES) ? FRAME_SIZE_IN_BYTES : (payloadSize-position);
|
||||
|
||||
QByteArray buf = makeFrameHeader(OPCODE::TEXT, frameSize, isLastFrame);
|
||||
sendMessage_Raw(buf);
|
||||
|
||||
qint64 written = sendMessage_Raw(payload+position,frameSize);
|
||||
if (written > 0)
|
||||
{
|
||||
payloadWritten += written;
|
||||
}
|
||||
else
|
||||
{
|
||||
_socket->flush();
|
||||
Error(_log, "Error writing bytes to socket: %s", QSTRING_CSTR(_socket->errorString()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (payloadSize != payloadWritten)
|
||||
{
|
||||
Error(_log, "Error writing bytes to socket %d bytes from %d written", payloadWritten, payloadSize);
|
||||
return -1;
|
||||
}
|
||||
return payloadWritten;
|
||||
}
|
||||
|
||||
qint64 WebSocketClient::sendMessage_Raw(const char* data, quint64 size)
|
||||
{
|
||||
return _socket->write(data, size);
|
||||
}
|
||||
|
||||
qint64 WebSocketClient::sendMessage_Raw(QByteArray &data)
|
||||
{
|
||||
return _socket->write(data.data(), data.size());
|
||||
}
|
||||
|
||||
|
||||
QByteArray WebSocketClient::makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame)
|
||||
{
|
||||
QByteArray header;
|
||||
|
||||
if (payloadLength <= 0x7FFFFFFFFFFFFFFFULL)
|
||||
{
|
||||
//FIN, RSV1-3, opcode (RSV-1, RSV-2 and RSV-3 are zero)
|
||||
quint8 byte = static_cast<quint8>((opCode & 0x0F) | (lastFrame ? 0x80 : 0x00));
|
||||
header.append(static_cast<char>(byte));
|
||||
|
||||
byte = 0x00;
|
||||
if (payloadLength <= 125)
|
||||
{
|
||||
byte |= static_cast<quint8>(payloadLength);
|
||||
header.append(static_cast<char>(byte));
|
||||
}
|
||||
else if (payloadLength <= 0xFFFFU)
|
||||
{
|
||||
byte |= 126;
|
||||
header.append(static_cast<char>(byte));
|
||||
quint16 swapped = qToBigEndian<quint16>(static_cast<quint16>(payloadLength));
|
||||
header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte |= 127;
|
||||
header.append(static_cast<char>(byte));
|
||||
quint64 swapped = qToBigEndian<quint64>(payloadLength);
|
||||
header.append(static_cast<const char *>(static_cast<const void *>(&swapped)), 8);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Error(_log, "Payload too big!");
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
72
libsrc/webserver/WebSocketClient.h
Normal file
72
libsrc/webserver/WebSocketClient.h
Normal file
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/Logger.h>
|
||||
#include "WebSocketUtils.h"
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QtHttpRequest;
|
||||
class Hyperion;
|
||||
class JsonAPI;
|
||||
|
||||
class WebSocketClient : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WebSocketClient(QtHttpRequest* request, QTcpSocket* sock, QObject* parent);
|
||||
|
||||
struct WebSocketHeader
|
||||
{
|
||||
bool fin;
|
||||
quint8 opCode;
|
||||
bool masked;
|
||||
quint64 payloadLength;
|
||||
char key[4];
|
||||
};
|
||||
|
||||
private:
|
||||
QTcpSocket* _socket;
|
||||
Logger* _log;
|
||||
Hyperion* _hyperion;
|
||||
JsonAPI* _jsonAPI;
|
||||
|
||||
void getWsFrameHeader(WebSocketHeader* header);
|
||||
void sendClose(int status, QString reason = "");
|
||||
void handleBinaryMessage(QByteArray &data);
|
||||
qint64 sendMessage_Raw(const char* data, quint64 size);
|
||||
qint64 sendMessage_Raw(QByteArray &data);
|
||||
QByteArray makeFrameHeader(quint8 opCode, quint64 payloadLength, bool lastFrame);
|
||||
|
||||
/// The buffer used for reading data from the socket
|
||||
QByteArray _receiveBuffer;
|
||||
|
||||
/// buffer for websockets multi frame receive
|
||||
QByteArray _wsReceiveBuffer;
|
||||
quint8 _maskKey[4];
|
||||
|
||||
bool _onContinuation = false;
|
||||
|
||||
// true when data is missing for parsing
|
||||
bool _notEnoughData = false;
|
||||
|
||||
// websocket header store
|
||||
WebSocketHeader _wsh;
|
||||
|
||||
// masks for fields in the basic header
|
||||
static uint8_t const BHB0_OPCODE = 0x0F;
|
||||
static uint8_t const BHB0_RSV3 = 0x10;
|
||||
static uint8_t const BHB0_RSV2 = 0x20;
|
||||
static uint8_t const BHB0_RSV1 = 0x40;
|
||||
static uint8_t const BHB0_FIN = 0x80;
|
||||
|
||||
static uint8_t const BHB1_PAYLOAD = 0x7F;
|
||||
static uint8_t const BHB1_MASK = 0x80;
|
||||
|
||||
static uint8_t const payload_size_code_16bit = 0x7E; // 126
|
||||
static uint8_t const payload_size_code_64bit = 0x7F; // 127
|
||||
|
||||
static const quint64 FRAME_SIZE_IN_BYTES = 512 * 512 * 2; //maximum size of a frame when sending a message
|
||||
|
||||
private slots:
|
||||
void handleWebSocketFrame(void);
|
||||
qint64 sendMessage(QJsonObject obj);
|
||||
};
|
68
libsrc/webserver/WebSocketUtils.h
Normal file
68
libsrc/webserver/WebSocketUtils.h
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
/// Constants and utility functions related to WebSocket opcodes
|
||||
/**
|
||||
* WebSocket Opcodes are 4 bits. See RFC6455 section 5.2.
|
||||
*/
|
||||
namespace OPCODE {
|
||||
enum value {
|
||||
CONTINUATION = 0x0,
|
||||
TEXT = 0x1,
|
||||
BINARY = 0x2,
|
||||
RSV3 = 0x3,
|
||||
RSV4 = 0x4,
|
||||
RSV5 = 0x5,
|
||||
RSV6 = 0x6,
|
||||
RSV7 = 0x7,
|
||||
CLOSE = 0x8,
|
||||
PING = 0x9,
|
||||
PONG = 0xA,
|
||||
CONTROL_RSVB = 0xB,
|
||||
CONTROL_RSVC = 0xC,
|
||||
CONTROL_RSVD = 0xD,
|
||||
CONTROL_RSVE = 0xE,
|
||||
CONTROL_RSVF = 0xF
|
||||
};
|
||||
|
||||
/// Check if an opcode is reserved
|
||||
/**
|
||||
* @param v The opcode to test.
|
||||
* @return Whether or not the opcode is reserved.
|
||||
*/
|
||||
inline bool reserved(value v) {
|
||||
return (v >= RSV3 && v <= RSV7) || (v >= CONTROL_RSVB && v <= CONTROL_RSVF);
|
||||
}
|
||||
|
||||
/// Check if an opcode is invalid
|
||||
/**
|
||||
* Invalid opcodes are negative or require greater than 4 bits to store.
|
||||
*
|
||||
* @param v The opcode to test.
|
||||
* @return Whether or not the opcode is invalid.
|
||||
*/
|
||||
inline bool invalid(value v) {
|
||||
return (v > 0xF || v < 0);
|
||||
}
|
||||
|
||||
/// Check if an opcode is for a control frame
|
||||
/**
|
||||
* @param v The opcode to test.
|
||||
* @return Whether or not the opcode is a control opcode.
|
||||
*/
|
||||
inline bool is_control(value v) {
|
||||
return v >= 0x8;
|
||||
}
|
||||
}
|
||||
|
||||
namespace CLOSECODE {
|
||||
enum value {
|
||||
NORMAL = 1000,
|
||||
AWAY = 1001,
|
||||
TERM = 1002,
|
||||
INV_TYPE = 1003,
|
||||
INV_DATA = 1007,
|
||||
VIOLATION = 1008,
|
||||
BIG_MSG = 1009,
|
||||
UNEXPECTED= 1011
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user