Browse Source

Add client tutorial sample

- Add complete client tutorial with doxygen description
- Update README
- Fix few documentation issue
- Migrate to major.minor version for documentation indentification
Alexey Edelev 3 years ago
parent
commit
61e19c2c56

+ 3 - 0
README.md

@@ -35,6 +35,9 @@ QtProtobuf provides Qt-native support of Google protocol buffers. Generated code
 
 [QtProtobuf development](#qtprotobuf-development)
 
+## Tutorials
+
+[QtProtobuf Client Tutorial](https://semlanik.github.io/qtprotobuf/clienttutorial.html)
 
 # Linux Build
 ## Prerequesties

+ 2 - 2
doxygen/Doxyfile.in

@@ -38,7 +38,7 @@ PROJECT_NAME           = "QtProtobuf"
 # could be handy for archiving the generated documentation or if some version
 # control system is used.
 
-PROJECT_NUMBER         = @QT_PROTOBUF_VERSION@
+PROJECT_NUMBER         = @PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@
 
 # Using the PROJECT_BRIEF tag one can provide an optional one line description
 # for a project that appears at the top of each page and should give viewer a
@@ -790,7 +790,7 @@ WARN_LOGFILE           =
 # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
 # Note: If this tag is empty the current directory is searched.
 
-INPUT                  = @CMAKE_CURRENT_SOURCE_DIR@/src/
+INPUT                  = @CMAKE_CURRENT_SOURCE_DIR@/src @CMAKE_CURRENT_SOURCE_DIR@/examples/clienttutorial
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

+ 2 - 0
examples/CMakeLists.txt

@@ -7,3 +7,5 @@ configure_file(client_server_driver.sh.in addressbook_driver.sh @ONLY)
 add_subdirectory("simplechat")
 add_subdirectory("simplechatserver")
 configure_file(client_server_driver.sh.in simplechat_driver.sh @ONLY)
+
+add_subdirectory("clienttutorial")

+ 26 - 0
examples/clienttutorial/CMakeLists.txt

@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.5)
+
+project(clienttutorial LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+find_package(Qt5 COMPONENTS Core Quick REQUIRED)
+find_package(QtProtobufProject COMPONENTS QtProtobuf QtGrpc REQUIRED)
+
+add_executable(clienttutorial main.cpp echoclientengine.cpp qml.qrc)
+
+qtprotobuf_generate(TARGET clienttutorial PROTO_FILES ${CMAKE_CURRENT_SOURCE_DIR}/tutorial.proto QML TRUE)
+
+target_compile_definitions(clienttutorial
+  PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
+target_link_libraries(clienttutorial
+  PRIVATE Qt5::Core Qt5::Quick)
+target_link_libraries(clienttutorial PRIVATE QtProtobufProject::QtGrpc QtProtobufProject::QtProtobuf)

+ 45 - 0
examples/clienttutorial/echoclientengine.cpp

@@ -0,0 +1,45 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "echoclientengine.h"
+
+#include "tutorial_grpc.qpb.h"
+
+#include <QGrpcHttp2Channel>
+#include <QGrpcInsecureCredentials>
+
+using namespace qtprotobuf::tutorial;
+using namespace QtProtobuf;
+
+EchoClientEngine::EchoClientEngine(QObject *parent) : QObject(parent), m_client(new EchoServiceClient), m_response(new EchoResponse)
+{
+    m_client->attachChannel(std::shared_ptr<QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:65000"),
+                                                                                        QGrpcInsecureChannelCredentials()|QGrpcInsecureCallCredentials())));
+}
+
+void EchoClientEngine::request(qtprotobuf::tutorial::EchoRequest *req)
+{
+    m_client->Echo(*req, m_response.get());
+}

+ 52 - 0
examples/clienttutorial/echoclientengine.h

@@ -0,0 +1,52 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <memory>
+
+#include "tutorial.qpb.h"
+#include "tutorial_grpc.qpb.h"
+
+//! \private
+class EchoClientEngine : public QObject
+{
+    Q_OBJECT
+    Q_PROPERTY(qtprotobuf::tutorial::EchoResponse *response READ response)
+public:
+    explicit EchoClientEngine(QObject *parent = nullptr);
+
+    Q_INVOKABLE void request(qtprotobuf::tutorial::EchoRequest *req);
+
+    qtprotobuf::tutorial::EchoResponse *response() const
+    {
+        return m_response.get();
+    }
+
+private:
+    std::unique_ptr<qtprotobuf::tutorial::EchoServiceClient> m_client;
+    std::unique_ptr<qtprotobuf::tutorial::EchoResponse> m_response;
+};

+ 261 - 0
examples/clienttutorial/main.cpp

@@ -0,0 +1,261 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020 Alexey Edelev <semlanik@gmail.com>
+ *
+ * This file is part of QtProtobuf project https://git.semlanik.org/semlanik/qtprotobuf
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this
+ * software and associated documentation files (the "Software"), to deal in the Software
+ * without restriction, including without limitation the rights to use, copy, modify,
+ * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+ * to permit persons to whom the Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies
+ * or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*!
+ * \page clienttutorial QtProtobuf Client Tutorial
+ *
+ * This tutorial will show how to use QtProtobuf client with some existing server and specified communication protocol.
+ *
+ * \subsection clienttutorial_dependencies Add QtProtobuf package to CMake project
+ *
+ * After you successfully installed/built QtProtobuf (see <a href="https://github.com/semlanik/qtprotobuf">README.md</a>) with complete set of dependencies
+ * you may start creation either CMake or qmake project in QtCreator. Now we need to tell our project about QtProtobuf.
+ * Add following line in your CMakeLists.txt to add QtProtobuf as project dependency:
+ *
+ * \code
+ * find_package(QtProtobufProject COMPONENTS QtProtobuf QtGrpc REQUIRED)
+ * ...
+ * target_link_libraries(clienttutorial PRIVATE QtProtobufProject::QtGrpc QtProtobufProject::QtProtobuf)
+ * \endcode
+ * At this point you will have full access to QtProtobuf libraries and macroses.
+ *
+ * \subsection clienttutorial_generation Generate code
+ *
+ * Let's imagine you have echo sever with following protocol definition saved to tutorial.proto:
+ * \code
+ * syntax="proto3";
+ *
+ * package qtprotobuf.tutorial;
+ *
+ * message EchoRequest {
+ *   string message = 1;
+ * }
+ *
+ * message EchoResponse {
+ *   string message = 1;
+ * }
+ *
+ * service EchoService {
+ *   rpc Echo(EchoRequest) returns (EchoResponse);
+ * }
+ * \endcode
+ *
+ * Integration with your Qt application starts at point when you add protobuf and gRPC generation for .proto filed. Add \ref cmake_qtprotobuf_generate function to your CMakeLists.txt as following:
+ *
+ * \code
+ * ...
+ * add_executable(clienttutorial main.cpp qml.qrc) #Add QtProtobuf generator rules after CMake target defined
+ *
+ * qtprotobuf_generate(TARGET clienttutorial QML TRUE PROTO_FILES tutorial.proto)
+ * ...
+ * \endcode
+ *
+ * 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.
+ *
+ * To use them in C++ you need to include tutorial.qpb.h to get all messages defined in tutorial.proto accessible in C++ context:
+ * \code
+ * #include "tutorial.qpb.h"
+ * \endcode
+ *
+ * To access messages from QML code add appropriate import of protobuf package:
+ * \code
+ * import qtprotobuf.tutorial 1.0
+ * \endcode
+ *
+ * \note All QML imports by default generated with version 1.0.
+ *
+ * \subsection clienttutorial_cpp Implement client-side business logic
+ *
+ * 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
+ * logic and encapsulates QtGrpc client for communication purpose:
+ *
+ * \code
+ * #include "tutorial.qpb.h"
+ * #include "tutorial_grpc.qpb.h"
+ *
+ * class EchoClientEngine : public QObject
+ * {
+ *     Q_OBJECT
+ *     Q_PROPERTY(qtprotobuf::tutorial::EchoResponse *response READ response CONSTANT)
+ * public:
+ *     explicit EchoClientEngine(QObject *parent = nullptr);
+ *
+ *     Q_INVOKABLE void request(qtprotobuf::tutorial::EchoRequest *req);
+ *
+ *     qtprotobuf::tutorial::EchoResponse *response() const
+ *     {
+ *         return m_response.get();
+ *     }
+ *
+ * private:
+ *     std::unique_ptr<qtprotobuf::tutorial::EchoServiceClient> m_client;
+ *     std::unique_ptr<qtprotobuf::tutorial::EchoResponse> m_response;
+ * };
+ *
+ * \endcode
+ *
+ * Here we specify our interface to communicate to QML part.
+ *
+ *  - `response` property provides recent response from server. We made it CONSTANT because pointer it selves don't need any modifications.
+ *  - `request` invocable function expects pointer to EchoRequest, that will be utilized to call Echo remote procedure.
+ *  - `m_client` keeps pointer to generated EchoServerClient.
+ *
+ * \note This implementation is one of possible usage of client-side grpc API.
+ *
+ *
+ * Definition part of EchoClientEngine includes two main points:
+ *
+ *  1. Initialization of gRPC channel and attaching our EchoServiceClient to it.
+ *  2. Implement request call
+ *
+ * For gRPC channel we will use Http2 protocol without credentials. Simply create new channel with \ref QtProtobuf::QGrpcInsecureChannelCredentials "QGrpcInsecureChannelCredentials" / \ref QtProtobuf::QGrpcInsecureCallCredentials "QGrpcInsecureCallCredentials"
+ * and pass it as parameter to \ref QtProtobuf::QAbstractGrpcClient::attachChannel "attachChannel" call right in constructor:
+ *
+ * \code
+ * EchoClientEngine::EchoClientEngine(QObject *parent) : QObject(parent), m_client(new EchoServiceClient), m_response(new EchoResponse)
+ * {
+ *     m_client->attachChannel(std::shared_ptr<QAbstractGrpcChannel>(new QGrpcHttp2Channel(QUrl("http://localhost:65000"),
+ *                                                                                         QGrpcInsecureChannelCredentials()|QGrpcInsecureCallCredentials())));
+ * }
+ * \endcode
+ *
+ * Request function is very simple only utilizes generated Echo method, one of possible implementations as below:
+ *
+ * \code
+ * void EchoClientEngine::request(qtprotobuf::tutorial::EchoRequest *req)
+ * {
+ *     m_client->Echo(*req, m_response.get());
+ * }
+ * \endcode
+ *
+ * Let's stop at parameters, that we pass to `Echo` method.
+ * First parameter is request. Methods generated by QtProtobuf pass only reference to method argument, that's why we derefence it and provide as value.
+ * Second parameter is pointer to response that will be modified when server response will be received. Important point here is that responce value passed
+ * 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
+ * perspective QtGrpc provides to you async response safety.
+ *
+ * We complete our model implementation, and it's time to make it visible in QML context. Simply do it in main:
+ *
+ * \code
+ * ...
+ * #include <QtProtobufTypes>
+ *
+ * #include "echoclientengine.h
+ * ...
+ * int main(int argc, char *argv[])
+ * {
+ * ... //Before load QML
+ *     QtProtobuf::qRegisterProtobufTypes();
+ *     EchoClientEngine echoEngine;
+ *     engine.rootContext()->setContextProperty("echoEngine", &echoEngine);
+ * ...
+ * }
+ * \endcode
+ *
+ * It's important to call `qRegisterProtobufTypes` to register all QtProtobuf types including generated user types.
+ * In our case it's types generated from tutorial.proto.
+ *
+ * \subsection clienttutorial_qml Implement client-side UI and interact to business logic
+ * Let's move to presentation part. Of course it's better to use QML when we describe our project UI:
+ *
+ * \code
+ * import QtQuick 2.12
+ * import QtQuick.Window 2.12
+ *
+ * import QtQuick.Controls 2.12
+ *
+ * import qtprotobuf.tutorial 1.0
+ *
+ * Window {
+ * ...
+ *     EchoRequest {
+ *         id: request
+ *         message: messageInput.text
+ *     }
+ * ...
+ *     TextField {
+ *         id: messageInput
+ *         anchors.verticalCenter: parent.verticalCenter
+ *         width: 400
+ *         onAccepted: {
+ *             echoEngine.request(request);
+ *             text = ""
+ *         }
+ *     }
+ * ...
+ *     Text {
+ *         anchors.verticalCenter: parent.verticalCenter
+ *         text: echoEngine.response.message
+ *     }
+ * }
+ * \endcode
+ *
+ * After cut off all fancy QML stuff, only QtProtobuf related things are left. Its important to do not forget import of our freshly generated
+ * 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
+ * by adding `EchoRequest` object and bind `message` property to messageInput text.
+ * \note It's not mandatory and in some cases may cause often field update, but in out case it's just tutorial ¯\\_(ツ)_/¯.
+ *
+ * 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
+ * and written to echoEngine.response.message property. We bound it to appropriate text field, that will be updated "immediately".
+ * *
+ * 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
+ * `examples/tutorial/tutorialserver` folder and enjoy echo functionality of your QtProtobuf EchoService client UI.
+ *
+ * \subsection clienttutorial_feedback Feedback
+ * Please provide feedback using this <a href="https://github.com/semlanik/qtprotobuf/issues/116">issue</a> in project bug tracker.
+ *
+ * \subsection clienttutorial_references References
+ * <a href="https://github.com/semlanik/qtprotobuf/tree/master/examples/clienttutorial">Complete tutorial code</a>
+ */
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+#include <QQmlContext>
+
+#include <QtProtobufTypes>
+
+#include "echoclientengine.h"
+
+int main(int argc, char *argv[])
+{ 
+    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+    QGuiApplication app(argc, argv);
+
+    QQmlApplicationEngine engine;
+    const QUrl url(QStringLiteral("qrc:/main.qml"));
+    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
+                     &app, [url](QObject *obj, const QUrl &objUrl) {
+        if (!obj && url == objUrl)
+            QCoreApplication::exit(-1);
+    }, Qt::QueuedConnection);
+
+    QtProtobuf::qRegisterProtobufTypes();
+    EchoClientEngine echoEngine;
+    engine.rootContext()->setContextProperty("echoEngine", &echoEngine);
+
+    engine.load(url);
+
+    return app.exec();
+}

