mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
The slot in the websocket client will now run through until there are no more data in the buffer
Signed-off-by: Paulchen-Panther <Paulchen-Panter@protonmail.com>
This commit is contained in:
@@ -27,93 +27,131 @@ QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, const bool& localCo
|
||||
, m_websocketClient(nullptr)
|
||||
, m_webJsonRpc (nullptr)
|
||||
{
|
||||
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
|
||||
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;
|
||||
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
|
||||
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")
|
||||
{
|
||||
@@ -121,146 +159,184 @@ void QtHttpClientWrapper::onClientDataReceived (void) {
|
||||
{
|
||||
// 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);
|
||||
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, 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
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::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::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);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user