qt6-bb10/tests/auto/network/access/qnetworkreply_local/minihttpserver.h

251 lines
7.3 KiB
C++

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef MINIHTTPSERVER_H
#define MINIHTTPSERVER_H
#include <QtNetwork/qtnetworkglobal.h>
#include <QtNetwork/qtcpserver.h>
#include <QtNetwork/qtcpsocket.h>
#if QT_CONFIG(ssl)
# include <QtNetwork/qsslsocket.h>
#endif
#if QT_CONFIG(localserver)
# include <QtNetwork/qlocalserver.h>
# include <QtNetwork/qlocalsocket.h>
#endif
#include <QtCore/qpointer.h>
#include <QtCore/qhash.h>
#include <utility>
static inline QByteArray default200Response()
{
return QByteArrayLiteral("HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Content-Length: 12\r\n"
"\r\n"
"Hello World!");
}
class MiniHttpServerV2 : public QObject
{
Q_OBJECT
public:
struct State;
#if QT_CONFIG(localserver)
void bind(QLocalServer *server)
{
Q_ASSERT(!localServer);
localServer = server;
connect(server, &QLocalServer::newConnection, this,
&MiniHttpServerV2::incomingLocalConnection);
}
#endif
void bind(QTcpServer *server)
{
Q_ASSERT(!tcpServer);
tcpServer = server;
connect(server, &QTcpServer::pendingConnectionAvailable, this,
&MiniHttpServerV2::incomingConnection);
}
void setDataToTransmit(QByteArray data) { dataToTransmit = std::move(data); }
void clearServerState()
{
auto copy = std::exchange(clientStates, {});
for (auto [socket, _] : copy.asKeyValueRange()) {
if (auto *tcpSocket = qobject_cast<QTcpSocket *>(socket))
tcpSocket->disconnectFromHost();
#if QT_CONFIG(localserver)
else if (auto *localSocket = qobject_cast<QLocalSocket *>(socket))
localSocket->disconnectFromServer();
#endif
else
Q_UNREACHABLE_RETURN();
socket->deleteLater();
}
}
bool hasPendingConnections() const
{
return
#if QT_CONFIG(localserver)
(localServer && localServer->hasPendingConnections()) ||
#endif
(tcpServer && tcpServer->hasPendingConnections());
}
QString addressForScheme(QStringView scheme) const
{
using namespace Qt::StringLiterals;
if (scheme.startsWith("unix"_L1) || scheme.startsWith("local"_L1)) {
#if QT_CONFIG(localserver)
if (localServer)
return localServer->serverName();
#endif
} else if (scheme == "http"_L1) {
if (tcpServer)
return "%1:%2"_L1.arg(tcpServer->serverAddress().toString(),
QString::number(tcpServer->serverPort()));
}
return {};
}
QList<State> peerStates() const { return clientStates.values(); }
protected:
#if QT_CONFIG(localserver)
void incomingLocalConnection()
{
auto *socket = localServer->nextPendingConnection();
connectSocketSignals(socket);
}
#endif
void incomingConnection()
{
auto *socket = tcpServer->nextPendingConnection();
connectSocketSignals(socket);
}
void reply(QIODevice *socket)
{
Q_ASSERT(socket);
if (dataToTransmit.isEmpty()) {
emit socket->bytesWritten(0); // emulate having written the data
return;
}
if (!stopTransfer)
socket->write(dataToTransmit);
}
private:
void connectSocketSignals(QIODevice *socket)
{
connect(socket, &QIODevice::readyRead, this, [this, socket]() { readyReadSlot(socket); });
connect(socket, &QIODevice::bytesWritten, this,
[this, socket]() { bytesWrittenSlot(socket); });
#if QT_CONFIG(ssl)
if (auto *sslSocket = qobject_cast<QSslSocket *>(socket))
connect(sslSocket, &QSslSocket::sslErrors, this, &MiniHttpServerV2::slotSslErrors);
#endif
if (auto *tcpSocket = qobject_cast<QTcpSocket *>(socket)) {
connect(tcpSocket, &QAbstractSocket::errorOccurred, this, &MiniHttpServerV2::slotError);
#if QT_CONFIG(localserver)
} else if (auto *localSocket = qobject_cast<QLocalSocket *>(socket)) {
connect(localSocket, &QLocalSocket::errorOccurred, this,
[this](QLocalSocket::LocalSocketError error) {
slotError(QAbstractSocket::SocketError(error));
});
#endif
} else {
Q_UNREACHABLE_RETURN();
}
}
void parseContentLength(State &st, QByteArrayView header)
{
qsizetype index = header.indexOf("\r\ncontent-length:");
if (index == -1)
return;
st.foundContentLength = true;
index += sizeof("\r\ncontent-length:") - 1;
const auto *end = std::find(header.cbegin() + index, header.cend(), '\r');
QByteArrayView num = header.mid(index, std::distance(header.cbegin() + index, end));
bool ok = false;
st.contentLength = num.toInt(&ok);
if (!ok)
st.contentLength = -1;
}
private slots:
#if QT_CONFIG(ssl)
void slotSslErrors(const QList<QSslError> &errors)
{
QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender());
Q_ASSERT(currentClient);
qDebug() << "slotSslErrors" << currentClient->errorString() << errors;
}
#endif
void slotError(QAbstractSocket::SocketError err)
{
QTcpSocket *currentClient = qobject_cast<QTcpSocket *>(sender());
Q_ASSERT(currentClient);
qDebug() << "slotError" << err << currentClient->errorString();
}
public slots:
void readyReadSlot(QIODevice *socket)
{
if (stopTransfer)
return;
State &st = clientStates[socket];
st.receivedData += socket->readAll();
const qsizetype doubleEndlPos = st.receivedData.indexOf("\r\n\r\n");
if (doubleEndlPos != -1) {
const qsizetype endOfHeader = doubleEndlPos + 4;
st.contentRead = st.receivedData.size() - endOfHeader;
if (!st.checkedContentLength) {
parseContentLength(st, QByteArrayView(st.receivedData).first(endOfHeader));
st.checkedContentLength = true;
}
if (st.contentRead < st.contentLength)
return;
// multiple requests incoming, remove the bytes of the current one
if (multiple)
st.receivedData.remove(0, endOfHeader);
reply(socket);
}
}
void bytesWrittenSlot(QIODevice *socket)
{
// Disconnect and delete in next cycle (else Windows clients will fail with
// RemoteHostClosedError).
if (doClose && socket->bytesToWrite() == 0) {
disconnect(socket, nullptr, this, nullptr);
socket->deleteLater();
}
}
private:
QByteArray dataToTransmit = default200Response();
QTcpServer *tcpServer = nullptr;
#if QT_CONFIG(localserver)
QLocalServer *localServer = nullptr;
#endif
QHash<QIODevice *, State> clientStates;
public:
struct State
{
QByteArray receivedData;
qsizetype contentLength = 0;
qsizetype contentRead = 0;
bool checkedContentLength = false;
bool foundContentLength = false;
};
bool doClose = true;
bool multiple = false;
bool stopTransfer = false;
};
#endif // MINIHTTPSERVER_H