+ 51 - 0
examples/clienttutorial/main.qml

@@ -0,0 +1,51 @@
+import QtQuick 2.12
+import QtQuick.Window 2.12
+
+import QtQuick.Controls 2.12
+
+import qtprotobuf.tutorial 1.0
+
+Window {
+    visible: true
+    width: 640
+    height: 480
+    title: qsTr("QtProtobuf Tutorial")
+
+    EchoRequest {
+        id: request
+        message: messageInput.text
+    }
+
+    Column {
+        spacing: 20
+        Row {
+            spacing: 20
+            Text {
+                anchors.verticalCenter: parent.verticalCenter
+                text: "Enter request message:"
+            }
+
+            TextField {
+                id: messageInput
+                anchors.verticalCenter: parent.verticalCenter
+                width: 400
+                onAccepted: {
+                    echoEngine.request(request);
+                    text = ""
+                }
+            }
+        }
+        Row {
+            spacing: 20
+            Text {
+                anchors.verticalCenter: parent.verticalCenter
+                text: "Server respond:"
+            }
+
+            Text {
+                anchors.verticalCenter: parent.verticalCenter
+                text: echoEngine.response.message
+            }
+        }
+    }
+}

+ 5 - 0
examples/clienttutorial/qml.qrc

@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/">
+        <file>main.qml</file>
+    </qresource>
+</RCC>

