// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE using namespace Qt::Literals; static const QByteArray headerServerString = "Server"_ba; static const QByteArray headerServerValue = "Test server"_ba; class tst_QHttpServerResponder : public QObject { Q_OBJECT std::unique_ptr networkAccessManager; private slots: void init() { networkAccessManager.reset(new QNetworkAccessManager); } void cleanup() { networkAccessManager.reset(); } void defaultStatusCodeNoParameters(); void defaultStatusCodeByteArray(); void defaultStatusCodeJson(); void writeStatusCode_data(); void writeStatusCode(); void writeStatusCodeExtraHeader(); void writeJson(); void writeJsonExtraHeader(); void writeFile_data(); void writeFile(); void writeFileExtraHeader(); void writeByteArrayExtraHeader(); }; #define qWaitForFinished(REPLY) QVERIFY(QSignalSpy(REPLY, &QNetworkReply::finished).wait()) struct HttpServer : QAbstractHttpServer { std::function handleRequestFunction; QUrl url; HttpServer(decltype(handleRequestFunction) function) : handleRequestFunction(function) { auto tcpserver = std::make_unique(); tcpserver->listen(); quint16 port = tcpserver->serverPort(); bind(tcpserver.get()); tcpserver.release(); url.setUrl(QStringLiteral("http://localhost:%1").arg(port)); } bool handleRequest(const QHttpServerRequest &, QHttpServerResponder &) override; void missingHandler(const QHttpServerRequest &, QHttpServerResponder &&) override { Q_ASSERT(false); } }; bool HttpServer::handleRequest(const QHttpServerRequest &, QHttpServerResponder &responder) { handleRequestFunction(std::move(responder)); return true; } void tst_QHttpServerResponder::defaultStatusCodeNoParameters() { HttpServer server([](QHttpServerResponder responder) { responder.write(); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); qWaitForFinished(reply); QCOMPARE(reply->error(), QNetworkReply::NoError); } void tst_QHttpServerResponder::defaultStatusCodeByteArray() { HttpServer server([](QHttpServerResponder responder) { responder.write(QByteArray(), "application/x-empty"_ba); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); qWaitForFinished(reply); QCOMPARE(reply->error(), QNetworkReply::NoError); } void tst_QHttpServerResponder::defaultStatusCodeJson() { const auto json = QJsonDocument::fromJson("{}"_ba); HttpServer server([json](QHttpServerResponder responder) { responder.write(json); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); qWaitForFinished(reply); QCOMPARE(reply->error(), QNetworkReply::NoError); } void tst_QHttpServerResponder::writeStatusCode_data() { using StatusCode = QHttpServerResponder::StatusCode; QTest::addColumn("statusCode"); QTest::addColumn("networkError"); QTest::addRow("OK") << StatusCode::Ok << QNetworkReply::NoError; QTest::addRow("Content Access Denied") << StatusCode::Forbidden << QNetworkReply::ContentAccessDenied; QTest::addRow("Connection Refused") << StatusCode::NotFound << QNetworkReply::ContentNotFoundError; } void tst_QHttpServerResponder::writeStatusCode() { QFETCH(QHttpServerResponder::StatusCode, statusCode); QFETCH(QNetworkReply::NetworkError, networkError); HttpServer server([statusCode](QHttpServerResponder responder) { responder.write(statusCode); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); qWaitForFinished(reply); QCOMPARE(reply->bytesAvailable(), 0); QCOMPARE(reply->error(), networkError); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), "application/x-empty"_ba); } void tst_QHttpServerResponder::writeStatusCodeExtraHeader() { HttpServer server([=](QHttpServerResponder responder) { QHttpHeaders headers; headers.append(headerServerString, headerServerValue); responder.write(headers); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); qWaitForFinished(reply); QCOMPARE(reply->bytesAvailable(), 0); QCOMPARE(reply->error(), QNetworkReply::NoError); QCOMPARE(reply->header(QNetworkRequest::ServerHeader).toByteArray(), headerServerValue); } void tst_QHttpServerResponder::writeJson() { const auto json = QJsonDocument::fromJson(R"JSON({ "key" : "value" })JSON"_ba); HttpServer server([json](QHttpServerResponder responder) { responder.write(json); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); qWaitForFinished(reply); QCOMPARE(reply->error(), QNetworkReply::NoError); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), "application/json"_ba); QCOMPARE(QJsonDocument::fromJson(reply->readAll()), json); } void tst_QHttpServerResponder::writeJsonExtraHeader() { const auto json = QJsonDocument::fromJson(R"JSON({ "key" : "value" })JSON"_ba); HttpServer server([json](QHttpServerResponder responder) { QHttpHeaders headers; headers.append(headerServerString, headerServerValue); responder.write(json, headers); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); qWaitForFinished(reply); QCOMPARE(reply->error(), QNetworkReply::NoError); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), "application/json"_ba); QCOMPARE(reply->header(QNetworkRequest::ServerHeader).toByteArray(), headerServerValue); QCOMPARE(QJsonDocument::fromJson(reply->readAll()), json); } void tst_QHttpServerResponder::writeFile_data() { QTest::addColumn("iodevice"); QTest::addColumn("code"); QTest::addColumn("type"); QTest::addColumn("data"); QTest::addRow("index.html") << qobject_cast(new QFile(QFINDTESTDATA("data/index.html"), this)) << 200 << "text/html" << ""; QTest::addRow("index1.html - not found") << qobject_cast(new QFile("./index1.html", this)) << 500 << "application/x-empty" << QString(); QTest::addRow("temporary file") << qobject_cast(new QTemporaryFile(this)) << 200 << "text/html" << QString(); } void tst_QHttpServerResponder::writeFile() { QFETCH(QIODevice *, iodevice); QFETCH(int, code); QFETCH(QString, type); QFETCH(QString, data); QSignalSpy spyDestroyIoDevice(iodevice, &QObject::destroyed); HttpServer server([&iodevice](QHttpServerResponder responder) { responder.write(iodevice, "text/html"_ba); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); QTRY_VERIFY(reply->isFinished()); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader), type); QCOMPARE(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(), code); QCOMPARE(reply->readAll().trimmed(), data); QCOMPARE(spyDestroyIoDevice.size(), 1); } void tst_QHttpServerResponder::writeFileExtraHeader() { auto file = new QFile(QFINDTESTDATA("data/index.html"), this); QSignalSpy spyDestroyIoDevice(file, &QObject::destroyed); HttpServer server([=](QHttpServerResponder responder) { QHttpHeaders headers; headers.append(QHttpHeaders::WellKnownHeader::ContentType, "text/html"_ba); headers.append(headerServerString, headerServerValue); responder.write(file, headers); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); QTRY_VERIFY(reply->isFinished()); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), "text/html"_ba); QCOMPARE(reply->header(QNetworkRequest::ServerHeader).toByteArray(), headerServerValue); QCOMPARE(reply->readAll().trimmed(), ""); QCOMPARE(spyDestroyIoDevice.size(), 1); } void tst_QHttpServerResponder::writeByteArrayExtraHeader() { const QByteArray data("test data"); const QByteArray contentType("text/plain"); HttpServer server([=](QHttpServerResponder responder) { QHttpHeaders headers; headers.append(QHttpHeaders::WellKnownHeader::ContentType, contentType); headers.append(headerServerString, headerServerValue); responder.write(data, headers); }); auto reply = networkAccessManager->get(QNetworkRequest(server.url)); QTRY_VERIFY(reply->isFinished()); QCOMPARE(reply->header(QNetworkRequest::ContentTypeHeader).toByteArray(), contentType); QCOMPARE(reply->header(QNetworkRequest::ServerHeader).toByteArray(), headerServerValue); QCOMPARE(reply->readAll(), data); } QT_END_NAMESPACE QTEST_MAIN(tst_QHttpServerResponder) #include "tst_qhttpserverresponder.moc"