diff options
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 6 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl_p.h | 4 | ||||
-rw-r--r-- | tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp | 49 |
3 files changed, 55 insertions, 4 deletions
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 08ef1866bd..e609653aa4 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -1035,9 +1035,6 @@ void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d) // cache this, we need it later and it's invalidated when dealing with compressed data auto dataSize = d.size(); - // Grab this to compare later (only relevant for compressed data) in case none of the data - // will be propagated to the user - const qint64 previousBytesDownloaded = bytesDownloaded; if (cacheEnabled && isCachingAllowed() && !cacheSaveDevice) initCacheSaveDevice(); @@ -1121,11 +1118,12 @@ void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d) // This can occur when downloading compressed data as some of the data may be the content // encoding's header. Don't emit anything for this. - if (previousBytesDownloaded == bytesDownloaded) { + if (lastReadyReadEmittedSize == bytesDownloaded) { if (readBufferMaxSize) emit q->readBufferFreed(dataSize); return; } + lastReadyReadEmittedSize = bytesDownloaded; QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader); diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h index 55953ae878..11897d1420 100644 --- a/src/network/access/qnetworkreplyhttpimpl_p.h +++ b/src/network/access/qnetworkreplyhttpimpl_p.h @@ -200,6 +200,10 @@ public: qint64 bytesDownloaded; qint64 bytesBuffered; + // We use this to keep track of whether or not we need to emit readyRead + // when we deal with signal compression (delaying emission) + decompressing + // data (potentially receiving bytes that don't end up in the final output): + qint64 lastReadyReadEmittedSize = 0; QTimer *transferTimeout; diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index f97fe88fdd..b4fbee8594 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -511,6 +511,7 @@ private Q_SLOTS: void downloadProgressWithContentEncoding(); void contentEncodingError_data(); void contentEncodingError(); + void compressedReadyRead(); // NOTE: This test must be last! void parentingRepliesToTheApp(); @@ -9880,6 +9881,54 @@ void tst_QNetworkReply::contentEncodingError() QTEST(reply->error(), "expectedError"); } +// When this test is failing it will appear flaky because it relies on the +// timing of delivery from one socket to another in the OS. +// + we have to send all the data at once, so the readyRead emissions are +// compressed into a single emission, so we cannot artificially time it with +// waits and sleeps. +void tst_QNetworkReply::compressedReadyRead() +{ + // There were historically an issue where a mix of signal compression and + // data decompression made it so we accidentally didn't emit the final + // readyRead signal before emitting finished(). Test this here to make sure + // it happens: + const QByteArray gzipPayload = + QByteArray::fromBase64("H4sIAAAAAAAAA8tIzcnJVyjPL8pJAQCFEUoNCwAAAA=="); + const QByteArray expected = "hello world"; + + QString header("HTTP/1.0 200 OK\r\nContent-Encoding: gzip\r\nContent-Length: %1\r\n\r\n"); + header = header.arg(gzipPayload.size()); + MiniHttpServer server(header.toLatin1()); // only send header automatically + server.doClose = false; // don't close and delete client socket right away + + QNetworkRequest request( + QUrl(QLatin1String("http://localhost:%1").arg(QString::number(server.serverPort())))); + QNetworkReplyPtr reply(manager.get(request)); + + QObject::connect(reply.get(), &QNetworkReply::metaDataChanged, reply.get(), + [&server, &gzipPayload]() { + // Client received headers, now send data: + // We do this awkward write,flush,write dance to try to + // make sure the data does not all arrive at the same + // time. By design we send the final "=" byte by itself + qsizetype boundary = gzipPayload.size() - 1; + server.client->write(gzipPayload.sliced(0, boundary)); + server.client->flush(); + // Let the server take care of deleting the client once + // the rest of the data is written: + server.doClose = true; + server.client->write(gzipPayload.sliced(boundary)); + }); + + QByteArray received; + QObject::connect(reply.get(), &QNetworkReply::readyRead, reply.get(), + [reply = reply.get(), &received]() { + received += reply->readAll(); + }); + QTRY_VERIFY(reply->isFinished()); + QCOMPARE(received, expected); +} + // NOTE: This test must be last testcase in tst_qnetworkreply! void tst_QNetworkReply::parentingRepliesToTheApp() { |