+ 15 - 0
examples/clienttutorial/tutorial.proto

@@ -0,0 +1,15 @@
+syntax="proto3";
+
+package qtprotobuf.tutorial;
+
+message EchoRequest {
+  string message = 1;
+}
+
+message EchoResponse {
+  string message = 1;
+}
+
+service EchoService {
+  rpc Echo(EchoRequest) returns (EchoResponse);
+} 

+ 14 - 0
examples/clienttutorial/tutorialserver/build_and_run.sh

@@ -0,0 +1,14 @@
+export PATH=$PATH:$PWD/bin
+export GOBIN=$PWD/bin
+export RPC_PATH=$PWD/../
+export GO111MODULE=on
+
+go get -u github.com/golang/protobuf/protoc-gen-go@v1.3.3
+
+mkdir -p qtprotobuf_tutorial
+rm -f qtprotobuf_tutorial/*.pb.go
+protoc -I../ --go_out=plugins=grpc:qtprotobuf_tutorial ../tutorial.proto
+
+go build
+
+./tutorialserver

+ 27 - 0
examples/clienttutorial/tutorialserver/main.go

@@ -0,0 +1,27 @@
+package main
+
+import (
+	"context"
+	"log"
+	"net"
+
+	"tutorialserver/qtprotobuf_tutorial"
+
+	grpc "google.golang.org/grpc"
+)
+
+type echoServer struct{}
+
+func (s *echoServer) Echo(ctx context.Context, req *qtprotobuf_tutorial.EchoRequest) (*qtprotobuf_tutorial.EchoResponse, error) {
+	return &qtprotobuf_tutorial.EchoResponse{Message: req.Message}, nil
+}
+
+func main() {
+	lis, err := net.Listen("tcp", ":65000")
+	if err != nil {
+		log.Fatalf("failed to listen: %v", err)
+	}
+	grpcServer := grpc.NewServer()
+	qtprotobuf_tutorial.RegisterEchoServiceServer(grpcServer, &echoServer{})
+	grpcServer.Serve(lis)
+}

+ 4 - 0
src/generator/main.cpp

@@ -43,6 +43,10 @@
  *  - \ref QtProtobuf
  *  - \ref QtGrpc
  *  - \ref QtProtobufWellKnownTypes
+ *
+ * \subsection gettingstarted Getting Started with QtProtobuf
+ *
+ * \ref clienttutorial
  */
 using namespace ::QtProtobuf::generator;
 int main(int argc, char *argv[])

+ 1 - 1
src/protobuf/qtprotobuftypes.h

@@ -233,7 +233,7 @@ using DoubleList = QList<double>;
 
 /*!
  * \ingroup QtProtobuf
- * \brief registerTypes
+ * \brief qRegisterProtobufTypes
  * This method should be called in all applications that supposed to use QtProtobuf
  */
 extern Q_PROTOBUF_EXPORT void qRegisterProtobufTypes();