Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package qt6-httpserver for openSUSE:Factory checked in at 2026-02-03 21:27:00 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/qt6-httpserver (Old) and /work/SRC/openSUSE:Factory/.qt6-httpserver.new.1995 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "qt6-httpserver" Tue Feb 3 21:27:00 2026 rev:26 rq:1330581 version:6.10.2 Changes: -------- --- /work/SRC/openSUSE:Factory/qt6-httpserver/qt6-httpserver.changes 2025-11-25 15:48:37.683802357 +0100 +++ /work/SRC/openSUSE:Factory/.qt6-httpserver.new.1995/qt6-httpserver.changes 2026-02-03 21:27:24.546329418 +0100 @@ -1,0 +2,6 @@ +Sat Jan 31 08:10:35 UTC 2026 - Christophe Marin <[email protected]> + +- Update to 6.10.2: + * https://www.qt.io/blog/qt-6.10.2-released + +------------------------------------------------------------------- Old: ---- qthttpserver-everywhere-src-6.10.1.tar.xz New: ---- qthttpserver-everywhere-src-6.10.2.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ qt6-httpserver.spec ++++++ --- /var/tmp/diff_new_pack.zr2Ait/_old 2026-02-03 21:27:25.582373016 +0100 +++ /var/tmp/diff_new_pack.zr2Ait/_new 2026-02-03 21:27:25.586373185 +0100 @@ -16,7 +16,7 @@ # -%define real_version 6.10.1 +%define real_version 6.10.2 %define short_version 6.10 %define tar_name qthttpserver-everywhere-src %define tar_suffix %{nil} @@ -27,7 +27,7 @@ %endif # Name: qt6-httpserver%{?pkg_suffix} -Version: 6.10.1 +Version: 6.10.2 Release: 0 Summary: Qt HTTP Server License: GPL-3.0-only ++++++ qthttpserver-everywhere-src-6.10.1.tar.xz -> qthttpserver-everywhere-src-6.10.2.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qthttpserver-everywhere-src-6.10.1/.cmake.conf new/qthttpserver-everywhere-src-6.10.2/.cmake.conf --- old/qthttpserver-everywhere-src-6.10.1/.cmake.conf 2025-11-13 22:11:08.000000000 +0100 +++ new/qthttpserver-everywhere-src-6.10.2/.cmake.conf 2026-01-22 21:38:02.000000000 +0100 @@ -1,4 +1,6 @@ -set(QT_REPO_MODULE_VERSION "6.10.1") +set(QT_REPO_MODULE_VERSION "6.10.2") set(QT_REPO_MODULE_PRERELEASE_VERSION_SEGMENT "alpha1") -set(QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_FOREACH=1") -list(APPEND QT_EXTRA_INTERNAL_TARGET_DEFINES "QT_NO_CONTEXTLESS_CONNECT=1") +set(QT_EXTRA_INTERNAL_TARGET_DEFINES + "QT_NO_CONTEXTLESS_CONNECT=1" + "QT_NO_FOREACH=1" +) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qthttpserver-everywhere-src-6.10.1/.tag new/qthttpserver-everywhere-src-6.10.2/.tag --- old/qthttpserver-everywhere-src-6.10.1/.tag 2025-11-13 22:11:08.000000000 +0100 +++ new/qthttpserver-everywhere-src-6.10.2/.tag 2026-01-22 21:38:02.000000000 +0100 @@ -1 +1 @@ -8dc17fd1270d367a10e1d69e561fba8797b896a9 +86b01c42b871a0240862ccbeb6ddc9f43ef483a4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qthttpserver-everywhere-src-6.10.1/dependencies.yaml new/qthttpserver-everywhere-src-6.10.2/dependencies.yaml --- old/qthttpserver-everywhere-src-6.10.1/dependencies.yaml 2025-11-13 22:11:08.000000000 +0100 +++ new/qthttpserver-everywhere-src-6.10.2/dependencies.yaml 2026-01-22 21:38:02.000000000 +0100 @@ -1,7 +1,7 @@ dependencies: ../qtbase: - ref: 90b845d15ffb97693dba527385db83510ebd121a + ref: 000d6c62f7880bb8d3054724e8da0b8ae244130e required: true ../qtwebsockets: - ref: ba2ada87ef9027650efb6251e7fc05519f484e95 + ref: 2b969cb983d1e22df0e6fc6ece54043942090bd8 required: false diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/qthttpserver-everywhere-src-6.10.1/tests/auto/qhttpservermultithreaded/tst_qhttpservermultithreaded.cpp new/qthttpserver-everywhere-src-6.10.2/tests/auto/qhttpservermultithreaded/tst_qhttpservermultithreaded.cpp --- old/qthttpserver-everywhere-src-6.10.1/tests/auto/qhttpservermultithreaded/tst_qhttpservermultithreaded.cpp 2025-11-13 22:11:08.000000000 +0100 +++ new/qthttpserver-everywhere-src-6.10.2/tests/auto/qhttpservermultithreaded/tst_qhttpservermultithreaded.cpp 2026-01-22 21:38:02.000000000 +0100 @@ -124,10 +124,12 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +using namespace std::chrono_literals; enum ServerType { - TCP, + HTTP1_0, + HTTP1_1, #if QT_CONFIG(ssl) SSL, #endif @@ -147,21 +149,27 @@ public: LocalHttpClient(ServerType type); ~LocalHttpClient(); - QString get(const QString &url); - QString getSlowRead(const QString &url, qsizetype chunkSize, qsizetype mSleep); + QString get(const QString &url, const QHttpHeaders &headers = {}); + QString getSlowRead(const QString &url, qsizetype chunkSize, qsizetype mSleep, + const QHttpHeaders &headers); void pipelinedSendGet(const QString &url); QString piplinedFetchResults(); QString postSlow(const QString &url, const QHttpHeaders &headers, qsizetype mSleep); private: - void sendGet(const QString &url); + void sendGet(const QString &url, const QHttpHeaders &headers = {}); QString fetchResults(); + void fetchStatusLine(); + void fetchCrLf(); QIODevice *socket = nullptr; + QByteArray version; + ServerType serverType; }; LocalHttpClient::LocalHttpClient(ServerType type) + : version(type == HTTP1_0 ? "HTTP/1.0" : "HTTP/1.1"), serverType(type) { - if (type == TCP) { + if (type == HTTP1_0 || type == HTTP1_1) { QTcpSocket *tcpSocket = new QTcpSocket(); tcpSocket->connectToHost("localhost", port); tcpSocket->waitForConnected(); @@ -214,15 +222,16 @@ return fetchResults(); } -QString LocalHttpClient::get(const QString &url) +QString LocalHttpClient::get(const QString &url, const QHttpHeaders &headers) { - sendGet(url); + sendGet(url, headers); return fetchResults(); } -QString LocalHttpClient::getSlowRead(const QString &url, qsizetype chunkSize, qsizetype mSleep) +QString LocalHttpClient::getSlowRead(const QString &url, qsizetype chunkSize, qsizetype mSleep, + const QHttpHeaders &headers) { - sendGet(url); + sendGet(url, headers); qint64 contentLength = -1; constexpr qint64 headerBufferSize = 4 * 1024; @@ -268,8 +277,8 @@ QString LocalHttpClient::postSlow(const QString &url, const QHttpHeaders &headers, qsizetype mSleep) { Q_ASSERT(socket); - qint64 result = socket->write(u"POST %1 HTTP/1.1\r\n"_s.arg(url).toUtf8()); - QVERIFY2(result >= 0, "Error writing POST method"); + qint64 result = socket->write(u"POST %1 %2\r\n"_s.arg(url, version).toUtf8()); + QVERIFY2(result != -1, "Error writing POST method"); for (qsizetype i = 0; i < headers.size(); ++i) { QByteArray output; @@ -294,16 +303,46 @@ return fetchResults(); } -void LocalHttpClient::sendGet(const QString &url) +void LocalHttpClient::sendGet(const QString &url, const QHttpHeaders &headers) +{ + Q_ASSERT(socket); + socket->write(u"GET %1 %2\r\n"_s.arg(url, version).toUtf8()); + for (qsizetype i = 0; i < headers.size(); ++i) { + QByteArray output; + output.append(headers.nameAt(i)); + output.append(": "); + output.append(headers.valueAt(i)); + output.append("\r\n"); + socket->write(output); + } + socket->write("\r\n"); +} + +void LocalHttpClient::fetchStatusLine() { Q_ASSERT(socket); - socket->write(u"GET %1 HTTP/1.1\r\n\r\n"_s.arg(url).toUtf8()); + constexpr qint64 bufferSize = 4 * 1024; + char buffer[bufferSize]; + qint64 read = 0; + while (!socket->canReadLine()) + socket->waitForReadyRead(10); + read = socket->readLine(buffer, bufferSize); + QVERIFY2(read > 2, "Status line too short"); + + QByteArrayView line(buffer, read); + auto space = line.indexOf(' '); + QVERIFY2(space != -1, "Error parsing status line"); + + auto httpVersion = line.first(space); + QCOMPARE(httpVersion, version); } QString LocalHttpClient::fetchResults() { Q_ASSERT(socket); + fetchStatusLine(); qint64 contentLength = -1; + QByteArray transferEncoding; constexpr qint64 bufferSize = 4 * 1024; char buffer[bufferSize]; qint64 read = 0; @@ -321,27 +360,146 @@ auto headerTitle = line.first(colon); if (headerTitle.compare("Content-Length", Qt::CaseInsensitive) == 0) contentLength = line.sliced(colon + 1).trimmed().toLongLong(); + else if (headerTitle.compare("Transfer-Encoding", Qt::CaseInsensitive) == 0) + transferEncoding = line.sliced(colon + 1).trimmed().toByteArray(); } }; - QVERIFY2(contentLength != -1, "Content length missing"); QVERIFY2(contentLength < bufferSize, "Buffer too small"); if (contentLength == 0) return u""_s; // No content - read = 0; - forever { - qint64 result = socket->read(&buffer[read], contentLength - read); - QVERIFY2(result >= 0, "IO error reading content"); - read += result; - if (read == contentLength) - break; - socket->waitForReadyRead(10); - }; - + if (contentLength > 0) { + read = 0; + while (read < contentLength) { + socket->waitForReadyRead(10); + qint64 result = socket->read(&buffer[read], contentLength - read); + QVERIFY2(result >= 0, "IO Error reading content"); + read += result; + }; + } else if (transferEncoding.compare("chunked", Qt::CaseInsensitive) == 0) { + if (serverType == HTTP1_0) + QEXPECT_FAIL("", "QTBUG-138410: The HTTP/1.0 support is incomplete", Abort); + QVERIFY2(serverType != HTTP1_0, "Chunked encoding not supported for HTTP/1.0"); + read = 0; + forever { + while (!socket->canReadLine()) + socket->waitForReadyRead(10); + QByteArray line = socket->readLine(); + qint64 result = line.trimmed().toInt(nullptr, 16); + if (result == 0) { + fetchCrLf(); + contentLength = read; + break; + } + qint64 toRead = qMin(result, bufferSize - read); + while (toRead > 0) { + result = socket->read(&buffer[read], toRead); + QVERIFY2(result >= 0, "Read error decoding chunked encoding"); + read += result; + toRead -= result; + if (toRead) + socket->waitForReadyRead(10); + } + fetchCrLf(); + }; + } else { + // Read until closed + read = 0; + while (read < bufferSize) { + socket->waitForReadyRead(10); + qint64 result = socket->read(&buffer[read], bufferSize - read); + if (result == -1) { + contentLength = read; + break; + } + read += result; + }; + } return QString::fromUtf8(buffer, contentLength); } +void LocalHttpClient::fetchCrLf() +{ + qint64 read = 0; + char buffer[2]; + while (read < 2) { + qint64 toRead = 2 - read; + qint64 result = socket->read(&buffer[read], toRead); + QVERIFY2(result >= 0, "Read error decoding chunked encoding"); + read += result; + if (read < 2) + socket->waitForReadyRead(10); + } + QVERIFY2(buffer[0] == '\r' && buffer[1] == '\n', "Read error decoding chunked encoding"); +} + +class SequentialIODevice : public QIODevice +{ + Q_OBJECT + +public: + SequentialIODevice(const QByteArray &data, int times, std::chrono::milliseconds readInterval) + : message(data), times(times) + { + setOpenMode(QIODeviceBase::ReadOnly); + timer = new QTimer(this); + timer->callOnTimeout(this, &SequentialIODevice::onTimeout); + timer->setSingleShot(false); + timer->setInterval(readInterval); + timer->start(); + } + + bool isSequential() const override { return true; } + qint64 bytesAvailable() const override { return buffer.size() - readPos; } + qint64 bytesToWrite() const override { return 0; } + + bool seek(qint64) override + { + return false; // No random accesss on sequential devices + } + + qint64 readData(char *data, qint64 maxSize) override + { + qint64 length = qMin(maxSize, buffer.size() - readPos); + if (length == 0) + return finishedReading ? -1 : 0; + QVERIFY(length + readPos <= buffer.size()); + memcpy(data, buffer.constData() + readPos, length); + readPos += length; + + if (readPos == buffer.size()) { + readPos = 0; + buffer.clear(); + } + return length; + } + + qint64 writeData(const char *, qint64) override { return -1; } + + void onTimeout() + { + if (times > 0) { + buffer.append(message); + emit readyRead(); + } + + if (--times <= 0) { + timer->stop(); + finishedReading = true; + emit readChannelFinished(); + } + } + +private: + QTimer *timer; + const QByteArray message; + QByteArray buffer; + qsizetype readPos = 0; + int times; + bool finishedReading = false; +}; + class tst_QHttpServerMultithreaded final : public QObject { Q_OBJECT @@ -369,6 +527,8 @@ void waitPipelinedQnam(); void manyWaitingToRespond(); void oneSlowManyFast(); + void multipartBack(); + void sequentialDevice(); private: static constexpr qsizetype NumberOfThreads = 6; @@ -415,7 +575,8 @@ void tst_QHttpServerMultithreaded::initTestCase_data() { QTest::addColumn<ServerType>("serverType"); - QTest::addRow("TCP") << ServerType::TCP; + QTest::addRow("HTTP/1.0") << ServerType::HTTP1_0; + QTest::addRow("HTTP/1.1") << ServerType::HTTP1_1; #if QT_CONFIG(ssl) if (QSslSocket::supportsSsl() && !QTestPrivate::isSecureTransportBlockingTest()) QTest::addRow("SSL") << ServerType::SSL; @@ -468,6 +629,27 @@ }); }); + httpserver.route("/multipart/<arg>/<arg>", + [this](QString message, int times, QHttpServerResponder &responder) { + ++callCounter; + if (times > 0) { + QByteArray ba = message.toUtf8(); + responder.writeBeginChunked("text/plain"_ba); + for (int i = 1; i < times; ++i) + responder.writeChunk(ba); + responder.writeEndChunked(ba); + } else { + responder.write(); + } + }); + + httpserver.route("/sequential/<arg>/<arg>", + [this](QString message, int times, QHttpServerResponder &responder) { + ++callCounter; + auto device = new SequentialIODevice(message.toUtf8(), times, 50ms); + responder.write(device, QHttpHeaders()); + }); + auto tcpserver = std::make_unique<QTcpServer>(); QVERIFY2(tcpserver->listen(), "HTTP server listen failed"); port = tcpserver->serverPort(); @@ -563,17 +745,21 @@ QFuture<QList<QString>> futureLower = QtConcurrent::run(&clientThreadPool, [&]() { LocalHttpClient client(serverType); + QHttpHeaders headers; + headers.append(QHttpHeaders::WellKnownHeader::Connection, "keep-alive"); QList<QString> results; for (auto &input : inputs) - results.push_back(client.get(u"/convert-lowercase/%1"_s.arg(input))); + results.push_back(client.get(u"/convert-lowercase/%1"_s.arg(input), headers)); return results; }); QFuture<QList<QString>> futureUpper = QtConcurrent::run(&clientThreadPool, [&]() { LocalHttpClient client(serverType); + QHttpHeaders headers; + headers.append(QHttpHeaders::WellKnownHeader::Connection, "keep-alive"); QList<QString> results; for (auto &input : inputs) - results.push_back(client.get(u"/convert-uppercase/%1"_s.arg(input))); + results.push_back(client.get(u"/convert-uppercase/%1"_s.arg(input), headers)); return results; }); @@ -605,17 +791,20 @@ futures[i] = QtConcurrent::run(&clientThreadPool, [&]() { LocalHttpClient client(serverType); QList<std::pair<QString, QString>> results; + QHttpHeaders headers; + headers.append(QHttpHeaders::WellKnownHeader::Connection, "keep-alive"); for (auto &input : inputs) { - QString lower = client.get(u"/convert-lowercase/%1"_s.arg(input)); - QString upper = client.get(u"/convert-uppercase/%1"_s.arg(input)); + QString lower = client.get(u"/convert-lowercase/%1"_s.arg(input), headers); + QString upper = client.get(u"/convert-uppercase/%1"_s.arg(input), headers); results.push_back(std::make_pair(lower, upper)); } return results; }); }; - QTRY_VERIFY(std::all_of(futures.begin(), futures.end(), - [](auto &future) { return future.isFinished(); })); + QTRY_VERIFY_WITH_TIMEOUT(std::all_of(futures.begin(), futures.end(), + [](auto &future) { return future.isFinished(); }), + 10s); QCOMPARE(getCallCount(), inputs.size() * NumberOfTasks * 2); for (qsizetype i = 0; i < NumberOfTasks; ++i) { @@ -637,8 +826,12 @@ for (qsizetype i = 0; i < NumberOfTasks; ++i) { futures[i] = QtConcurrent::run([&](QPromise<QString> &promise) { LocalHttpClient client(serverType); - promise.addResult(client.getSlowRead(u"/convert-lowercase/%1"_s.arg(input), 10, 3000)); - promise.addResult(client.getSlowRead(u"/convert-uppercase/%1"_s.arg(input), 10, 1000)); + QHttpHeaders headers; + headers.append(QHttpHeaders::WellKnownHeader::Connection, "keep-alive"); + promise.addResult( + client.getSlowRead(u"/convert-lowercase/%1"_s.arg(input), 10, 3000, headers)); + promise.addResult( + client.getSlowRead(u"/convert-uppercase/%1"_s.arg(input), 10, 1000, headers)); }); } @@ -731,15 +924,19 @@ QFuture<QString> future1 = QtConcurrent::run(&clientThreadPool, [&]() { LocalHttpClient client(serverType); - QString result = client.get(u"/wait/2001"_s); - result += client.get(u"/wait/9"_s); + QHttpHeaders headers; + headers.append(QHttpHeaders::WellKnownHeader::Connection, "keep-alive"); + QString result = client.get(u"/wait/2001"_s, headers); + result += client.get(u"/wait/9"_s, headers); return result; }); QFuture<QString> future2 = QtConcurrent::run(&clientThreadPool, [&]() { LocalHttpClient client(serverType); - QString result = client.get(u"/wait/2000"_s); - result += client.get(u"/wait/10"_s); + QHttpHeaders headers; + headers.append(QHttpHeaders::WellKnownHeader::Connection, "keep-alive"); + QString result = client.get(u"/wait/2000"_s, headers); + result += client.get(u"/wait/10"_s, headers); return result; }); @@ -751,6 +948,8 @@ void tst_QHttpServerMultithreaded::waitPipelined() { QFETCH_GLOBAL(ServerType, serverType); + if (serverType == HTTP1_0) + QSKIP("Pipelining is not supported for HTTP/1.0"); QList<qsizetype> waitTimes = { 3000, 1000, 1 }; constexpr qsizetype NumberOfTasks = NumberOfThreads; @@ -782,8 +981,8 @@ void tst_QHttpServerMultithreaded::waitPipelinedQnam() { QFETCH_GLOBAL(ServerType, serverType); - if (serverType != TCP) - QSKIP("This test only supports TCP"); + if (serverType != HTTP1_1) + QSKIP("This test only supports HTTP/1.1 over TCP"); QList<qsizetype> waitTimes = { 3000, 1000, 1 }; constexpr qsizetype NumberOfTasks = NumberOfThreads; @@ -877,6 +1076,40 @@ QCOMPARE(getCallCount(), NumberOfFastTasks + 1); } +void tst_QHttpServerMultithreaded::multipartBack() +{ + QFETCH_GLOBAL(ServerType, serverType); + + QFuture<QString> future = QtConcurrent::run([&]() { + LocalHttpClient client(serverType); + return client.get("/multipart/Hey/3"); + }); + + QTRY_VERIFY(future.isFinished()); + + QString returned = future.result(); + QCOMPARE(returned, u"HeyHeyHey"_s); + QCOMPARE(getCallCount(), 1); +} + +void tst_QHttpServerMultithreaded::sequentialDevice() +{ + QFETCH_GLOBAL(ServerType, serverType); + + QFuture<QString> future = QtConcurrent::run([&]() { + LocalHttpClient client(serverType); + return client.get("/sequential/Hey/3"); + }); + + QTRY_VERIFY(future.isFinished()); + + QString returned = future.result(); + if (serverType == HTTP1_0) + QSKIP("QTBUG-138410: The HTTP/1.0 support is incomplete: Flaky, mostly fails on HTTP/1.0"); + QCOMPARE(returned, u"HeyHeyHey"_s); + QCOMPARE(getCallCount(), 1); +} + QT_END_NAMESPACE QTEST_MAIN(tst_QHttpServerMultithreaded)
