/**************************************************************************** ** ** 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 #include "private/qhttpnetworkconnection_p.h" #include "private/qnoncontiguousbytedevice_p.h" #include #include #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 &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("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("statusCode"); QTest::addColumn("statusString"); QTest::addColumn("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("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("statusCode"); QTest::addColumn("statusString"); QTest::addColumn("contentLength"); QTest::addColumn("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("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("data"); QTest::addColumn("succeed"); QTest::newRow("success-internal") << "http://" << httpServerName() << "/dav/file1.txt" << ushort(80) << false << "Hello World\nEnd of file\n"<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:" <("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("data"); QTest::addColumn("statusCode"); QTest::addColumn("statusString"); QTest::addColumn("contentLength"); QTest::addColumn("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(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("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("setCredentials"); QTest::addColumn("username"); QTest::addColumn("password"); QTest::addColumn("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("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("statusCode"); QTest::addColumn("statusString"); QTest::addColumn("contentLength"); QTest::addColumn("downloadSize"); QTest::addColumn("autoCompress"); QTest::addColumn("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 &errors) { Q_UNUSED(errors) QHttpNetworkReply *reply = qobject_cast(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("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("ignoreInit"); QTest::addColumn("ignoreFromSignal"); QTest::addColumn("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)), SLOT(sslErrors(QList))); 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("protocol"); QTest::addColumn("host"); QTest::addColumn("path"); QTest::addColumn("port"); QTest::addColumn("encrypt"); QTest::addColumn("networkError"); QTest::newRow("protocol-error") << "https://" << httpServerName() << "/" << ushort(443) << true <("connectionCount"); QTest::addColumn("pipeliningAllowed"); // send 100 requests. apache will usually force-close after 100 requests in a single tcp connection QTest::addColumn("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 *_replies) { const QList &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 requests; QList 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 requests; QList 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 requests; QList 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 requests; QList 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 requests; QList 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("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"