Deprecate serverMetadata for server{Initial,Trailing}Metadata

We forgot to deprecate the server-metadata interfaces when we deprecated
the client-metadata. The same problems apply here too!

Found during the 6.10 API-review.

[ChangeLog][Deprecation Notice]
Deprecate the metadata()/serverMetadata()/setServerMetadata() methods on
QGrpcOperation and QGrpcOperationContext that use QHash in favor of the
new server{Initial,Trailing}Metadata interfaces, that use QMultiHash and
provide the correct handling of the received metadata in their
respective phase. This is a behavior change as the old metadata()
interface now only returns the initial metadata.

Fixes: QTBUG-138039
Pick-to: 6.10
Change-Id: I307ee81fc353a0f4316fea2d10f56bb6910ae859
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
This commit is contained in:
Dennis Oberst 2025-07-14 17:08:04 +02:00
parent 814c0b6ac2
commit 0e2803842a
6 changed files with 226 additions and 28 deletions

View File

@ -771,7 +771,7 @@ void Http2Handler::handleHeaders(const HPack::HttpHeader &headers, HeaderPhase p
false,
};
QHash<QByteArray, QByteArray> metadata;
QMultiHash<QByteArray, QByteArray> metadata;
std::optional<QtGrpc::StatusCode> statusCode;
QString statusMessage;
@ -837,16 +837,14 @@ void Http2Handler::handleHeaders(const HPack::HttpHeader &headers, HeaderPhase p
switch (phase) {
case HeaderPhase::Initial:
m_operation->setServerMetadata(std::move(metadata));
m_operation->setServerInitialMetadata(std::move(metadata));
break;
case HeaderPhase::TrailersOnly:
[[fallthrough]];
case HeaderPhase::Trailers: {
auto md = m_operation->serverMetadata();
md.insert(metadata);
m_operation->setServerMetadata(std::move(md));
case HeaderPhase::Trailers:
m_operation->setServerTrailingMetadata(std::move(metadata));
finish({ *statusCode, statusMessage });
} break;
break;
default:
Q_UNREACHABLE();
}

View File

@ -155,16 +155,69 @@ void QGrpcOperation::cancel()
emit d->operationContext->cancelRequested();
}
/*!
Returns the server metadata received from the channel.
#if QT_DEPRECATED_SINCE(6, 13)
\note For \l {QGrpcHttp2Channel} {HTTP/2 channels} it usually includes the
HTTP headers received from the server.
/*!
\deprecated [6.13] Use serverInitialMetadata() and serverTrailingMetadata() instead.
\include qgrpcoperation.cpp serverInitialMetadata
\sa serverInitialMetadata() serverTrailingMetadata()
*/
const QHash<QByteArray, QByteArray> &QGrpcOperation::metadata() const & noexcept
{
Q_D(const QGrpcOperation);
return d->operationContext->serverMetadata();
QT_IGNORE_DEPRECATIONS(return d->operationContext->serverMetadata();)
}
#endif // QT_DEPRECATED_SINCE(6, 13)
/*!
\since 6.10
//! [serverInitialMetadata]
Returns the initial metadata received from the server before any response
messages.
Initial metadata is sent by the server immediately after the call is
established. It may include key-value pairs that provide context for the
call.
\note For \l {QGrpcHttp2Channel} {HTTP/2 channels}, this is delivered
via response headers.
//! [serverInitialMetadata]
The metadata may contain multiple entries under the same key.
\sa serverTrailingMetadata()
*/
const QMultiHash<QByteArray, QByteArray> &QGrpcOperation::serverInitialMetadata() const & noexcept
{
Q_D(const QGrpcOperation);
return d->operationContext->serverInitialMetadata();
}
/*!
\since 6.10
//! [serverTrailingMetadata]
Returns the trailing metadata received from the server after all response
messages.
Trailing metadata is sent only by the server once all response messages
have been sent and just before the RPC completes. It may include key-value
pairs providing additional context about the completed call.
\note For \l {QGrpcHttp2Channel} {HTTP/2 channels}, this is delivered
via response trailers.
//! [serverTrailingMetadata]
The metadata may contain multiple entries under the same key.
*/
const QMultiHash<QByteArray, QByteArray> &QGrpcOperation::serverTrailingMetadata() const & noexcept
{
Q_D(const QGrpcOperation);
return d->operationContext->serverTrailingMetadata();
}
/*!

View File

@ -13,6 +13,7 @@
#include <QtCore/qhash.h>
#include <QtCore/qobject.h>
#include <QtCore/qstringfwd.h>
#include <QtCore/qtdeprecationdefinitions.h>
#include <optional>
@ -37,8 +38,16 @@ public:
}
bool read(QProtobufMessage *message) const;
#if QT_DEPRECATED_SINCE(6, 13)
QT_DEPRECATED_VERSION_X_6_13("Use serverInitialMetadata()")
[[nodiscard]] const QHash<QByteArray, QByteArray> &metadata() const & noexcept;
void metadata() const && = delete;
#endif
[[nodiscard]] const QMultiHash<QByteArray, QByteArray> &
serverInitialMetadata() const & noexcept;
[[nodiscard]] const QMultiHash<QByteArray, QByteArray> &
serverTrailingMetadata() const & noexcept;
[[nodiscard]] QLatin1StringView method() const noexcept;

View File

@ -1,6 +1,7 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtGrpc/private/qgrpccommonoptions_p.h>
#include <QtGrpc/qgrpccalloptions.h>
#include <QtGrpc/qgrpcoperationcontext.h>
#include <QtGrpc/qgrpcstatus.h>
@ -158,8 +159,12 @@ public:
QByteArray argument;
QGrpcCallOptions options;
std::shared_ptr<QAbstractProtobufSerializer> serializer;
QHash<QByteArray, QByteArray> serverMetadata;
QMetaType responseMetaType;
QMultiHash<QByteArray, QByteArray> serverInitialMetadata;
#if QT_DEPRECATED_SINCE(6, 13)
QHash<QByteArray, QByteArray> deprServerInitialMetadata;
#endif
QMultiHash<QByteArray, QByteArray> serverTrailingMetadata;
};
/*!
@ -235,33 +240,112 @@ QGrpcOperationContext::serializer() const
return d->serializer;
}
/*!
Returns the metadata received from the server.
#if QT_DEPRECATED_SINCE(6, 13)
\note This method is used implicitly by the QGrpcOperation counterpart.
/*!
\deprecated [6.13] Use serverInitialMetadata() and serverTrailingMetadata() instead.
\include qgrpcoperation.cpp serverInitialMetadata
\note This method is used implicitly by QGrpcOperation.
\sa serverInitialMetadata() QGrpcOperation::serverInitialMetadata()
*/
const QHash<QByteArray, QByteArray> &QGrpcOperationContext::serverMetadata() const & noexcept
{
Q_D(const QGrpcOperationContext);
return d->serverMetadata;
return d->deprServerInitialMetadata;
}
/*!
\fn void QGrpcOperationContext::setServerMetadata(const QHash<QByteArray, QByteArray> &metadata)
\fn void QGrpcOperationContext::setServerMetadata(QHash<QByteArray, QByteArray> &&metadata)
\deprecated [6.13] Use setServerInitialMetadata() instead.
Sets the server \a metadata received from the service.
Sets the metadata received from the server at the start of the RPC.
\sa setServerInitialMetadata()
*/
void QGrpcOperationContext::setServerMetadata(const QHash<QByteArray, QByteArray> &metadata)
{
Q_D(QGrpcOperationContext);
d->serverMetadata = metadata;
if (d->deprServerInitialMetadata == metadata)
return;
d->deprServerInitialMetadata = metadata;
d->serverInitialMetadata = QMultiHash<QByteArray, QByteArray>(metadata);
}
void QGrpcOperationContext::setServerMetadata(QHash<QByteArray, QByteArray> &&metadata)
{
Q_D(QGrpcOperationContext);
d->serverMetadata = std::move(metadata);
if (d->deprServerInitialMetadata == metadata)
return;
d->deprServerInitialMetadata = std::move(metadata);
d->serverInitialMetadata = QMultiHash<QByteArray, QByteArray>(d->deprServerInitialMetadata);
}
#endif // QT_DEPRECATED_SINCE(6, 13)
/*!
\since 6.10
\include qgrpcoperation.cpp serverInitialMetadata
\note This method is used implicitly by QGrpcOperation.
\sa QGrpcOperation::serverInitialMetadata() serverTrailingMetadata()
*/
const QMultiHash<QByteArray, QByteArray> &
QGrpcOperationContext::serverInitialMetadata() const & noexcept
{
Q_D(const QGrpcOperationContext);
return d->serverInitialMetadata;
}
/*!
\since 6.10
Sets the \a metadata received from the server at the start of the RPC.
\sa serverInitialMetadata()
*/
void QGrpcOperationContext::setServerInitialMetadata(QMultiHash<QByteArray, QByteArray> &&metadata)
{
Q_D(QGrpcOperationContext);
if (d->serverInitialMetadata == metadata)
return;
d->serverInitialMetadata = std::move(metadata);
#if QT_DEPRECATED_SINCE(6, 13)
d->deprServerInitialMetadata = QtGrpcPrivate::mergeHash(d->serverInitialMetadata);
#endif
}
/*!
\since 6.10
\include qgrpcoperation.cpp serverTrailingMetadata
\note This method is used implicitly by QGrpcOperation.
\sa QGrpcOperation::serverTrailingMetadata() setServerTrailingMetadata()
*/
const QMultiHash<QByteArray, QByteArray> &
QGrpcOperationContext::serverTrailingMetadata() const & noexcept
{
Q_D(const QGrpcOperationContext);
return d->serverTrailingMetadata;
}
/*!
\since 6.10
Sets the trailing \a metadata received from the server after all response
messages.
\sa serverTrailingMetadata()
*/
void QGrpcOperationContext::setServerTrailingMetadata(QMultiHash<QByteArray, QByteArray> &&metadata)
{
Q_D(QGrpcOperationContext);
if (d->serverTrailingMetadata == metadata)
return;
d->serverTrailingMetadata = std::move(metadata);
}
/*!

View File

@ -11,6 +11,7 @@
#include <QtCore/qhash.h>
#include <QtCore/qobject.h>
#include <QtCore/qstringfwd.h>
#include <QtCore/qtdeprecationdefinitions.h>
#include <memory>
@ -39,10 +40,25 @@ public:
void callOptions() const && = delete;
[[nodiscard]] const QGrpcCallOptions &callOptions() const & noexcept;
#if QT_DEPRECATED_SINCE(6, 13)
void serverMetadata() const && = delete;
QT_DEPRECATED_VERSION_X_6_13("Use serverInitialMetadata()")
[[nodiscard]] const QHash<QByteArray, QByteArray> &serverMetadata() const & noexcept;
QT_DEPRECATED_VERSION_X_6_13("Use setServerInitialMetadata(QMultiHash&&)")
void setServerMetadata(const QHash<QByteArray, QByteArray> &metadata);
QT_DEPRECATED_VERSION_X_6_13("Use setServerInitialMetadata(QMultiHash&&)")
void setServerMetadata(QHash<QByteArray, QByteArray> &&metadata);
#endif // QT_DEPRECATED_SINCE(6, 13)
void serverInitialMetadata() const && = delete;
[[nodiscard]] const QMultiHash<QByteArray, QByteArray> &
serverInitialMetadata() const & noexcept;
void setServerInitialMetadata(QMultiHash<QByteArray, QByteArray> &&metadata);
void serverTrailingMetadata() const && = delete;
[[nodiscard]] const QMultiHash<QByteArray, QByteArray> &
serverTrailingMetadata() const & noexcept;
void setServerTrailingMetadata(QMultiHash<QByteArray, QByteArray> &&metadata);
[[nodiscard]] QMetaType responseMetaType() const;
void setResponseMetaType(QMetaType metaType);

View File

@ -44,6 +44,9 @@ private Q_SLOTS:
void deferredCancel();
void asyncClientStatusMessage();
void asyncStatusMessage();
#if QT_DEPRECATED_SINCE(6, 13)
void deprecatedMetadata();
#endif
void metadata();
};
@ -139,7 +142,9 @@ void QtGrpcClientUnaryCallTest::asyncStatusMessage()
QCOMPARE(args.first().value<QGrpcStatus>().message(), request.testFieldString());
}
void QtGrpcClientUnaryCallTest::metadata()
#if QT_DEPRECATED_SINCE(6, 13)
void QtGrpcClientUnaryCallTest::deprecatedMetadata()
{
QGrpcCallOptions opt;
QHash<QByteArray, QByteArray> clientMd{
@ -158,19 +163,52 @@ void QtGrpcClientUnaryCallTest::metadata()
QCOMPARE_EQ(args.count(), 1);
QVERIFY(args.first().value<QGrpcStatus>().isOk());
const auto &md = reply->metadata();
QT_IGNORE_DEPRECATIONS(const auto &md = reply->metadata();)
auto initialIt = md.equal_range("response_initial");
QCOMPARE_EQ(std::distance(initialIt.first, initialIt.second), 1);
QCOMPARE_EQ(initialIt.first.value(), "2");
}
auto trailingIt = md.equal_range("response_trailing");
QCOMPARE_EQ(std::distance(trailingIt.first, trailingIt.second), 1);
#endif // QT_DEPRECATED_SINCE(6, 13)
void QtGrpcClientUnaryCallTest::metadata()
{
QGrpcCallOptions opt;
QMultiHash<QByteArray, QByteArray> clientMd{
{ "request_initial", "3" },
{ "request_trailing", "2" },
{ "request_sum", "20" },
{ "request_sum", "10" }
};
opt.setMetadata(clientMd);
auto reply = client()->testMetadata({}, opt);
QSignalSpy replyFinishedSpy(reply.get(), &QGrpcCallReply::finished);
QVERIFY(replyFinishedSpy.isValid());
QTRY_COMPARE_EQ_WITH_TIMEOUT(replyFinishedSpy.count(), 1, FailTimeout);
const auto &args = replyFinishedSpy.first();
QCOMPARE_EQ(args.count(), 1);
QVERIFY(args.first().value<QGrpcStatus>().isOk());
const auto &initialMd = reply->serverInitialMetadata();
auto initialIt = initialMd.equal_range("response_initial");
QCOMPARE_EQ(std::distance(initialIt.first, initialIt.second), 3);
QCOMPARE_EQ(initialIt.first.value(), "2");
std::advance(initialIt.first, 1);
QCOMPARE_EQ(initialIt.first.value(), "1");
std::advance(initialIt.first, 1);
QCOMPARE_EQ(initialIt.first.value(), "0");
const auto &trailingMd = reply->serverTrailingMetadata();
auto trailingIt = trailingMd.equal_range("response_trailing");
QCOMPARE_EQ(std::distance(trailingIt.first, trailingIt.second), 2);
QCOMPARE_EQ(trailingIt.first.value(), "1");
std::advance(trailingIt.first, 1);
QCOMPARE_EQ(trailingIt.first.value(), "0");
auto sumIt = md.equal_range("response_sum");
auto sumIt = trailingMd.equal_range("response_sum");
QCOMPARE_EQ(std::distance(sumIt.first, sumIt.second), 1);
auto sum = sumIt.first.value();
QVERIFY(sum == "20"_ba || sum == "10"_ba);
QCOMPARE_EQ(sumIt.first.value(), "30"_ba);
}
QTEST_MAIN(QtGrpcClientUnaryCallTest)