blob: 57fa5a613c4e7acc0aaf85d74121bd9837f09fdf [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** 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-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include "private/qhttpnetworkconnection_p.h"
#include "private/qnoncontiguousbytedevice_p.h"
#include <QAuthenticator>
#include <QTcpServer>
#include "../../../network-settings.h"
class tst_QHttpNetworkConnection: public QObject
{
Q_OBJECT
public Q_SLOTS:
void finishedReply();
void finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail);
void challenge401(const QHttpNetworkRequest &request, QAuthenticator *authenticator);
#ifndef QT_NO_SSL
void sslErrors(const QList<QSslError> &errors);
#endif
private:
bool finishedCalled;
bool finishedWithErrorCalled;
QNetworkReply::NetworkError netErrorCode;
QString (*httpServerName)() = QtNetworkSettings::httpServerName;
private Q_SLOTS:
void initTestCase();
void options_data();
void options();
void get_data();
void get();
void head_data();
void head();
void post_data();
void post();
void put_data();
void put();
void _delete_data();
void _delete();
void trace_data();
void trace();
void _connect_data();
void _connect();
#ifndef QT_NO_COMPRESS
void compression_data();
void compression();
#endif
#ifndef QT_NO_SSL
void ignoresslerror_data();
void ignoresslerror();
#endif
#ifdef QT_NO_SSL
void nossl_data();
void nossl();
#endif
void get401_data();
void get401();
void getMultiple_data();
void getMultiple();
void getMultipleWithPipeliningAndMultiplePriorities();
void getMultipleWithPriorities();
void getEmptyWithPipelining();
void getAndEverythingShouldBePipelined();
void getAndThenDeleteObject();
void getAndThenDeleteObject_data();
void overlappingCloseAndWrite();
};
void tst_QHttpNetworkConnection::initTestCase()
{
#if defined(QT_TEST_SERVER)
QVERIFY(QtNetworkSettings::verifyConnection(httpServerName(), 80));
#else
if (!QtNetworkSettings::verifyTestNetworkSettings())
QSKIP("No network test server available");
#endif
}
void tst_QHttpNetworkConnection::options_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::options()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::head_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 200 << "OK" << 25962;
QTest::newRow("failure-path") << "http://" << httpServerName() << "/t" << ushort(80) << false << 404 << "Not Found" << -1;
QTest::newRow("failure-protocol") << "" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 400 << "Bad Request" << -1;
}
void tst_QHttpNetworkConnection::head()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Head);
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
// only check it if it is set and expected
if (reply->contentLength() != -1 && contentLength != -1)
QCOMPARE(reply->contentLength(), qint64(contentLength));
QVERIFY(reply->isFinished());
delete reply;
}
void tst_QHttpNetworkConnection::get_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::addColumn<int>("downloadSize");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 200 << "OK" << 25962 << 25962;
QTest::newRow("failure-path") << "http://" << httpServerName() << "/t" << ushort(80) << false << 404 << "Not Found" << -1 << -1;
QTest::newRow("failure-protocol") << "" << httpServerName() << "/qtest/rfc3252.txt" << ushort(80) << false << 400 << "Bad Request" << -1 << -1;
}
void tst_QHttpNetworkConnection::get()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QFETCH(int, downloadSize);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
// only check it if it is set and expected
if (reply->contentLength() != -1 && contentLength != -1)
QCOMPARE(reply->contentLength(), qint64(contentLength));
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QByteArray ba = reply->readAll();
//do not require server generated error pages to be a fixed size
if (downloadSize != -1)
QCOMPARE(ba.size(), downloadSize);
//but check against content length if it was sent
if (reply->contentLength() != -1)
QCOMPARE(ba.size(), (int)reply->contentLength());
delete reply;
}
void tst_QHttpNetworkConnection::finishedReply()
{
finishedCalled = true;
}
void tst_QHttpNetworkConnection::finishedWithError(QNetworkReply::NetworkError errorCode, const QString &detail)
{
Q_UNUSED(detail)
finishedWithErrorCalled = true;
netErrorCode = errorCode;
}
void tst_QHttpNetworkConnection::put_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<QString>("data");
QTest::addColumn<bool>("succeed");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/dav/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<true;
QTest::newRow("fail-internal") << "http://" << httpServerName() << "/dav2/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<false;
QTest::newRow("fail-host") << "http://" << "invalid.test.qt-project.org" << "/dav2/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<<false;
}
void tst_QHttpNetworkConnection::put()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(QString, data);
QFETCH(bool, succeed);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Put);
QByteArray array = data.toLatin1();
QNonContiguousByteDevice *bd = QNonContiguousByteDeviceFactory::create(&array);
bd->setParent(this);
request.setUploadByteDevice(bd);
finishedCalled = false;
finishedWithErrorCalled = false;
QHttpNetworkReply *reply = connection.sendRequest(request);
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished() || finishedCalled || finishedWithErrorCalled, 30000);
if (reply->isFinished()) {
QByteArray ba;
while (reply->bytesAvailable())
ba += reply->readAny();
} else if(finishedWithErrorCalled) {
if(!succeed) {
delete reply;
return;
} else {
QFAIL("Error in PUT");
}
} else {
QFAIL("PUT timed out");
}
int status = reply->statusCode();
if (status != 200 && status != 201 && status != 204) {
if (succeed) {
qDebug()<<"PUT failed, Status Code:" <<status;
QFAIL("Error in PUT");
}
} else {
if (!succeed) {
qDebug()<<"PUT Should fail, Status Code:" <<status;
QFAIL("Error in PUT");
}
}
delete reply;
}
void tst_QHttpNetworkConnection::post_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<QString>("data");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::addColumn<int>("downloadSize");
QTest::newRow("success-internal") << "http://" << httpServerName() << "/qtest/cgi-bin/echo.cgi" << ushort(80) << false << "7 bytes" << 200 << "OK" << 7 << 7;
QTest::newRow("failure-internal") << "http://" << httpServerName() << "/t" << ushort(80) << false << "Hello World" << 404 << "Not Found" << -1 << -1;
}
void tst_QHttpNetworkConnection::post()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(QString, data);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QFETCH(int, downloadSize);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path, QHttpNetworkRequest::Post);
QByteArray array = data.toLatin1();
QNonContiguousByteDevice *bd = QNonContiguousByteDeviceFactory::create(&array);
bd->setParent(this);
request.setUploadByteDevice(bd);
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
qint64 cLen = reply->contentLength();
if (contentLength != -1) {
// only check the content length if test expected it to be set
if (cLen==-1) {
// HTTP 1.1 server may respond with chunked encoding and in that
// case contentLength is not present in reply -> verify that it is the case
QByteArray transferEnc = reply->headerField("Transfer-Encoding");
QCOMPARE(transferEnc, QByteArray("chunked"));
} else {
QCOMPARE(cLen, qint64(contentLength));
}
}
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QByteArray ba = reply->readAll();
//don't require fixed size for generated error pages
if (downloadSize != -1)
QCOMPARE(ba.size(), downloadSize);
//but do compare with content length if possible
if (cLen != -1)
QCOMPARE(ba.size(), (int)cLen);
delete reply;
}
void tst_QHttpNetworkConnection::_delete_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::_delete()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::trace_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::trace()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::_connect_data()
{
// not tested yet
}
void tst_QHttpNetworkConnection::_connect()
{
QEXPECT_FAIL("", "not tested yet", Continue);
QVERIFY(false);
}
void tst_QHttpNetworkConnection::challenge401(const QHttpNetworkRequest &request,
QAuthenticator *authenticator)
{
Q_UNUSED(request)
QHttpNetworkReply *reply = qobject_cast<QHttpNetworkReply*>(sender());
if (reply) {
QHttpNetworkConnection *c = reply->connection();
QVariant val = c->property("setCredentials");
if (val.toBool()) {
QVariant user = c->property("username");
QVariant password = c->property("password");
authenticator->setUser(user.toString());
authenticator->setPassword(password.toString());
c->setProperty("setCredentials", false);
}
}
}
void tst_QHttpNetworkConnection::get401_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<bool>("setCredentials");
QTest::addColumn<QString>("username");
QTest::addColumn<QString>("password");
QTest::addColumn<int>("statusCode");
QTest::newRow("no-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << false << "" << ""<<401;
QTest::newRow("invalid-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << true << "test" << "test"<<401;
QTest::newRow("valid-credentials") << "http://" << httpServerName() << "/qtest/rfcs-auth/index.html" << ushort(80) << false << true << "httptest" << "httptest"<<200;
QTest::newRow("digest-authentication-invalid") << "http://" << httpServerName() << "/qtest/auth-digest/index.html" << ushort(80) << false << true << "wrong" << "wrong"<<401;
QTest::newRow("digest-authentication-valid") << "http://" << httpServerName() << "/qtest/auth-digest/index.html" << ushort(80) << false << true << "httptest" << "httptest"<<200;
}
void tst_QHttpNetworkConnection::get401()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(bool, setCredentials);
QFETCH(QString, username);
QFETCH(QString, password);
QFETCH(int, statusCode);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
connection.setProperty("setCredentials", setCredentials);
connection.setProperty("username", username);
connection.setProperty("password", password);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
connect(reply, SIGNAL(authenticationRequired(QHttpNetworkRequest,QAuthenticator*)),
SLOT(challenge401(QHttpNetworkRequest,QAuthenticator*)));
finishedCalled = false;
finishedWithErrorCalled = false;
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
QTRY_VERIFY_WITH_TIMEOUT(finishedCalled || finishedWithErrorCalled, 30000);
QCOMPARE(reply->statusCode(), statusCode);
delete reply;
}
#ifndef QT_NO_COMPRESS
void tst_QHttpNetworkConnection::compression_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<int>("statusCode");
QTest::addColumn<QString>("statusString");
QTest::addColumn<int>("contentLength");
QTest::addColumn<int>("downloadSize");
QTest::addColumn<bool>("autoCompress");
QTest::addColumn<QString>("contentCoding");
QTest::newRow("success-autogzip-temp") << "http://" << httpServerName() << "/qtest/rfcs/rfc2616.html" << ushort(80) << false << 200 << "OK" << -1 << 418321 << true << "";
QTest::newRow("success-nogzip-temp") << "http://" << httpServerName() << "/qtest/rfcs/rfc2616.html" << ushort(80) << false << 200 << "OK" << 418321 << 418321 << false << "identity";
QTest::newRow("success-manualgzip-temp") << "http://" << httpServerName() << "/qtest/deflate/rfc2616.html" << ushort(80) << false << 200 << "OK" << 119124 << 119124 << false << "gzip";
}
void tst_QHttpNetworkConnection::compression()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(int, statusCode);
QFETCH(QString, statusString);
QFETCH(int, contentLength);
QFETCH(int, downloadSize);
QFETCH(bool, autoCompress);
QFETCH(QString, contentCoding);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QCOMPARE(connection.isSsl(), encrypt);
QHttpNetworkRequest request(protocol + host + path);
if (!autoCompress)
request.setHeaderField("Accept-Encoding", contentCoding.toLatin1());
QHttpNetworkReply *reply = connection.sendRequest(request);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode(), statusCode);
QCOMPARE(reply->reasonPhrase(), statusString);
bool isLengthOk = (reply->contentLength() == qint64(contentLength)
|| reply->contentLength() == qint64(downloadSize)
|| reply->contentLength() == -1); //apache2 does not send content-length for compressed pages
QVERIFY(isLengthOk);
QTRY_VERIFY_WITH_TIMEOUT(reply->isFinished(), 30000);
QByteArray ba = reply->readAll();
QCOMPARE(ba.size(), downloadSize);
delete reply;
}
#endif
#ifndef QT_NO_SSL
void tst_QHttpNetworkConnection::sslErrors(const QList<QSslError> &errors)
{
Q_UNUSED(errors)
QHttpNetworkReply *reply = qobject_cast<QHttpNetworkReply*>(sender());
if (reply) {
QHttpNetworkConnection *connection = reply->connection();
QVariant val = connection->property("ignoreFromSignal");
if (val.toBool())
connection->ignoreSslErrors();
finishedWithErrorCalled = true;
}
}
void tst_QHttpNetworkConnection::ignoresslerror_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<bool>("ignoreInit");
QTest::addColumn<bool>("ignoreFromSignal");
QTest::addColumn<int>("statusCode");
// This test will work only if the website has ssl errors.
// fluke's certificate is signed by a non-standard authority.
// Since we don't introduce that CA into the SSL verification chain,
// connecting should fail.
QTest::newRow("success-init") << "https://" << httpServerName() << "/" << ushort(443) << true << true << false << 200;
QTest::newRow("success-fromSignal") << "https://" << httpServerName() << "/" << ushort(443) << true << false << true << 200;
QTest::newRow("failure") << "https://" << httpServerName() << "/" << ushort(443) << true << false << false << 100;
}
void tst_QHttpNetworkConnection::ignoresslerror()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(bool, ignoreInit);
QFETCH(bool, ignoreFromSignal);
QFETCH(int, statusCode);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
if (ignoreInit)
connection.ignoreSslErrors();
QCOMPARE(connection.isSsl(), encrypt);
connection.setProperty("ignoreFromSignal", ignoreFromSignal);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
SLOT(sslErrors(QList<QSslError>)));
finishedWithErrorCalled = false;
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable() || (statusCode == 100 && finishedWithErrorCalled), 30000);
QCOMPARE(reply->statusCode(), statusCode);
delete reply;
}
#endif
#ifdef QT_NO_SSL
void tst_QHttpNetworkConnection::nossl_data()
{
QTest::addColumn<QString>("protocol");
QTest::addColumn<QString>("host");
QTest::addColumn<QString>("path");
QTest::addColumn<ushort>("port");
QTest::addColumn<bool>("encrypt");
QTest::addColumn<QNetworkReply::NetworkError>("networkError");
QTest::newRow("protocol-error") << "https://" << httpServerName() << "/" << ushort(443) << true <<QNetworkReply::ProtocolUnknownError;
}
void tst_QHttpNetworkConnection::nossl()
{
QFETCH(QString, protocol);
QFETCH(QString, host);
QFETCH(QString, path);
QFETCH(ushort, port);
QFETCH(bool, encrypt);
QFETCH(QNetworkReply::NetworkError, networkError);
QHttpNetworkConnection connection(host, port, encrypt);
QCOMPARE(connection.port(), port);
QCOMPARE(connection.hostName(), host);
QHttpNetworkRequest request(protocol + host + path);
QHttpNetworkReply *reply = connection.sendRequest(request);
finishedWithErrorCalled = false;
netErrorCode = QNetworkReply::NoError;
connect(reply, SIGNAL(finished()), SLOT(finishedReply()));
connect(reply, SIGNAL(finishedWithError(QNetworkReply::NetworkError,QString)),
SLOT(finishedWithError(QNetworkReply::NetworkError,QString)));
QTRY_VERIFY_WITH_TIMEOUT(finishedWithErrorCalled, 30000);
QCOMPARE(netErrorCode, networkError);
delete reply;
}
#endif
void tst_QHttpNetworkConnection::getMultiple_data()
{
QTest::addColumn<quint16>("connectionCount");
QTest::addColumn<bool>("pipeliningAllowed");
// send 100 requests. apache will usually force-close after 100 requests in a single tcp connection
QTest::addColumn<int>("requestCount");
QTest::newRow("6 connections, no pipelining, 100 requests") << quint16(6) << false << 100;
QTest::newRow("1 connection, no pipelining, 100 requests") << quint16(1) << false << 100;
QTest::newRow("6 connections, pipelining allowed, 100 requests") << quint16(6) << true << 100;
QTest::newRow("1 connection, pipelining allowed, 100 requests") << quint16(1) << true << 100;
}
static bool allRepliesFinished(const QList<QHttpNetworkReply*> *_replies)
{
const QList<QHttpNetworkReply*> &replies = *_replies;
for (int i = 0; i < replies.length(); i++)
if (!replies.at(i)->isFinished())
return false;
return true;
}
void tst_QHttpNetworkConnection::getMultiple()
{
QFETCH(quint16, connectionCount);
QFETCH(bool, pipeliningAllowed);
QFETCH(int, requestCount);
QHttpNetworkConnection connection(connectionCount, httpServerName());
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
// depending on what you use the results will vary.
// for the "real" results, use a URL that has "internet latency" for you. Then (6 connections, pipelining) will win.
// for LAN latency, you will possibly get that (1 connection, no pipelining) is the fastest
QHttpNetworkRequest *request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt");
if (pipeliningAllowed)
request->setPipeliningAllowed(true);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
replies.append(reply);
}
QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000);
qDeleteAll(requests);
qDeleteAll(replies);
}
void tst_QHttpNetworkConnection::getMultipleWithPipeliningAndMultiplePriorities()
{
quint16 requestCount = 100;
// use 2 connections.
QHttpNetworkConnection connection(2, httpServerName());
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = 0;
if (i % 3)
request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt", QHttpNetworkRequest::Get);
else
request = new QHttpNetworkRequest("http://" + httpServerName() + "/qtest/rfc3252.txt", QHttpNetworkRequest::Head);
if (i % 2 || i % 3)
request->setPipeliningAllowed(true);
if (i % 3)
request->setPriority(QHttpNetworkRequest::HighPriority);
else if (i % 5)
request->setPriority(QHttpNetworkRequest::NormalPriority);
else if (i % 7)
request->setPriority(QHttpNetworkRequest::LowPriority);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
replies.append(reply);
}
QTRY_VERIFY_WITH_TIMEOUT(allRepliesFinished(&replies), 60000);
int pipelinedCount = 0;
for (int i = 0; i < replies.length(); i++) {
QVERIFY (!(replies.at(i)->request().isPipeliningAllowed() == false
&& replies.at(i)->isPipeliningUsed()));
if (replies.at(i)->isPipeliningUsed())
pipelinedCount++;
}
// We allow pipelining for every 2nd,3rd,4th,6th,8th,9th,10th etc request.
// Assume that half of the requests had been pipelined.
// (this is a very relaxed condition, when last measured 79 of 100
// requests had been pipelined)
QVERIFY(pipelinedCount >= requestCount / 2);
qDeleteAll(requests);
qDeleteAll(replies);
}
class GetMultipleWithPrioritiesReceiver : public QObject
{
Q_OBJECT
public:
int highPrioReceived;
int lowPrioReceived;
int requestCount;
GetMultipleWithPrioritiesReceiver(int rq) : highPrioReceived(0), lowPrioReceived(0), requestCount(rq) { }
public Q_SLOTS:
void finishedSlot() {
QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
if (reply->request().priority() == QHttpNetworkRequest::HighPriority)
highPrioReceived++;
else if (reply->request().priority() == QHttpNetworkRequest::LowPriority)
lowPrioReceived++;
else
QFAIL("Wrong priority!?");
QVERIFY(highPrioReceived + 7 >= lowPrioReceived);
if (highPrioReceived + lowPrioReceived == requestCount)
QTestEventLoop::instance().exitLoop();
}
};
void tst_QHttpNetworkConnection::getMultipleWithPriorities()
{
quint16 requestCount = 100;
// use 2 connections.
QHttpNetworkConnection connection(2, httpServerName());
GetMultipleWithPrioritiesReceiver receiver(requestCount);
QUrl url("http://" + httpServerName() + "/qtest/rfc3252.txt");
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = 0;
if (i % 3)
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
else
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Head);
if (i % 2)
request->setPriority(QHttpNetworkRequest::HighPriority);
else
request->setPriority(QHttpNetworkRequest::LowPriority);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
connect(reply, SIGNAL(finished()), &receiver, SLOT(finishedSlot()));
replies.append(reply);
}
QTestEventLoop::instance().enterLoop(40);
QVERIFY(!QTestEventLoop::instance().timeout());
qDeleteAll(requests);
qDeleteAll(replies);
}
class GetEmptyWithPipeliningReceiver : public QObject
{
Q_OBJECT
public:
int receivedCount;
int requestCount;
GetEmptyWithPipeliningReceiver(int rq) : receivedCount(0),requestCount(rq) { }
public Q_SLOTS:
void finishedSlot() {
QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
Q_UNUSED(reply);
receivedCount++;
if (receivedCount == requestCount)
QTestEventLoop::instance().exitLoop();
}
};
void tst_QHttpNetworkConnection::getEmptyWithPipelining()
{
quint16 requestCount = 50;
// use 2 connections.
QHttpNetworkConnection connection(2, httpServerName());
GetEmptyWithPipeliningReceiver receiver(requestCount);
QUrl url("http://" + httpServerName() + "/cgi-bin/echo.cgi"); // a get on this = getting an empty file
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = 0;
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
request->setPipeliningAllowed(true);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
connect(reply, SIGNAL(finished()), &receiver, SLOT(finishedSlot()));
replies.append(reply);
}
QTestEventLoop::instance().enterLoop(20);
QVERIFY(!QTestEventLoop::instance().timeout());
qDeleteAll(requests);
qDeleteAll(replies);
}
class GetAndEverythingShouldBePipelinedReceiver : public QObject
{
Q_OBJECT
public:
int receivedCount;
int requestCount;
GetAndEverythingShouldBePipelinedReceiver(int rq) : receivedCount(0),requestCount(rq) { }
public Q_SLOTS:
void finishedSlot() {
QHttpNetworkReply *reply = (QHttpNetworkReply*) sender();
Q_UNUSED(reply);
receivedCount++;
if (receivedCount == requestCount)
QTestEventLoop::instance().exitLoop();
}
};
void tst_QHttpNetworkConnection::getAndEverythingShouldBePipelined()
{
quint16 requestCount = 100;
// use 1 connection.
QHttpNetworkConnection connection(1, httpServerName());
QUrl url("http://" + httpServerName() + "/qtest/rfc3252.txt");
QList<QHttpNetworkRequest*> requests;
QList<QHttpNetworkReply*> replies;
GetAndEverythingShouldBePipelinedReceiver receiver(requestCount);
for (int i = 0; i < requestCount; i++) {
QHttpNetworkRequest *request = 0;
request = new QHttpNetworkRequest(url, QHttpNetworkRequest::Get);
request->setPipeliningAllowed(true);
requests.append(request);
QHttpNetworkReply *reply = connection.sendRequest(*request);
connect(reply, SIGNAL(finished()), &receiver, SLOT(finishedSlot()));
replies.append(reply);
}
QTestEventLoop::instance().enterLoop(40);
QVERIFY(!QTestEventLoop::instance().timeout());
qDeleteAll(requests);
qDeleteAll(replies);
}
void tst_QHttpNetworkConnection::getAndThenDeleteObject_data()
{
QTest::addColumn<bool>("replyFirst");
QTest::newRow("delete-reply-first") << true;
QTest::newRow("delete-connection-first") << false;
}
void tst_QHttpNetworkConnection::getAndThenDeleteObject()
{
// yes, this will leak if the testcase fails. I don't care. It must not fail then :P
QHttpNetworkConnection *connection = new QHttpNetworkConnection(httpServerName());
QHttpNetworkRequest request("http://" + httpServerName() + "/qtest/bigfile");
QHttpNetworkReply *reply = connection->sendRequest(request);
reply->setDownstreamLimited(true);
QTRY_VERIFY_WITH_TIMEOUT(reply->bytesAvailable(), 30000);
QCOMPARE(reply->statusCode() ,200);
QVERIFY(!reply->isFinished()); // must not be finished
QFETCH(bool, replyFirst);
if (replyFirst) {
delete reply;
delete connection;
} else {
delete connection;
delete reply;
}
}
class TestTcpServer : public QTcpServer
{
Q_OBJECT
public:
TestTcpServer() : errorCodeReports(0)
{
connect(this, &QTcpServer::newConnection, this, &TestTcpServer::onNewConnection);
QVERIFY(listen(QHostAddress::LocalHost));
}
int errorCodeReports;
public slots:
void onNewConnection()
{
QTcpSocket *socket = nextPendingConnection();
if (!socket)
return;
// close socket instantly!
connect(socket, &QTcpSocket::readyRead, socket, &QTcpSocket::close);
}
void onReply(QNetworkReply::NetworkError code)
{
QCOMPARE(code, QNetworkReply::RemoteHostClosedError);
++errorCodeReports;
}
};
void tst_QHttpNetworkConnection::overlappingCloseAndWrite()
{
// server accepts connections, but closes the socket instantly
TestTcpServer server;
QNetworkAccessManager accessManager;
// ten requests are scheduled. All should result in an RemoteHostClosed...
QUrl url;
url.setScheme(QStringLiteral("http"));
url.setHost(server.serverAddress().toString());
url.setPort(server.serverPort());
for (int i = 0; i < 10; ++i) {
QNetworkRequest request(url);
QNetworkReply *reply = accessManager.get(request);
// Not using Qt5 connection syntax here because of overly baroque syntax to discern between
// different error() methods.
QObject::connect(reply, SIGNAL(error(QNetworkReply::NetworkError)),
&server, SLOT(onReply(QNetworkReply::NetworkError)));
}
QTRY_COMPARE(server.errorCodeReports, 10);
}
QTEST_MAIN(tst_QHttpNetworkConnection)
#include "tst_qhttpnetworkconnection.moc"