React on QQmlGrpcMetadata::dataChanged in QQmlGrpcChannelOptions

It's necessary not only to set the inner metadata when the setMetadata
is called, but also when QQmlGrpcMetadata::data is changed. Otherwise
the internal metadata value will not be in sync.

As drive-by change, fix the usage of channel options in
QGrpcHttp2Channel. Instead of storing the options duplicate in
QGrpcHttp2Channel, use the QAbstractGrpcHttp2Channel copy.
Make QAbstractGrpcHttp2Channel::channelOptions public.

TODO: QGrpcHttp2Channel doesn't consider the changes of ssl
configuration and serialization format when updating the
channelOptions, but it should react respectively or disallow setting
those at runtime.

Pick-to: 6.8
Change-Id: I40cf8476679f83a2925d77bcd1d89f043a0b6e67
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
This commit is contained in:
Alexey Edelev 2024-08-25 18:39:02 +02:00
parent 4d1653d6f6
commit 6cf8e05703
4 changed files with 42 additions and 25 deletions

View File

@ -34,6 +34,9 @@ public:
[[nodiscard]] virtual std::shared_ptr<QAbstractProtobufSerializer>
serializer() const noexcept = 0;
[[nodiscard]] const QGrpcChannelOptions &channelOptions() const & noexcept;
void channelOptions() && = delete;
void setChannelOptions(const QGrpcChannelOptions &options) noexcept;
void setChannelOptions(QGrpcChannelOptions &&options) noexcept;
@ -47,9 +50,6 @@ protected:
virtual void clientStream(std::shared_ptr<QGrpcOperationContext> operationContext) = 0;
virtual void bidiStream(std::shared_ptr<QGrpcOperationContext> operationContext) = 0;
[[nodiscard]] const QGrpcChannelOptions &channelOptions() const & noexcept;
void channelOptions() && = delete;
private:
std::shared_ptr<QGrpcCallReply> call(QLatin1StringView method, QLatin1StringView service,
QByteArrayView arg,

View File

@ -159,14 +159,14 @@ class QGrpcHttp2ChannelPrivate : public QObject
{
Q_OBJECT
public:
explicit QGrpcHttp2ChannelPrivate(const QUrl &uri, const QGrpcChannelOptions &options);
explicit QGrpcHttp2ChannelPrivate(const QUrl &uri, QGrpcHttp2Channel *q);
~QGrpcHttp2ChannelPrivate() override;
void processOperation(const std::shared_ptr<QGrpcOperationContext> &operationContext,
bool endStream = false);
std::shared_ptr<QAbstractProtobufSerializer> serializer;
QUrl hostUri;
QGrpcChannelOptions channelOptions;
QGrpcHttp2Channel *q_ptr = nullptr;
private:
Q_DISABLE_COPY_MOVE(QGrpcHttp2ChannelPrivate)
@ -394,8 +394,8 @@ void QGrpcHttp2ChannelPrivate::Http2Handler::attachStream(QHttp2Stream *stream_)
std::optional<std::chrono::milliseconds> deadline;
if (channelOpPtr->callOptions().deadlineTimeout())
deadline = channelOpPtr->callOptions().deadlineTimeout();
else if (parentChannel->channelOptions.deadlineTimeout())
deadline = parentChannel->channelOptions.deadlineTimeout();
else if (parentChannel->q_ptr->channelOptions().deadlineTimeout())
deadline = parentChannel->q_ptr->channelOptions().deadlineTimeout();
if (deadline) {
// We have an active stream and a deadline. It's time to start the timer.
QObject::connect(&m_deadlineTimer, &QTimer::timeout, this,
@ -428,7 +428,7 @@ void QGrpcHttp2ChannelPrivate::Http2Handler::prepareInitialRequest(QGrpcOperatio
QGrpcHttp2ChannelPrivate
*channel)
{
auto &channelOptions = channel->channelOptions;
const auto &channelOptions = channel->q_ptr->channelOptions();
QByteArray service{ operationContext->service().data(), operationContext->service().size() };
QByteArray method{ operationContext->method().data(), operationContext->method().size() };
m_initialHeaders = HPack::HttpHeader{
@ -585,10 +585,10 @@ void QGrpcHttp2ChannelPrivate::Http2Handler::onDeadlineTimeout()
}
}
QGrpcHttp2ChannelPrivate::QGrpcHttp2ChannelPrivate(const QUrl &uri,
const QGrpcChannelOptions &options)
: hostUri(uri), channelOptions(options)
QGrpcHttp2ChannelPrivate::QGrpcHttp2ChannelPrivate(const QUrl &uri, QGrpcHttp2Channel *q)
: hostUri(uri), q_ptr(q)
{
auto channelOptions = q_ptr->channelOptions();
auto formatSuffix = channelOptions.serializationFormat().suffix();
const QByteArray defaultContentType = DefaultContentType.toByteArray();
const QByteArray contentTypeFromOptions = !formatSuffix.isEmpty()
@ -608,6 +608,7 @@ QGrpcHttp2ChannelPrivate::QGrpcHttp2ChannelPrivate(const QUrl &uri,
<< it.value() << ". Using protobuf format as the default one.";
channelOptions.setSerializationFormat(SerializationFormat::Default);
}
q_ptr->setChannelOptions(channelOptions);
} else if (it.value() != contentTypeFromOptions) {
warnAboutFormatConflict = true;
} else {
@ -652,13 +653,13 @@ QGrpcHttp2ChannelPrivate::QGrpcHttp2ChannelPrivate(const QUrl &uri,
};
} else
#if QT_CONFIG(ssl)
if (hostUri.scheme() == "https"_L1 || options.sslConfiguration()) {
if (hostUri.scheme() == "https"_L1 || channelOptions.sslConfiguration()) {
auto *sslSocket = initSocket<QSslSocket>();
if (hostUri.port() < 0) {
hostUri.setPort(443);
}
if (const auto userSslConfig = options.sslConfiguration(); userSslConfig) {
if (const auto userSslConfig = channelOptions.sslConfiguration(); userSslConfig) {
sslSocket->setSslConfiguration(*userSslConfig);
} else {
static const QByteArray h2NexProtocol = "h2"_ba;
@ -824,8 +825,7 @@ void QGrpcHttp2ChannelPrivate::deleteHandler(Http2Handler *handler)
Constructs QGrpcHttp2Channel with \a hostUri.
*/
QGrpcHttp2Channel::QGrpcHttp2Channel(const QUrl &hostUri)
: QAbstractGrpcChannel(),
d_ptr(std::make_unique<QGrpcHttp2ChannelPrivate>(hostUri, QGrpcChannelOptions{}))
: QAbstractGrpcChannel(), d_ptr(std::make_unique<QGrpcHttp2ChannelPrivate>(hostUri, this))
{
}
@ -834,7 +834,7 @@ QGrpcHttp2Channel::QGrpcHttp2Channel(const QUrl &hostUri)
*/
QGrpcHttp2Channel::QGrpcHttp2Channel(const QUrl &hostUri, const QGrpcChannelOptions &options)
: QAbstractGrpcChannel(options),
d_ptr(std::make_unique<QGrpcHttp2ChannelPrivate>(hostUri, options))
d_ptr(std::make_unique<QGrpcHttp2ChannelPrivate>(hostUri, this))
{
}
@ -892,7 +892,7 @@ void QGrpcHttp2Channel::bidiStream(std::shared_ptr<QGrpcOperationContext> operat
*/
std::shared_ptr<QAbstractProtobufSerializer> QGrpcHttp2Channel::serializer() const noexcept
{
return d_ptr->channelOptions.serializationFormat().serializer();
return channelOptions().serializationFormat().serializer();
}
QT_END_NAMESPACE

View File

@ -27,6 +27,7 @@ public:
#if QT_CONFIG(ssl)
QQmlSslConfiguration configuration;
#endif // QT_CONFIG(ssl)
QMetaObject::Connection metadataUpdateConnection;
};
QQmlGrpcChannelOptionsPrivate::QQmlGrpcChannelOptionsPrivate() : QObjectPrivate()
@ -68,12 +69,22 @@ void QQmlGrpcChannelOptions::setMetadata(QQmlGrpcMetadata *value)
{
Q_D(QQmlGrpcChannelOptions);
if (d->metadata != value) {
if (d->metadataUpdateConnection) {
disconnect(d->metadataUpdateConnection);
d->metadataUpdateConnection = {};
}
d->metadata = value;
if (d->metadata)
d->options.setMetadata(d->metadata->metadata());
else
d->options.setMetadata({});
emit metadataChanged();
if (d->metadata) {
const auto updateMetadata = [this]() {
Q_D(QQmlGrpcChannelOptions);
d->options.setMetadata(d->metadata->metadata());
emit metadataChanged();
};
d->metadataUpdateConnection = connect(d->metadata, &QQmlGrpcMetadata::dataChanged, this,
updateMetadata);
updateMetadata();
}
}
}

View File

@ -38,7 +38,10 @@ Item {
return Qt.createQmlObject("import QtQuick; import QtGrpc; GrpcHttp2Channel { \
hostUri: \"http://localhost:50051\"; \
options: GrpcChannelOptions { \
deadlineTimeout: { 2000 } } }", root)
deadlineTimeout: { 2000 } \
metadata: GrpcMetadata {
data: ({ \"common-meta-data\": \"test-channel-metadata\" }) \
}}}", root)
}
function createGrpcChannelWithDeadlineItem() {
@ -149,13 +152,16 @@ Item {
var missingHeaders = Array()
missingHeaders.push("user-name")
missingHeaders.push("user-password")
missingHeaders.push("common-meta-data")
for (var i = 0; i < unaryCallWithOptions.result.valuesData.length; i++) {
var md = unaryCallWithOptions.result.valuesData[i]
if (md.key == "user-name" && md.value == "localhost")
if (md.key === "user-name" && md.value === "localhost")
removeElementFromArray(missingHeaders, "user-name")
if (md.key == "user-password" && md.value == "qwerty")
if (md.key === "user-password" && md.value === "qwerty")
removeElementFromArray(missingHeaders, "user-password")
if (md.key === "common-meta-data" && md.value === "test-channel-metadata")
removeElementFromArray(missingHeaders, "common-meta-data")
}
verify(missingHeaders.length === 0,