qthttpserver/tests/auto/qhttpserverresponder/tst_qhttpserverresponder.cpp
Mikhail Svetkin 2e7a824526 Refactor QHttpServerResponder::write(QIODevice, ...)
The current implementation has several problems.

1. The function takes an ownership the QIODevice and puts it into a smartpointer.
Also we conntected socket's destroyed signal to lambda which has captured the
smartpointer.
So if responder does not find the file, the smartpointer will
call deleteLater which then will call socket::destroyed and then lambda
will be called and try to access the smartpointer which does not exist
anymore.

2. The function takes an ownership the file(QIODevice)
and puts it into a smartpointer.
Also we conntected the QTemporaryFile's aboutToClose signal to lambda which has captured the
smartpointer.
So when the QTemporaryFile calls destructor it will emit aboutToClose
signal which will call the lambda and this lambda will try to access the smartpointer
which does not exist anymore.

3. If we send a file smaller than chunksize,
IOChunkedTransfer we will never be deleted.

4. Does not check a socket connection type (keep-alive).

5. Does not send anything if file is not found or opened in wrong
mode.

Change-Id: I699e7d5a462c4b8d195908747bf0386132b19973
Reviewed-by: Jesus Fernandez <Jesus.Fernandez@qt.io>
2018-10-04 08:12:29 +00:00

212 lines
7.8 KiB
C++

/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtHttpServer/qhttpserverresponder.h>
#include <QtHttpServer/qabstracthttpserver.h>
#include <QtCore/qjsondocument.h>
#include <QtCore/qfile.h>
#include <QtCore/qtemporaryfile.h>
#include <QtTest/qsignalspy.h>
#include <QtTest/qtest.h>
#include <QtNetwork/qnetworkaccessmanager.h>
#include <functional>
QT_BEGIN_NAMESPACE
class tst_QHttpServerResponder : public QObject
{
Q_OBJECT
std::unique_ptr<QNetworkAccessManager> 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 writeJson();
void writeFile_data();
void writeFile();
};
#define qWaitForFinished(REPLY) QVERIFY(QSignalSpy(REPLY, &QNetworkReply::finished).wait())
struct HttpServer : QAbstractHttpServer {
std::function<void(QHttpServerResponder responder)> handleRequestFunction;
QUrl url { QStringLiteral("http://localhost:%1").arg(listen()) };
HttpServer(decltype(handleRequestFunction) function) : handleRequestFunction(function) {}
bool handleRequest(const QHttpServerRequest &request, QTcpSocket *socket) override;
};
bool HttpServer::handleRequest(const QHttpServerRequest &request, QTcpSocket *socket)
{
handleRequestFunction(makeResponder(request, socket));
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(), QByteArrayLiteral("application/x-empty"));
});
auto reply = networkAccessManager->get(QNetworkRequest(server.url));
qWaitForFinished(reply);
QCOMPARE(reply->error(), QNetworkReply::NoError);
}
void tst_QHttpServerResponder::defaultStatusCodeJson()
{
const auto json = QJsonDocument::fromJson(QByteArrayLiteral("{}"));
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<QHttpServerResponder::StatusCode>("statusCode");
QTest::addColumn<QNetworkReply::NetworkError>("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),
QByteArrayLiteral("application/x-empty"));
QCOMPARE(reply->header(QNetworkRequest::ServerHeader), QStringLiteral("%1/%2(%3)")
.arg(QCoreApplication::instance()->applicationName())
.arg(QCoreApplication::instance()->applicationVersion())
.arg(QSysInfo::prettyProductName()).toUtf8());
}
void tst_QHttpServerResponder::writeJson()
{
const auto json = QJsonDocument::fromJson(QByteArrayLiteral(R"JSON({ "key" : "value" })JSON"));
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), QByteArrayLiteral("text/json"));
QCOMPARE(QJsonDocument::fromJson(reply->readAll()), json);
}
void tst_QHttpServerResponder::writeFile_data()
{
QTest::addColumn<QIODevice *>("iodevice");
QTest::addColumn<int>("code");
QTest::addColumn<QString>("type");
QTest::addColumn<QString>("data");
QTest::addRow("index.html")
<< qobject_cast<QIODevice *>(new QFile(QFINDTESTDATA("index.html"), this))
<< 200
<< "text/html"
<< "<html>\n</html>\n";
QTest::addRow("index1.html - not found")
<< qobject_cast<QIODevice *>(new QFile("./index1.html", this))
<< 500
<< "application/x-empty"
<< "";
QTest::addRow("temporary file")
<< qobject_cast<QIODevice *>(new QTemporaryFile(this))
<< 200
<< "text/html"
<< "";
}
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");
});
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(), data);
QCOMPARE(spyDestroyIoDevice.count(), 1);
}
QT_END_NAMESPACE
QTEST_MAIN(tst_QHttpServerResponder)
#include "tst_qhttpserverresponder.moc"