Skip to main content

What's New in the Qt GRPC library in 6.11

Comments

Qt 6.11 brings a set of meaningful improvements to the Qt GRPC library, focusing on stability, safety, performance, and new capabilities that make building gRPC™ based applications in Qt more powerful and productive.

Stability, Safety, and Performance

Much of the work in this release happened beneath the surface. We significantly improved the internal testing infrastructure for Qt GRPC, which allowed us to exercise a much broader range of edge cases, including protocol corner cases and transport-level behavior that was previously difficult to cover. As a direct result of this expanded test coverage, numerous subtle issues were detected and resolved. Many of these fixes have been backported to older branches, including Qt 6.8, so users on long-term-supported versions benefit as well.

On the transport side, QGrpcHttp2Channel now supports reading compressed messages. gRPC allows message payloads to be compressed at the protocol level, and Qt GRPC clients can now handle servers that make use of this capability transparently.

New API Additions

Two smaller but useful additions ship with the API release.

serverInitialMetadataReceived: A signal that fires as soon as the server initial metadata arrives, allowing early verification, UI feedback, or cancellation decisions before any response messages.

Metadata filtering: Available on both QGrpcChannelOptions and QGrpcCallOptions, this option controls whether protocol-related server metadata is included in the results. By default, it is disabled.

Interceptors

Qt GRPC 6.11 introduces the client-side interceptor mechanism. Interceptors are a well-established pattern in other gRPC implementations and are now available natively in Qt GRPC.

What Are Interceptors?

Interceptors provide a composable way to implement behavior that should apply consistently across many RPCs, without modifying your application's call sites. They are the right tool for cross-cutting concerns such as logging, authentication, metrics collection, or policy enforcement.

Interceptors are installed on a channel via a QGrpcInterceptorChain and hook into specific stages of the RPC lifecycle. See the Qt GRPC Interceptors Overview  for a general introduction and guidance on using interceptors.

Adding Interceptors to the Chat Example

Below, we extend the Qt GRPC Chat example by introducing a simple LoggingInterceptor. This interceptor captures activity at every interception point and forwards structured log entries to a LogModel. The model is then exposed to QML and displayed using a ListView.

The LoggingInterceptor reimplements all available interception hooks, allowing it to observe and log each stage of an RPC. From startup through message exchange to final completion. Its declaration is shown below:

class LoggingInterceptor final : public QGrpcStartInterceptor, public QGrpcInitialMetadataInterceptor, public QGrpcMessageReceivedInterceptor, public QGrpcWriteMessageInterceptor, public QGrpcWritesDoneInterceptor, public QGrpcTrailingMetadataInterceptor, public QGrpcCancelInterceptor, public QGrpcFinishedInterceptor { public: LoggingInterceptor(std::shared_ptr<LogModel> logModel); ~LoggingInterceptor() override; Continuation onStart(QGrpcInterceptionContext &context, QProtobufMessage &message, QGrpcCallOptions &callOptions) override; void onInitialMetadata(QGrpcInterceptionContext &context, QMultiHash<QByteArray, QByteArray> &metadata) override; void onMessageReceived(QGrpcInterceptionContext &context, QByteArray &messageData) override; void onWriteMessage(QGrpcInterceptionContext &context, QProtobufMessage &message) override; void onWritesDone(QGrpcInterceptionContext &context) override; void onTrailingMetadata(QGrpcInterceptionContext &context, QMultiHash<QByteArray, QByteArray> &metadata) override; void onCancel(QGrpcInterceptionContext &context) override; void onFinished(QGrpcInterceptionContext &context, QGrpcStatus &status) override; private: std::shared_ptr<LogModel> mLog; using Clock = std::chrono::steady_clock; using Ms = std::chrono::duration<double, std::milli>; QHash<quint64, Clock::time_point> m_activeRPCs; };

The implementation itself is fairly straightforward and focuses on logging the most relevant events at each interception point, while also measuring the total duration of each RPC from start to finish.

