diff --git a/src/grpc/qgrpchttp2channel.cpp b/src/grpc/qgrpchttp2channel.cpp index 3a3a80f4..2cdb9136 100644 --- a/src/grpc/qgrpchttp2channel.cpp +++ b/src/grpc/qgrpchttp2channel.cpp @@ -771,7 +771,7 @@ void Http2Handler::handleHeaders(const HPack::HttpHeader &headers, HeaderPhase p false, }; - QHash metadata; + QMultiHash metadata; std::optional 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(); } diff --git a/src/grpc/qgrpcoperation.cpp b/src/grpc/qgrpcoperation.cpp index 922c36f4..47b8458c 100644 --- a/src/grpc/qgrpcoperation.cpp +++ b/src/grpc/qgrpcoperation.cpp @@ -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 &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 &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 &QGrpcOperation::serverTrailingMetadata() const & noexcept +{ + Q_D(const QGrpcOperation); + return d->operationContext->serverTrailingMetadata(); } /*! diff --git a/src/grpc/qgrpcoperation.h b/src/grpc/qgrpcoperation.h index 294e792f..b1b98e16 100644 --- a/src/grpc/qgrpcoperation.h +++ b/src/grpc/qgrpcoperation.h @@ -13,6 +13,7 @@ #include #include #include +#include #include @@ -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 &metadata() const & noexcept; void metadata() const && = delete; +#endif + + [[nodiscard]] const QMultiHash & + serverInitialMetadata() const & noexcept; + [[nodiscard]] const QMultiHash & + serverTrailingMetadata() const & noexcept; [[nodiscard]] QLatin1StringView method() const noexcept; diff --git a/src/grpc/qgrpcoperationcontext.cpp b/src/grpc/qgrpcoperationcontext.cpp index db1ac2d9..8b889ba3 100644 --- a/src/grpc/qgrpcoperationcontext.cpp +++ b/src/grpc/qgrpcoperationcontext.cpp @@ -1,6 +1,7 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only +#include #include #include #include @@ -158,8 +159,12 @@ public: QByteArray argument; QGrpcCallOptions options; std::shared_ptr serializer; - QHash serverMetadata; QMetaType responseMetaType; + QMultiHash serverInitialMetadata; +#if QT_DEPRECATED_SINCE(6, 13) + QHash deprServerInitialMetadata; +#endif + QMultiHash 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 &QGrpcOperationContext::serverMetadata() const & noexcept { Q_D(const QGrpcOperationContext); - return d->serverMetadata; + return d->deprServerInitialMetadata; } /*! \fn void QGrpcOperationContext::setServerMetadata(const QHash &metadata) \fn void QGrpcOperationContext::setServerMetadata(QHash &&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 &metadata) { Q_D(QGrpcOperationContext); - d->serverMetadata = metadata; + if (d->deprServerInitialMetadata == metadata) + return; + d->deprServerInitialMetadata = metadata; + d->serverInitialMetadata = QMultiHash(metadata); } - void QGrpcOperationContext::setServerMetadata(QHash &&metadata) { Q_D(QGrpcOperationContext); - d->serverMetadata = std::move(metadata); + if (d->deprServerInitialMetadata == metadata) + return; + d->deprServerInitialMetadata = std::move(metadata); + d->serverInitialMetadata = QMultiHash(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 & +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 &&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 & +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 &&metadata) +{ + Q_D(QGrpcOperationContext); + if (d->serverTrailingMetadata == metadata) + return; + d->serverTrailingMetadata = std::move(metadata); } /*! diff --git a/src/grpc/qgrpcoperationcontext.h b/src/grpc/qgrpcoperationcontext.h index 6dcf416b..61b5a1fe 100644 --- a/src/grpc/qgrpcoperationcontext.h +++ b/src/grpc/qgrpcoperationcontext.h @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -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 &serverMetadata() const & noexcept; + QT_DEPRECATED_VERSION_X_6_13("Use setServerInitialMetadata(QMultiHash&&)") void setServerMetadata(const QHash &metadata); + QT_DEPRECATED_VERSION_X_6_13("Use setServerInitialMetadata(QMultiHash&&)") void setServerMetadata(QHash &&metadata); +#endif // QT_DEPRECATED_SINCE(6, 13) + + void serverInitialMetadata() const && = delete; + [[nodiscard]] const QMultiHash & + serverInitialMetadata() const & noexcept; + void setServerInitialMetadata(QMultiHash &&metadata); + + void serverTrailingMetadata() const && = delete; + [[nodiscard]] const QMultiHash & + serverTrailingMetadata() const & noexcept; + void setServerTrailingMetadata(QMultiHash &&metadata); [[nodiscard]] QMetaType responseMetaType() const; void setResponseMetaType(QMetaType metaType); diff --git a/tests/auto/grpc/client/unarycall/tst_grpc_client_unarycall.cpp b/tests/auto/grpc/client/unarycall/tst_grpc_client_unarycall.cpp index 9cbe869b..b50af773 100644 --- a/tests/auto/grpc/client/unarycall/tst_grpc_client_unarycall.cpp +++ b/tests/auto/grpc/client/unarycall/tst_grpc_client_unarycall.cpp @@ -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().message(), request.testFieldString()); } -void QtGrpcClientUnaryCallTest::metadata() +#if QT_DEPRECATED_SINCE(6, 13) + +void QtGrpcClientUnaryCallTest::deprecatedMetadata() { QGrpcCallOptions opt; QHash clientMd{ @@ -158,19 +163,52 @@ void QtGrpcClientUnaryCallTest::metadata() QCOMPARE_EQ(args.count(), 1); QVERIFY(args.first().value().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 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().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)