main.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2020 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. /*!
  26. * \page clienttutorial QtProtobuf Client Tutorial
  27. *
  28. * This tutorial will show how to use QtProtobuf client with some existing server and specified communication protocol.
  29. *
  30. * \subsection clienttutorial_dependencies Add QtProtobuf package to CMake project
  31. *
  32. * After you successfully installed/built QtProtobuf (see <a href="https://github.com/semlanik/qtprotobuf">README.md</a>) with complete set of dependencies
  33. * you may start creation either CMake or qmake project in QtCreator. Now we need to tell our project about QtProtobuf.
  34. * Add following line in your CMakeLists.txt to add QtProtobuf as project dependency:
  35. *
  36. * \code
  37. * find_package(QtProtobuf COMPONENTS ProtobufGenerator Protobuf Grpc REQUIRED)
  38. * ...
  39. * target_link_libraries(clienttutorial PRIVATE QtProtobuf::Grpc QtProtobuf::Protobuf)
  40. * \endcode
  41. * At this point you will have full access to QtProtobuf libraries and macroses.
  42. *
  43. * \subsection clienttutorial_generation Generate code
  44. *
  45. * Let's imagine you have echo sever with following protocol definition saved to tutorial.proto:
  46. * \code
  47. * syntax="proto3";
  48. *
  49. * package qtprotobuf.tutorial;
  50. *
  51. * message EchoRequest {
  52. * string message = 1;
  53. * }
  54. *
  55. * message EchoResponse {
  56. * string message = 1;
  57. * }
  58. *
  59. * service EchoService {
  60. * rpc Echo(EchoRequest) returns (EchoResponse);
  61. * }
  62. * \endcode
  63. *
  64. * Integration with your Qt application starts at point when you add protobuf and gRPC generation for .proto filed. Add \ref cmake_qt6_protobuf_generate function to your CMakeLists.txt as following:
  65. *
  66. * \code
  67. * ...
  68. * add_executable(clienttutorial main.cpp qml.qrc) #Add QtProtobuf generator rules after CMake target defined
  69. *
  70. * qt6_protobuf_generate(TARGET clienttutorial QML TRUE PROTO_FILES tutorial.proto)
  71. * ...
  72. * \endcode
  73. *
  74. * We specified `QML TRUE` to have generated classes visibled in QML. At this point we have direct access to all classes specified in tutorial.proto from C++ and QML contexts.
  75. *
  76. * To use them in C++ you need to include tutorial.qpb.h to get all messages defined in tutorial.proto accessible in C++ context:
  77. * \code
  78. * #include "tutorial.qpb.h"
  79. * \endcode
  80. *
  81. * To access messages from QML code add appropriate import of protobuf package:
  82. * \code
  83. * import qtprotobuf.tutorial 1.0
  84. * \endcode
  85. *
  86. * \note All QML imports by default generated with version 1.0.
  87. *
  88. * \subsection clienttutorial_cpp Implement client-side business logic
  89. *
  90. * Now lets go deeper and try to interact with server using our gRPC API. For server communication we will have C++ model, that implements business
  91. * logic and encapsulates QtGrpc client for communication purpose:
  92. *
  93. * \code
  94. * #include "tutorial.qpb.h"
  95. * #include "tutorial_grpc.qpb.h"
  96. *
  97. * class EchoClientEngine : public QObject
  98. * {
  99. * Q_OBJECT
  100. * Q_PROPERTY(qtprotobuf::tutorial::EchoResponse *response READ response CONSTANT)
  101. * public:
  102. * explicit EchoClientEngine(QObject *parent = nullptr);
  103. *
  104. * Q_INVOKABLE void request(qtprotobuf::tutorial::EchoRequest *req);
  105. *
  106. * qtprotobuf::tutorial::EchoResponse *response() const
  107. * {
  108. * return m_response.get();
  109. * }
  110. *
  111. * private:
  112. * std::unique_ptr<qtprotobuf::tutorial::EchoServiceClient> m_client;
  113. * std::unique_ptr<qtprotobuf::tutorial::EchoResponse> m_response;
  114. * };
  115. *
  116. * \endcode
  117. *
  118. * Here we specify our interface to communicate to QML part.
  119. *
  120. * - `response` property provides recent response from server. We made it CONSTANT because pointer it selves don't need any modifications.
  121. * - `request` invocable function expects pointer to EchoRequest, that will be utilized to call Echo remote procedure.
  122. * - `m_client` keeps pointer to generated EchoServerClient.
  123. *
  124. * \note This implementation is one of possible usage of client-side grpc API.
  125. *
  126. *
  127. * Definition part of EchoClientEngine includes two main points:
  128. *
  129. * 1. Initialization of gRPC channel and attaching our EchoServiceClient to it.
  130. * 2. Implement request call
  131. *
  132. * For gRPC channel we will use Http2 protocol without credentials. Simply create new channel with \ref QtProtobuf::QGrpcInsecureChannelCredentials "QGrpcInsecureChannelCredentials" / \ref QtProtobuf::QGrpcInsecureCallCredentials "QGrpcInsecureCallCredentials"
  133. * and pass it as parameter to \ref QtProtobuf::QAbstractGrpcClient::attachChannel "attachChannel" call right in constructor:
  134. *
  135. * \code
  136. * EchoClientEngine::EchoClientEngine(QObject *parent) : QObject(parent), m_client(new EchoServiceClient), m_response(new EchoResponse)
  137. * {
  138. * m_client->attachChannel(std::shared_ptr<QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:65000"),
  139. * QGrpcInsecureChannelCredentials()|QGrpcInsecureCallCredentials())));
  140. * }
  141. * \endcode
  142. *
  143. * Request function is very simple only utilizes generated Echo method, one of possible implementations as below:
  144. *
  145. * \code
  146. * void EchoClientEngine::request(qtprotobuf::tutorial::EchoRequest *req)
  147. * {
  148. * m_client->Echo(*req, m_response.get());
  149. * }
  150. * \endcode
  151. *
  152. * Let's stop at parameters, that we pass to `Echo` method.
  153. * First parameter is request. Methods generated by QtProtobuf pass only reference to method argument, that's why we derefence it and provide as value.
  154. * Second parameter is pointer to response that will be modified when server response will be received. Important point here is that responce value passed
  155. * as QPointer and its time of life is controlled by grpc client. In case if pointer was removed client will ignore server response silently. So from this
  156. * perspective QtGrpc provides to you async response safety.
  157. *
  158. * We complete our model implementation, and it's time to make it visible in QML context. Simply do it in main:
  159. *
  160. * \code
  161. * ...
  162. * #include <QtProtobufTypes>
  163. *
  164. * #include "echoclientengine.h
  165. * ...
  166. * int main(int argc, char *argv[])
  167. * {
  168. * ... //Before load QML
  169. * qRegisterProtobufTypes();
  170. * qmlRegisterSingletonType<EchoClientEngine>("qtprotobuf.tutorial", 1, 0, "EchoClientEngine", [](QQmlEngine *engine, QJSEngine *){
  171. * static EchoClientEngine echoEngine;
  172. * engine->setObjectOwnership(&echoEngine, QQmlEngine::CppOwnership);
  173. * return &echoEngine;
  174. * });
  175. * ...
  176. * }
  177. * \endcode
  178. *
  179. * It's important to call `qRegisterProtobufTypes` to register all QtProtobuf types including generated user types.
  180. * In our case it's types generated from tutorial.proto.
  181. *
  182. * \subsection clienttutorial_qml Implement client-side UI and interact to business logic
  183. * Let's move to presentation part. Of course it's better to use QML when we describe our project UI:
  184. *
  185. * \code
  186. * import QtQuick 2.12
  187. * import QtQuick.Window 2.12
  188. *
  189. * import QtQuick.Controls 2.12
  190. *
  191. * import qtprotobuf.tutorial 1.0
  192. *
  193. * Window {
  194. * ...
  195. * EchoRequest {
  196. * id: request
  197. * message: messageInput.text
  198. * }
  199. * ...
  200. * TextField {
  201. * id: messageInput
  202. * anchors.verticalCenter: parent.verticalCenter
  203. * width: 400
  204. * onAccepted: {
  205. * echoEngine.request(request);
  206. * text = ""
  207. * }
  208. * }
  209. * ...
  210. * Text {
  211. * anchors.verticalCenter: parent.verticalCenter
  212. * text: echoEngine.response.message
  213. * }
  214. * }
  215. * \endcode
  216. *
  217. * After cut off all fancy QML stuff, only QtProtobuf related things are left. Its important to do not forget import of our freshly generated
  218. * qtprotobuf.tutorial package. At this point we have access to any message inside qtprotobuf.tutorial package. So we create view model right in QML code
  219. * by adding `EchoRequest` object and bind `message` property to messageInput text.
  220. * \note It's not mandatory and in some cases may cause often field update, but in out case it's just tutorial ¯\\_(ツ)_/¯.
  221. *
  222. * Only thing left is to call our request method. We pass request that already contains text, that we would like to send to server. Result will be received
  223. * and written to echoEngine.response.message property. We bound it to appropriate text field, that will be updated "immediately".
  224. * *
  225. * It's time for testing! For tests you may use test server from projects `examples/tutorial/tutorialserver` folder. Run "build_and_run.sh" script in
  226. * `examples/tutorial/tutorialserver` folder and enjoy echo functionality of your QtProtobuf EchoService client UI.
  227. *
  228. * \subsection clienttutorial_feedback Feedback
  229. * Please provide feedback using this <a href="https://github.com/semlanik/qtprotobuf/issues/116">issue</a> in project bug tracker.
  230. *
  231. * \subsection clienttutorial_references References
  232. * <a href="https://github.com/semlanik/qtprotobuf/tree/master/examples/clienttutorial">Complete tutorial code</a>
  233. */
  234. #include <QGuiApplication>
  235. #include <QQmlApplicationEngine>
  236. #include <QQmlContext>
  237. #include <QtProtobufTypes>
  238. #include "echoclientengine.h"
  239. int main(int argc, char *argv[])
  240. {
  241. QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
  242. QGuiApplication app(argc, argv);
  243. QQmlApplicationEngine engine;
  244. const QUrl url(QStringLiteral("qrc:/main.qml"));
  245. QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
  246. &app, [url](QObject *obj, const QUrl &objUrl) {
  247. if (!obj && url == objUrl)
  248. QCoreApplication::exit(-1);
  249. }, Qt::QueuedConnection);
  250. qRegisterProtobufTypes();
  251. qmlRegisterSingletonType<EchoClientEngine>("qtprotobuf.tutorial", 1, 0, "EchoClientEngine", [](QQmlEngine *engine, QJSEngine *) -> QObject *{
  252. static EchoClientEngine echoEngine;
  253. engine->setObjectOwnership(&echoEngine, QQmlEngine::CppOwnership);
  254. return &echoEngine;
  255. });
  256. engine.load(url);
  257. return app.exec();
  258. }