#include "logginginterceptor.h" #include <QtGrpc/QGrpcStatus> using namespace Qt::Literals::StringLiterals; LoggingInterceptor::LoggingInterceptor(std::shared_ptr<LogModel> logModel) : mLog(std::move(logModel)) { Q_ASSERT(mLog); } LoggingInterceptor::~LoggingInterceptor() = default; LoggingInterceptor::Continuation LoggingInterceptor::onStart(QGrpcInterceptionContext &context, QProtobufMessage &, QGrpcCallOptions &) { const auto id = context.operationId(); m_activeRPCs.insert(id, Clock::now()); mLog->add(LogModel::Level::Debug, id, context.descriptor(), u"Starting"_s); return Continuation::Proceed; } void LoggingInterceptor::onInitialMetadata(QGrpcInterceptionContext &context, QMultiHash<QByteArray, QByteArray> &metadata) { if (metadata.isEmpty()) return; mLog->add(LogModel::Level::Info, context.operationId(), context.descriptor(), u"Initial metadata: %1"_s.arg(QDebug::toString(metadata))); } void LoggingInterceptor::onMessageReceived(QGrpcInterceptionContext &context, QByteArray &messageData) { mLog->add(LogModel::Level::Debug, context.operationId(), context.descriptor(), u"Received %2 bytes"_s.arg(messageData.size())); } void LoggingInterceptor::onWriteMessage(QGrpcInterceptionContext &context, QProtobufMessage &) { mLog->add(LogModel::Level::Debug, context.operationId(), context.descriptor(), u"About to write message"_s); } void LoggingInterceptor::onWritesDone(QGrpcInterceptionContext &context) { mLog->add(LogModel::Level::Info, context.operationId(), context.descriptor(), u"About to finish communication"_s); } void LoggingInterceptor::onTrailingMetadata(QGrpcInterceptionContext &context, QMultiHash<QByteArray, QByteArray> &metadata) { if (metadata.isEmpty()) return; mLog->add(LogModel::Level::Info, context.operationId(), context.descriptor(), u"Trailing metadata: %1"_s.arg(QDebug::toString(metadata))); } void LoggingInterceptor::onCancel(QGrpcInterceptionContext &context) { mLog->add(LogModel::Level::Info, context.operationId(), context.descriptor(), u"About to cancel the RPC"_s); } void LoggingInterceptor::onFinished(QGrpcInterceptionContext &context, QGrpcStatus &status) { const auto it = m_activeRPCs.find(context.operationId()); Q_ASSERT(it != m_activeRPCs.cend()); const auto duration = Ms(Clock::now() - it.value()).count(); m_activeRPCs.erase(it); const auto codeStr = QDebug::toString(status.code()).section("::", -1); auto msg = u"Finished in %1 ms. StatusCode: %2"_s.arg(duration).arg(codeStr); if (!status.message().isEmpty()) msg += u", Message: "_s + status.message(); const auto level = [&] { switch (status.code()) { case QtGrpc::StatusCode::Ok: return LogModel::Level::Info; case QtGrpc::StatusCode::NotFound: case QtGrpc::StatusCode::Unauthenticated: return LogModel::Level::Warning; default: return LogModel::Level::Error; } }(); mLog->add(level, context.operationId(), context.descriptor(), msg); }

Running the QtGrpc Chat client with this interceptor in place now shows detailed logs for every RPC. With a single interceptor, we gain full visibility into the lifecycle of all calls executed over a channel.

interceptor_logs

At the time of writing, these updates to the QtGrpc Chat example have not yet been merged, but are expected in the next Qt 6.11 patch release. For a more advanced scenario, the Qt GRPC Client Guide  example demonstrates how three independent interceptors can work together, showcasing more complex interception workflows.

If there are features you rely on or areas you’d like to see explored, let us know.

 

Comments

Subscribe to our blog

Try Qt 6.11 Now!

Download the latest release here: www.qt.io/download

Qt 6.11 is now available, with new features and improvements for application developers and device creators.

We're Hiring

Check out all our open positions here and follow us on Instagram to see what it's like to be #QtPeople.