hyperion.ng/libsrc/webserver/QtHttpClientWrapper.cpp
LordGrey 7389068a66
LED Device Features, Fixes and Refactoring (Resubmit PR855) (#875)
* Refactor LedDevices - Initial version
* Small renamings
* Add WLED as own device
* Lpd8806 Remove open() method
* remove dependency on Qt 5.10
* Lpd8806 Remove open() method
* Update WS281x
* Update WS2812SPI
* Add writeBlack for WLED powerOff
* WLED remove extra bracket
* Allow different Nanoleaf panel numbering sequence (Feature req.#827)
* build(deps): bump websocket-extensions from 0.1.3 to 0.1.4 in /docs (#826)
* Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
  - [Release notes](https://github.com/faye/websocket-extensions-node/releases)
  - [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
  - [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)
* Fix typos
* Nanoleaf clean-up
* Yeelight support, generalize wizard elements
* Update Yeelight to handle quota in music mode
* Yeelight extend rage for extraTimeDarkness for testing
* Clean-up - Add commentary, Remove development debug statements
* Fix brightnessSwitchOffOnMinimum typo and default value
* Yeelight support restoreOriginalState, additional Fixes
* WLED - Remove UDP-Port, as it is not configurable
* Fix merging issue
* Remove QHostAddress::operator=(const QString&)' is deprecated
* Windows compile errors and (Qt 5.15 deprecation) warnings
* Fix order includes
* LedDeviceFile Support Qt5.7 and greater
* Windows compatibility and other Fixes
* Fix Qt Version compatability
* Rs232 - Resolve portname from unix /dev/ style, fix DMX sub-type support
* Disable WLED Wizard Button (until Wizard is available)
* Yeelight updates
* Add wrong log-type as per #505
* Fixes and Clean-up after clang-tidy report
* Fix udpe131 not enabled for generated CID
* Change timer into dynamic for Qt Thread-Affinity
* Hue clean-up and diyHue workaround
* Updates after review feedback by m-seker
* Add "chrono" includes
2020-07-12 20:27:56 +02:00

359 lines
9.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <utils/QStringUtils.h>
#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, const bool& localConnection, QtHttpServer * parent)
: QObject (parent)
, m_guid ("")
, m_parsingStatus (AwaitingRequest)
, m_sockClient (sock)
, m_currentRequest (Q_NULLPTR)
, m_serverHandle (parent)
, m_localConnection(localConnection)
, m_websocketClient(nullptr)
, m_webJsonRpc (nullptr)
{
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 = QStringUtils::split(str,SPACE, QStringUtils::SplitBehavior::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);
// disabling packet bunching
m_sockClient->setSocketOption(QAbstractSocket::LowDelayOption, 1);
m_sockClient->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
m_websocketClient = new WebSocketClient(m_currentRequest, m_sockClient, m_localConnection, 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 = QStringUtils::split(path,'/', QStringUtils::SplitBehavior::SkipEmptyParts);
if ( ! uri_parts.empty() && uri_parts.at(0) == "json-rpc" )
{
if(m_webJsonRpc == Q_NULLPTR)
{
m_webJsonRpc = new WebJsonRpc(m_currentRequest, m_serverHandle, m_localConnection, 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);
if (reply->useChunked ()) // Header name: header value
{
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, Qt::UniqueConnection);
connect (reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested, Qt::UniqueConnection);
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;
}
void QtHttpClientWrapper::closeConnection()
{
// probably filter for request to follow http spec
if(m_currentRequest != Q_NULLPTR)
{
QtHttpReply reply(m_serverHandle);
reply.setStatusCode(QtHttpReply::StatusCode::Forbidden);
connect (&reply, &QtHttpReply::requestSendHeaders, this, &QtHttpClientWrapper::onReplySendHeadersRequested, Qt::UniqueConnection);
connect (&reply, &QtHttpReply::requestSendData, this, &QtHttpClientWrapper::onReplySendDataRequested, Qt::UniqueConnection);
m_parsingStatus = sendReplyToClient(&reply);
}
m_sockClient->close ();
}