qabstractgrpcclient.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>
  5. *
  6. * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy of this
  9. * software and associated documentation files (the "Software"), to deal in the Software
  10. * without restriction, including without limitation the rights to use, copy, modify,
  11. * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
  12. * to permit persons to whom the Software is furnished to do so, subject to the following
  13. * conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in all copies
  16. * or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  19. * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  20. * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  21. * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  22. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. * DEALINGS IN THE SOFTWARE.
  24. */
  25. #pragma once //QAbstractGrpcClient
  26. #include <memory>
  27. #include <functional>
  28. #include <type_traits>
  29. #include <QObject>
  30. #include <QPointer>
  31. #include <QByteArray>
  32. #include <qtprotobuflogging.h>
  33. #include <qabstractprotobufserializer.h>
  34. #include "qabstractgrpcchannel.h"
  35. #include "qtgrpcglobal.h"
  36. /*!
  37. * \defgroup QtGrpc
  38. * \brief Qt framework based gRPC clients and services
  39. */
  40. namespace QtProtobuf {
  41. class QGrpcCallReply;
  42. class QGrpcStream;
  43. class QGrpcAsyncOperationBase;
  44. class QAbstractGrpcChannel;
  45. class QAbstractGrpcClientPrivate;
  46. /*!
  47. * \private
  48. */
  49. using StreamHandler = std::function<void(const QByteArray&)>;
  50. /*!
  51. * \ingroup QtGrpc
  52. * \brief The QAbstractGrpcClient class is bridge between gRPC clients and channels. QAbstractGrpcClient provides set of
  53. * bridge functions for client classes generated out of protobuf services.
  54. * \details QAbstractGrpcClient provides threads safety for stream and call methods of generated clients.
  55. */
  56. class Q_GRPC_EXPORT QAbstractGrpcClient : public QObject
  57. {
  58. Q_OBJECT
  59. public:
  60. /*!
  61. * \brief Attaches \a channel to client as transport layer for gRPC. Parameters and return values will be serialized
  62. * to supported by channel format.
  63. * \note \b Warning: QtGrpc doesn't guarantie thread safety on channel level.
  64. * You have to be confident that channel routines are working in the same thread as QAbstractGrpcClient.
  65. * \see QAbstractGrcpChannel
  66. * \param channel Shared pointer to channel will be used as transport layer for gRPC
  67. */
  68. void attachChannel(const std::shared_ptr<QAbstractGrpcChannel> &channel);
  69. signals:
  70. /*!
  71. * \brief error signal is emited by client when error occured in channel or while serialization/deserialization
  72. * \param[out] code gRPC channel StatusCode
  73. * \param[out] errorText Error description from channel or from QGrpc
  74. */
  75. void error(const QGrpcStatus &status) const;
  76. protected:
  77. QAbstractGrpcClient(const QString &service, QObject *parent = nullptr);
  78. virtual ~QAbstractGrpcClient();
  79. /*!
  80. * \private
  81. * \brief Calls \p method of service client synchronously
  82. * \param[in] method Name of the method to be called
  83. * \param[in] arg Protobuf message argument for \p method
  84. * \param[out] ret A pointer to memory with protobuf message to write an gRPC reply to
  85. */
  86. template<typename A, typename R>
  87. QGrpcStatus call(const QString &method, const A &arg, const QPointer<R> &ret) {
  88. QGrpcStatus status{QGrpcStatus::Ok};
  89. if (ret.isNull()) {
  90. static const QString errorString("Unable to call method: %1. Pointer to return data is null");
  91. status = QGrpcStatus{QGrpcStatus::InvalidArgument, errorString.arg(method)};
  92. error(status);
  93. qProtoCritical() << errorString.arg(method);
  94. return status;
  95. }
  96. QByteArray retData;
  97. bool ok = false;
  98. QByteArray argData = trySerialize(arg, ok);
  99. if (ok) {
  100. status = call(method, argData, retData);
  101. if (status == QGrpcStatus::StatusCode::Ok) {
  102. status = tryDeserialize(*ret, retData);
  103. }
  104. } else {
  105. status = QGrpcStatus({QGrpcStatus::Unknown, QLatin1String("Serializing failed. Serializer is not ready")});
  106. }
  107. return status;
  108. }
  109. /*!
  110. * \private
  111. * \brief Calls \p method of service client asynchronously and returns pointer to assigned to call QGrpcCallReply
  112. * \param[in] method Name of the method to be called
  113. * \param[in] arg Protobuf message argument for \p method
  114. */
  115. template<typename A>
  116. QGrpcCallReplyShared call(const QString &method, const A &arg) {
  117. bool ok = false;
  118. QByteArray argData = trySerialize(arg, ok);
  119. if (!ok) {
  120. return QGrpcCallReplyShared();
  121. }
  122. return call(method, argData);
  123. }
  124. /*!
  125. * \private
  126. * \brief Streams to message notifications from server-stream with given message argument \a arg
  127. * \param[in] method Name of the method to be called
  128. * \param[in] arg Protobuf message argument for \p method
  129. * \param[out] signal Callback with return-message as input parameter that will be called each time message
  130. * update recevied from server-stream
  131. */
  132. template<typename A>
  133. QGrpcStreamShared stream(const QString &method, const A &arg) {
  134. bool ok = false;
  135. QByteArray argData = trySerialize(arg, ok);
  136. if (!ok) {
  137. return QGrpcStreamShared();
  138. }
  139. return stream(method, argData);
  140. }
  141. /*!
  142. * \private
  143. * \brief Streams to message notifications from server-stream with given message argument \a arg
  144. * \param[in] method Name of the method to be called
  145. * \param[in] arg Protobuf message argument for \p method
  146. * \param[out] ret Pointer to preallocated return-message structure. \p ret Structure fields will be update each
  147. * time message update recevied from server-stream.
  148. * \note If \p ret is used as property-fields in other object, property NOTIFY signal won't be called in case of
  149. * updated message recevied from server-stream
  150. */
  151. template<typename A, typename R>
  152. QGrpcStreamShared stream(const QString &method, const A &arg, const QPointer<R> &ret) {
  153. if (ret.isNull()) {
  154. static const QString nullPointerError("Unable to stream method: %1. Pointer to return data is null");
  155. error({QGrpcStatus::InvalidArgument, nullPointerError.arg(method)});
  156. qProtoCritical() << nullPointerError.arg(method);
  157. return nullptr;
  158. }
  159. bool ok = false;
  160. QByteArray argData = trySerialize(arg, ok);
  161. if (!ok) {
  162. return QGrpcStreamShared();
  163. }
  164. return stream(method, argData, [ret, this](const QByteArray &data) {
  165. if (!ret.isNull()) {
  166. tryDeserialize(*ret, data);
  167. } else {
  168. static const QLatin1String nullPointerError("Pointer to return data is null while stream update received");
  169. error({QGrpcStatus::InvalidArgument, nullPointerError});
  170. qProtoCritical() << nullPointerError;
  171. }
  172. });
  173. }
  174. /*!
  175. * \brief Canceles all streams for specified \p method
  176. * \param[in] method Name of method stream for to be canceled
  177. */
  178. void cancel(const QString &method);
  179. friend class QGrpcAsyncOperationBase;
  180. private:
  181. //!\private
  182. QGrpcStatus call(const QString &method, const QByteArray &arg, QByteArray &ret);
  183. //!\private
  184. QGrpcCallReplyShared call(const QString &method, const QByteArray &arg);
  185. //!\private
  186. QGrpcStreamShared stream(const QString &method, const QByteArray &arg, const QtProtobuf::StreamHandler &handler = {});
  187. /*!
  188. * \private
  189. * \brief Deserialization helper
  190. */
  191. template<typename R>
  192. QGrpcStatus tryDeserialize(R &ret, const QByteArray &retData) {
  193. QGrpcStatus status{QGrpcStatus::Ok};
  194. auto _serializer = serializer();
  195. if (_serializer != nullptr) {
  196. try {
  197. ret.deserialize(_serializer.get(), retData);
  198. } catch (std::invalid_argument &) {
  199. static const QLatin1String invalidArgumentErrorMessage("Response deserialization failed invalid field found");
  200. status = {QGrpcStatus::InvalidArgument, invalidArgumentErrorMessage};
  201. error(status);
  202. qProtoCritical() << invalidArgumentErrorMessage;
  203. } catch (std::out_of_range &) {
  204. static const QLatin1String outOfRangeErrorMessage("Invalid size of received buffer");
  205. status = {QGrpcStatus::OutOfRange, outOfRangeErrorMessage};
  206. error(status);
  207. qProtoCritical() << outOfRangeErrorMessage;
  208. } catch (...) {
  209. status = {QGrpcStatus::Internal, QLatin1String("Unknown exception caught during deserialization")};
  210. error(status);
  211. }
  212. } else {
  213. status = {QGrpcStatus::Unknown, QLatin1String("Deserializing failed. Serializer is not ready")};
  214. error(status);
  215. }
  216. return status;
  217. }
  218. template<typename R>
  219. QByteArray trySerialize(const R &arg, bool &ok) {
  220. ok = false;
  221. QGrpcStatus status{QGrpcStatus::Ok};
  222. auto _serializer = serializer();
  223. if (_serializer == nullptr) {
  224. error({QGrpcStatus::Unknown, QLatin1String("Serializing failed. Serializer is not ready")});
  225. return QByteArray();
  226. }
  227. ok = true;
  228. return arg.serialize(_serializer.get());
  229. }
  230. /*!
  231. * \private
  232. * \brief serializer provides assigned to client serializer
  233. * \return pointer to serializer. Serializer is owned by QtProtobuf::QProtobufSerializerRegistry.
  234. */
  235. std::shared_ptr<QAbstractProtobufSerializer> serializer() const;
  236. Q_DISABLE_COPY_MOVE(QAbstractGrpcClient)
  237. std::unique_ptr<QAbstractGrpcClientPrivate> dPtr;
  238. };
  239. }