2022-10-20 11:49:27 +00:00
|
|
|
// Copyright (C) 2022 The Qt Company Ltd.
|
|
|
|
// Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com>
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
|
|
|
|
2024-03-22 15:15:32 +00:00
|
|
|
#include <QtCore/qthread.h>
|
|
|
|
#include <QtCore/qtimer.h>
|
2023-02-02 09:03:34 +00:00
|
|
|
#include <QtGrpc/private/qabstractgrpcchannel_p.h>
|
2022-10-20 11:49:27 +00:00
|
|
|
#include <QtGrpc/qgrpccallreply.h>
|
|
|
|
#include <QtGrpc/qgrpcstream.h>
|
|
|
|
#include <QtProtobuf/qprotobufserializer.h>
|
2023-01-25 11:21:03 +00:00
|
|
|
|
2022-10-20 11:49:27 +00:00
|
|
|
#include <qtgrpcglobal_p.h>
|
|
|
|
|
2023-01-02 15:06:11 +00:00
|
|
|
#include <private/qobject_p.h>
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
#include "qgrpcclientbase.h"
|
2022-10-20 11:49:27 +00:00
|
|
|
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
2022-12-16 14:10:50 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2022-10-20 11:49:27 +00:00
|
|
|
namespace {
|
|
|
|
static QString threadSafetyWarning(QLatin1StringView methodName)
|
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
return QGrpcClientBase::tr("%1 is called from a different thread.\n"
|
2024-04-05 11:20:04 +00:00
|
|
|
"Qt GRPC doesn't guarantee thread safety on the channel level.\n"
|
|
|
|
"You have to be confident that channel routines are working in "
|
2024-04-19 12:18:32 +00:00
|
|
|
"the same thread as QGrpcClientBase.")
|
2024-04-05 11:20:04 +00:00
|
|
|
.arg(methodName);
|
2022-10-20 11:49:27 +00:00
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
/*!
|
2024-04-19 12:18:32 +00:00
|
|
|
\class QGrpcClientBase
|
2023-03-10 09:49:15 +00:00
|
|
|
\inmodule QtGrpc
|
2024-04-19 12:18:32 +00:00
|
|
|
\brief The QGrpcClientBase class is bridge between gRPC clients
|
2022-12-08 12:53:08 +00:00
|
|
|
and channels.
|
2022-10-20 11:49:27 +00:00
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase provides a set of functions for client classes
|
2022-12-08 12:53:08 +00:00
|
|
|
generated out of protobuf services.
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase enforces thread safety for startStream() and call() methods
|
2022-12-08 12:53:08 +00:00
|
|
|
of generated clients.
|
2024-04-19 12:18:32 +00:00
|
|
|
The methods QGrpcClientBase::call() and QGrpcClientBase::startStream()
|
2023-02-21 14:34:07 +00:00
|
|
|
should only be called by the generated client classes.
|
2022-12-08 12:53:08 +00:00
|
|
|
*/
|
2022-10-20 11:49:27 +00:00
|
|
|
|
|
|
|
/*!
|
2024-04-19 12:18:32 +00:00
|
|
|
\fn template <typename StreamType> std::shared_ptr<StreamType> QGrpcClientBase::startStream(QLatin1StringView method, const QProtobufMessage &arg, const QGrpcCallOptions &options)
|
2022-10-20 11:49:27 +00:00
|
|
|
|
2023-09-06 10:53:08 +00:00
|
|
|
Starts the stream \a method of the \e StreamType type with the message
|
2022-12-08 12:53:08 +00:00
|
|
|
argument \a arg to the attached channel.
|
2023-08-13 16:00:42 +00:00
|
|
|
|
|
|
|
Uses \a options argument to set additional parameter in the stream
|
|
|
|
communication.
|
|
|
|
|
2023-09-06 10:53:08 +00:00
|
|
|
The implementation is only available for \e StreamType:
|
2023-08-13 16:00:42 +00:00
|
|
|
QGrpcServerStream, QGrpcClientStream, and QGrpcBidirStream.
|
2022-12-08 12:53:08 +00:00
|
|
|
*/
|
2022-10-20 11:49:27 +00:00
|
|
|
|
2024-01-14 19:03:29 +00:00
|
|
|
/*!
|
2024-04-19 12:18:32 +00:00
|
|
|
\fn void QGrpcClientBase::channelChanged()
|
2024-01-14 19:03:29 +00:00
|
|
|
\since 6.7
|
|
|
|
|
|
|
|
Indicates that a new channel is attached to the client.
|
|
|
|
*/
|
|
|
|
|
2024-01-14 19:22:07 +00:00
|
|
|
/*!
|
2024-04-19 12:18:32 +00:00
|
|
|
\fn void QGrpcClientBase::errorOccurred(const QGrpcStatus &status);
|
2024-01-14 19:22:07 +00:00
|
|
|
|
|
|
|
Indicates that an error occurred during serialization.
|
|
|
|
|
|
|
|
This signal is emitted when an error with \a status occurs in the channel
|
|
|
|
or during serialization.
|
|
|
|
|
|
|
|
\sa QGrpcOperation::errorOccurred
|
|
|
|
*/
|
|
|
|
|
2024-01-14 19:03:29 +00:00
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
class QGrpcClientBasePrivate : public QObjectPrivate
|
2022-10-20 11:49:27 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_DECLARE_PUBLIC(QGrpcClientBase)
|
2022-10-20 11:49:27 +00:00
|
|
|
public:
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBasePrivate(QLatin1StringView service) : service(service)
|
2022-12-16 14:10:50 +00:00
|
|
|
{
|
|
|
|
}
|
2022-10-20 11:49:27 +00:00
|
|
|
|
2023-08-06 13:42:23 +00:00
|
|
|
QGrpcStatus checkThread(QLatin1StringView warningPreamble);
|
2023-08-13 16:00:42 +00:00
|
|
|
bool checkChannel();
|
|
|
|
void addStream(std::shared_ptr<QGrpcOperation> stream);
|
|
|
|
void removeStream(std::shared_ptr<QGrpcOperation> stream);
|
2024-04-02 12:05:34 +00:00
|
|
|
std::shared_ptr<QAbstractProtobufSerializer> serializer() const {
|
|
|
|
if (const auto &c = channel)
|
|
|
|
return c->serializer();
|
|
|
|
return nullptr;
|
|
|
|
}
|
2023-08-06 13:42:23 +00:00
|
|
|
|
2022-12-09 13:03:47 +00:00
|
|
|
std::shared_ptr<QAbstractGrpcChannel> channel;
|
2023-09-22 14:56:02 +00:00
|
|
|
const QLatin1StringView service;
|
2023-08-13 16:00:42 +00:00
|
|
|
std::vector<std::shared_ptr<QGrpcOperation>> activeStreams;
|
2022-10-20 11:49:27 +00:00
|
|
|
};
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcStatus QGrpcClientBasePrivate::checkThread(QLatin1StringView warningPreamble)
|
2023-08-06 13:42:23 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_Q(QGrpcClientBase);
|
2023-08-06 13:42:23 +00:00
|
|
|
|
|
|
|
QGrpcStatus status;
|
|
|
|
if (q->thread() != QThread::currentThread()) {
|
2024-04-02 13:23:55 +00:00
|
|
|
status = QGrpcStatus{ QGrpcStatus::Unknown, threadSafetyWarning(warningPreamble) };
|
2023-08-06 13:42:23 +00:00
|
|
|
qGrpcCritical() << status.message();
|
|
|
|
emit q->errorOccurred(status);
|
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
bool QGrpcClientBasePrivate::checkChannel()
|
2023-08-13 16:00:42 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_Q(QGrpcClientBase);
|
2023-08-13 16:00:42 +00:00
|
|
|
|
|
|
|
if (!channel) {
|
2024-04-05 11:20:04 +00:00
|
|
|
emit q->errorOccurred(QGrpcStatus{ QGrpcStatus::Unknown,
|
|
|
|
q->tr("No channel(s) attached.") });
|
2023-08-13 16:00:42 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
void QGrpcClientBasePrivate::addStream(std::shared_ptr<QGrpcOperation> grpcStream)
|
2023-08-13 16:00:42 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_Q(QGrpcClientBase);
|
2023-08-13 16:00:42 +00:00
|
|
|
auto errorConnection = std::make_shared<QMetaObject::Connection>();
|
|
|
|
auto finishedConnection = std::make_shared<QMetaObject::Connection>();
|
|
|
|
|
|
|
|
*errorConnection = QObject::connect(
|
|
|
|
grpcStream.get(), &QGrpcOperation::errorOccurred, q,
|
|
|
|
[this, grpcStream, finishedConnection, errorConnection](const QGrpcStatus &status) {
|
|
|
|
QObject::disconnect(*errorConnection);
|
|
|
|
QObject::disconnect(*finishedConnection);
|
|
|
|
|
|
|
|
qGrpcWarning() << grpcStream->method() << "call" << service
|
|
|
|
<< "stream error: " << status.message();
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_Q(QGrpcClientBase);
|
2023-08-13 16:00:42 +00:00
|
|
|
emit q->errorOccurred(status);
|
|
|
|
removeStream(std::move(grpcStream));
|
|
|
|
});
|
|
|
|
|
|
|
|
*finishedConnection = QObject::connect(
|
|
|
|
grpcStream.get(), &QGrpcOperation::finished, q,
|
|
|
|
[this, grpcStream, errorConnection, finishedConnection]() mutable {
|
|
|
|
QObject::disconnect(*errorConnection);
|
|
|
|
QObject::disconnect(*finishedConnection);
|
|
|
|
|
|
|
|
qGrpcWarning() << grpcStream->method() << "call" << service << "stream finished.";
|
|
|
|
removeStream(std::move(grpcStream));
|
|
|
|
});
|
|
|
|
|
|
|
|
activeStreams.push_back(grpcStream);
|
|
|
|
}
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
void QGrpcClientBasePrivate::removeStream(std::shared_ptr<QGrpcOperation> grpcStream)
|
2023-08-13 16:00:42 +00:00
|
|
|
{
|
|
|
|
auto it = std::find(activeStreams.begin(), activeStreams.end(), grpcStream);
|
|
|
|
if (it != activeStreams.end())
|
|
|
|
activeStreams.erase(it);
|
|
|
|
|
|
|
|
grpcStream.reset();
|
|
|
|
}
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase::QGrpcClientBase(QLatin1StringView service, QObject *parent)
|
|
|
|
: QObject(*new QGrpcClientBasePrivate(service), parent)
|
2022-10-20 11:49:27 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase::~QGrpcClientBase() = default;
|
2022-10-20 11:49:27 +00:00
|
|
|
|
|
|
|
/*!
|
|
|
|
Attaches \a channel to client as transport layer for gRPC.
|
2022-12-08 12:53:08 +00:00
|
|
|
|
|
|
|
Parameters and return values will be serialized to the channel
|
|
|
|
in a format it supports.
|
2022-10-20 11:49:27 +00:00
|
|
|
|
2022-12-13 11:28:02 +00:00
|
|
|
\note \b Warning: Qt GRPC doesn't guarantee thread safety on the channel level.
|
2022-12-08 12:53:08 +00:00
|
|
|
You have to invoke the channel-related functions on the same thread as
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase.
|
2022-10-20 11:49:27 +00:00
|
|
|
*/
|
2024-04-19 12:18:32 +00:00
|
|
|
void QGrpcClientBase::attachChannel(const std::shared_ptr<QAbstractGrpcChannel> &channel)
|
2022-10-20 11:49:27 +00:00
|
|
|
{
|
2023-01-25 11:21:03 +00:00
|
|
|
if (channel->dPtr->threadId != QThread::currentThreadId()) {
|
2024-04-19 12:18:32 +00:00
|
|
|
const QString status = threadSafetyWarning("QGrpcClientBase::attachChannel"_L1);
|
2023-04-25 11:35:36 +00:00
|
|
|
qGrpcCritical() << status;
|
2024-04-02 13:23:55 +00:00
|
|
|
emit errorOccurred(QGrpcStatus{ QGrpcStatus::Unknown, status });
|
2022-10-20 11:49:27 +00:00
|
|
|
return;
|
|
|
|
}
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_D(QGrpcClientBase);
|
2023-08-13 16:00:42 +00:00
|
|
|
for (auto &stream : d->activeStreams) {
|
|
|
|
assert(stream != nullptr);
|
2023-08-06 20:07:05 +00:00
|
|
|
stream->cancel();
|
2023-08-13 16:00:42 +00:00
|
|
|
}
|
2022-12-08 12:53:08 +00:00
|
|
|
|
2023-01-02 15:06:11 +00:00
|
|
|
d->channel = channel;
|
2023-07-25 12:29:23 +00:00
|
|
|
emit channelChanged();
|
|
|
|
}
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
std::shared_ptr<QGrpcCallReply> QGrpcClientBase::call(QLatin1StringView method,
|
2024-01-20 06:27:34 +00:00
|
|
|
const QProtobufMessage &arg,
|
|
|
|
const QGrpcCallOptions &options)
|
|
|
|
{
|
|
|
|
std::optional<QByteArray> argData = trySerialize(arg);
|
2024-04-19 12:41:53 +00:00
|
|
|
if (!argData) {
|
|
|
|
Q_EMIT errorOccurred(QGrpcStatus{ QGrpcStatus::Unknown,
|
|
|
|
tr("Serializing failed. Serializer is not ready.") });
|
2024-01-20 06:27:34 +00:00
|
|
|
return {};
|
2024-04-19 12:41:53 +00:00
|
|
|
}
|
2024-01-20 06:27:34 +00:00
|
|
|
return call(method, *argData, options);
|
|
|
|
}
|
|
|
|
|
2023-07-25 12:29:23 +00:00
|
|
|
/*!
|
2024-01-14 19:03:29 +00:00
|
|
|
\since 6.7
|
|
|
|
Returns the channel attached to this client.
|
|
|
|
*/
|
2024-04-19 12:18:32 +00:00
|
|
|
std::shared_ptr<QAbstractGrpcChannel> QGrpcClientBase::channel() const noexcept
|
2023-07-25 12:29:23 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_D(const QGrpcClientBase);
|
2023-07-25 12:29:23 +00:00
|
|
|
return d->channel;
|
2022-10-20 11:49:27 +00:00
|
|
|
}
|
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
std::shared_ptr<QGrpcCallReply> QGrpcClientBase::call(QLatin1StringView method,
|
2023-05-09 11:49:22 +00:00
|
|
|
QByteArrayView arg,
|
|
|
|
const QGrpcCallOptions &options)
|
2022-10-20 11:49:27 +00:00
|
|
|
{
|
2022-12-09 13:03:47 +00:00
|
|
|
std::shared_ptr<QGrpcCallReply> reply;
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_D(QGrpcClientBase);
|
|
|
|
if (d->checkThread("QGrpcClientBase::call"_L1) != QGrpcStatus::Ok)
|
2023-08-06 13:42:23 +00:00
|
|
|
return reply;
|
2023-01-02 15:06:11 +00:00
|
|
|
|
2023-08-13 16:00:42 +00:00
|
|
|
if (!d->checkChannel())
|
|
|
|
return reply;
|
|
|
|
|
|
|
|
reply = d->channel->call(method, QLatin1StringView(d->service), arg, options);
|
|
|
|
|
|
|
|
auto errorConnection = std::make_shared<QMetaObject::Connection>();
|
|
|
|
*errorConnection = connect(reply.get(), &QGrpcCallReply::errorOccurred, this,
|
|
|
|
[this, reply, errorConnection](const QGrpcStatus &status) {
|
|
|
|
QObject::disconnect(*errorConnection);
|
|
|
|
emit errorOccurred(status);
|
|
|
|
});
|
2022-10-20 11:49:27 +00:00
|
|
|
|
2024-04-03 20:48:04 +00:00
|
|
|
|
|
|
|
QT_WARNING_PUSH
|
|
|
|
QT_WARNING_DISABLE_MSVC(4573)
|
|
|
|
connect(reply.get(), &QGrpcCallReply::finished, this, [errorConnection] {
|
|
|
|
QObject::disconnect(*errorConnection);
|
|
|
|
});
|
|
|
|
QT_WARNING_POP
|
2022-10-20 11:49:27 +00:00
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
2023-08-06 20:53:17 +00:00
|
|
|
std::shared_ptr<QGrpcServerStream>
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase::startServerStream(QLatin1StringView method, QByteArrayView arg,
|
2023-09-11 16:54:51 +00:00
|
|
|
const QGrpcCallOptions &options)
|
2022-10-20 11:49:27 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_D(QGrpcClientBase);
|
2022-10-20 11:49:27 +00:00
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
if (d->checkThread("QGrpcClientBase::startStream<QGrpcServerStream>"_L1) != QGrpcStatus::Ok)
|
2023-08-13 16:00:42 +00:00
|
|
|
return {};
|
|
|
|
|
|
|
|
if (!d->checkChannel())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
auto grpcStream =
|
|
|
|
d->channel->startServerStream(method, QLatin1StringView(d->service), arg, options);
|
|
|
|
d->addStream(std::static_pointer_cast<QGrpcOperation>(grpcStream));
|
|
|
|
return grpcStream;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<QGrpcClientStream>
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase::startClientStream(QLatin1StringView method, QByteArrayView arg,
|
2023-09-11 16:54:51 +00:00
|
|
|
const QGrpcCallOptions &options)
|
2023-08-13 16:00:42 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_D(QGrpcClientBase);
|
2023-08-13 16:00:42 +00:00
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
if (d->checkThread("QGrpcClientBase::startStream<QGrpcClientStream>"_L1) != QGrpcStatus::Ok)
|
2023-08-13 16:00:42 +00:00
|
|
|
return {};
|
|
|
|
|
|
|
|
if (!d->checkChannel())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
auto grpcStream =
|
|
|
|
d->channel->startClientStream(method, QLatin1StringView(d->service), arg, options);
|
|
|
|
d->addStream(std::static_pointer_cast<QGrpcOperation>(grpcStream));
|
|
|
|
return grpcStream;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::shared_ptr<QGrpcBidirStream>
|
2024-04-19 12:18:32 +00:00
|
|
|
QGrpcClientBase::startBidirStream(QLatin1StringView method, QByteArrayView arg,
|
2023-09-11 16:54:51 +00:00
|
|
|
const QGrpcCallOptions &options)
|
2023-08-13 16:00:42 +00:00
|
|
|
{
|
2024-04-19 12:18:32 +00:00
|
|
|
Q_D(QGrpcClientBase);
|
2023-08-13 16:00:42 +00:00
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
if (d->checkThread("QGrpcClientBase::startStream<QGrpcBidirStream>"_L1) != QGrpcStatus::Ok)
|
2023-08-13 16:00:42 +00:00
|
|
|
return {};
|
|
|
|
|
|
|
|
if (!d->checkChannel())
|
|
|
|
return {};
|
|
|
|
|
|
|
|
auto grpcStream =
|
|
|
|
d->channel->startBidirStream(method, QLatin1StringView(d->service), arg, options);
|
|
|
|
d->addStream(std::static_pointer_cast<QGrpcOperation>(grpcStream));
|
2022-10-20 11:49:27 +00:00
|
|
|
return grpcStream;
|
|
|
|
}
|
|
|
|
|
2024-04-19 12:41:53 +00:00
|
|
|
std::optional<QByteArray> QGrpcClientBase::trySerialize(const QProtobufMessage &arg) const
|
2024-01-20 06:27:34 +00:00
|
|
|
{
|
2024-04-19 12:41:53 +00:00
|
|
|
Q_D(const QGrpcClientBase);
|
2024-01-20 06:27:34 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
2024-04-19 12:41:53 +00:00
|
|
|
auto serializer = d->serializer();
|
|
|
|
if (serializer == nullptr)
|
2024-01-20 06:27:34 +00:00
|
|
|
return std::nullopt;
|
2024-04-19 12:41:53 +00:00
|
|
|
|
|
|
|
return serializer->serialize(&arg);
|
2024-01-20 06:27:34 +00:00
|
|
|
}
|
|
|
|
|
2022-10-20 11:49:27 +00:00
|
|
|
QT_END_NAMESPACE
|
2022-12-09 12:03:04 +00:00
|
|
|
|
2024-04-19 12:18:32 +00:00
|
|
|
#include "moc_qgrpcclientbase.cpp"
|