mirror of https://github.com/qt/qtgrpc.git
The example of Qt gRPC-Client part with qml based UI
Pick-to: 6.5 Task-number: QTBUG-109598 Change-Id: I670f779fb9a85d02ad69a54dc6adaa50f52a7a71 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
This commit is contained in:
parent
613b3de08e
commit
681d104f0d
|
@ -12,7 +12,8 @@ project(QtGrpc
|
|||
)
|
||||
|
||||
find_package(Qt6 ${PROJECT_VERSION} CONFIG REQUIRED COMPONENTS BuildInternals Core)
|
||||
find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Network Gui Widgets)
|
||||
find_package(Qt6 ${PROJECT_VERSION} CONFIG OPTIONAL_COMPONENTS Network Gui Widgets Quick
|
||||
QuickControls2)
|
||||
|
||||
# Try to find Qt6::qtprotobufgen and Qt6::qtgrpcgen targets from host tools
|
||||
# when cross-compiling.
|
||||
|
|
|
@ -2,3 +2,6 @@ dependencies:
|
|||
../qtbase:
|
||||
ref: 360f69b74b5e28ea1cfb0ed1ead624d0323dfe09
|
||||
required: true
|
||||
../qtdeclarative:
|
||||
ref: 0737bb84e2bcf6acafe8e9892179234fd0926bdf
|
||||
required: false
|
||||
|
|
|
@ -5,4 +5,7 @@ qt_examples_build_begin(EXTERNAL_BUILD)
|
|||
|
||||
add_subdirectory(protobuf)
|
||||
|
||||
if(TARGET Qt6::Grpc)
|
||||
add_subdirectory(grpc)
|
||||
endif()
|
||||
qt_examples_build_end()
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# Copyright (C) 2023 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
if(TARGET Qt6::Quick
|
||||
AND TARGET Qt6::QuickControls2
|
||||
AND TARGET Qt6::qtprotobufgen
|
||||
AND TARGET Qt6::qtgrpcgen)
|
||||
qt_internal_add_example(magic8ball)
|
||||
endif()
|
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1,55 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
/*!
|
||||
\example magic8ball
|
||||
\meta category {Networking}
|
||||
\ingroup qtgrpc-examples
|
||||
\title Magic 8 Ball
|
||||
|
||||
\brief Creating a HTTP2 connection between a Qt GRPC client and
|
||||
a C++ gRPC server.
|
||||
|
||||
Magic 8 ball shows an answer it receives from a server:
|
||||
\image answer.webp
|
||||
|
||||
Magic 8 ball has the following components:
|
||||
\list
|
||||
\li \c magic8ball Qt GRPC client application that includes
|
||||
the \l {qt_add_protobuf} and \l {qt_add_grpc}
|
||||
CMake functions for message and service Qt code generation.
|
||||
\li \c SimpleGrpcServer application that calls C++ gRPC plugin
|
||||
for generating server code and implementing simple server
|
||||
logic.
|
||||
\endlist
|
||||
|
||||
Both components use generated messages from the protobuf schema
|
||||
described in the \c {exampleservice.proto} file:
|
||||
\quotefromfile magic8ball/proto/exampleservice.proto
|
||||
\skipto syntax = "proto3";
|
||||
\printuntil
|
||||
|
||||
The client application binds on the \c localhost with port
|
||||
\c 50051:
|
||||
\quotefromfile magic8ball/clientservice.cpp
|
||||
\skipto new QGrpcHttp2Channel
|
||||
\printuntil m_client->attachChannel(channel);
|
||||
|
||||
And sends a request to the server part:
|
||||
\quotefromfile magic8ball/clientservice.cpp
|
||||
\skipto void ClientService::sendRequest()
|
||||
\printuntil }
|
||||
|
||||
Click the \uicontrol {Ask question} button to send
|
||||
the request to the SimpleGrpcServer application.
|
||||
|
||||
The SimpleGrpcServer application chooses a random answer from
|
||||
the list of answers and sends the data to the client's port.
|
||||
\quotefromfile magic8ball/grpc_server_example/serverrunner.cpp
|
||||
\skipto Status ExampleServiceServiceImpl::answerMethod
|
||||
\printuntil }
|
||||
|
||||
After receiving a response the client application shows the answer.
|
||||
|
||||
*/
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal closed()
|
||||
|
||||
property alias closingAnimation: closeAnimator
|
||||
property alias openingAnimation: openAnimator
|
||||
property alias animationText: result.text
|
||||
|
||||
MagicText {
|
||||
id: result
|
||||
anchors.centerIn: parent
|
||||
|
||||
font.pointSize: text.length > 12 ? 14 : 16
|
||||
color: "#2E53B6"
|
||||
|
||||
ScaleAnimator on scale {
|
||||
id: openAnimator
|
||||
target: result
|
||||
from: 0
|
||||
to: 1
|
||||
duration: 2000
|
||||
running: false
|
||||
}
|
||||
|
||||
ScaleAnimator on scale {
|
||||
id: closeAnimator
|
||||
target: result
|
||||
from: 1
|
||||
to: 0
|
||||
duration: 2000
|
||||
running: false
|
||||
onStopped: root.closed()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(Magic8Ball LANGUAGES CXX)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/grpc/magic8ball")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Protobuf Grpc Quick)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
add_subdirectory(grpc_server_example)
|
||||
|
||||
qt_add_executable(magic8ball
|
||||
main.cpp
|
||||
)
|
||||
|
||||
qt_add_protobuf(magic8ball
|
||||
PROTO_FILES
|
||||
proto/exampleservice.proto
|
||||
)
|
||||
|
||||
qt_add_grpc(magic8ball CLIENT
|
||||
PROTO_FILES
|
||||
proto/exampleservice.proto
|
||||
)
|
||||
|
||||
set_target_properties(magic8ball PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
MACOSX_BUNDLE TRUE
|
||||
)
|
||||
|
||||
qt_add_qml_module(magic8ball
|
||||
URI qtgrpc.examples.magic8ball
|
||||
VERSION 1.0
|
||||
AUTO_RESOURCE_PREFIX
|
||||
SOURCES
|
||||
clientservice.h
|
||||
clientservice.cpp
|
||||
QML_FILES
|
||||
"WaitingAnimation.qml"
|
||||
"AnimatedAnswer.qml"
|
||||
"MagicText.qml"
|
||||
"ProgressDot.qml"
|
||||
"Main.qml"
|
||||
)
|
||||
|
||||
target_link_libraries(magic8ball PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Quick
|
||||
Qt6::Protobuf
|
||||
Qt6::Grpc
|
||||
)
|
||||
|
||||
install(TARGETS magic8ball
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.family: "Helvetica"
|
||||
font.pointSize: 16
|
||||
color: "#264BAF"
|
||||
style: Text.Sunken
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Shapes
|
||||
|
||||
import qtgrpc.examples.magic8ball
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
width: 665
|
||||
height: width
|
||||
|
||||
minimumWidth: width
|
||||
minimumHeight: height
|
||||
|
||||
visible: true
|
||||
title: qsTr("Magic-8-ball Qt GRPC Example")
|
||||
|
||||
property string textAnswer: ""
|
||||
property string textError: ""
|
||||
|
||||
MagicText {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
width: parent.width * 0.9
|
||||
height: parent.height/3
|
||||
|
||||
color: "black"
|
||||
|
||||
text: qsTr("For fortune-telling and seeking advice ask the ball"
|
||||
+ " a yes-no question and press the button.")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: magic8ball
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 433
|
||||
height: width
|
||||
|
||||
color: "#000000"
|
||||
radius: 300
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: "#4b4b4b" }
|
||||
GradientStop { position: 0.33; color: "#212121" }
|
||||
GradientStop { position: 1.0; color: "#000000" }
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
||||
width: 244
|
||||
height: width
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.horizontalCenterOffset: 6
|
||||
radius: 300
|
||||
|
||||
color: "#bababa"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: magic8ballCenter
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 244
|
||||
height: width
|
||||
|
||||
color: "black"
|
||||
border.width: 0.5
|
||||
border.color: "#bababa"
|
||||
radius: 300
|
||||
|
||||
Shape {
|
||||
anchors.centerIn: parent
|
||||
width: 200
|
||||
height: 200
|
||||
ShapePath {
|
||||
strokeWidth: 4
|
||||
strokeColor: "#213f94"
|
||||
capStyle: ShapePath.RoundCap
|
||||
|
||||
fillGradient: RadialGradient {
|
||||
centerX: 100
|
||||
centerY: 100
|
||||
focalX: centerX
|
||||
focalY: centerY
|
||||
centerRadius: 50
|
||||
focalRadius: 0
|
||||
|
||||
GradientStop { position: 0; color: "#1C2F60" }
|
||||
GradientStop { position: 0.5; color: "#000547" }
|
||||
GradientStop { position: 1; color: "#000324" }
|
||||
}
|
||||
|
||||
startX: 10
|
||||
startY: 40
|
||||
|
||||
PathLine { x: 100.5; y: 190 }
|
||||
PathLine { x: 188; y: 40 }
|
||||
PathLine { x: 10; y: 40 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WaitingAnimation {
|
||||
id: waitingAnimation
|
||||
anchors.centerIn: parent
|
||||
|
||||
visible: false
|
||||
}
|
||||
|
||||
AnimatedAnswer {
|
||||
id: answer
|
||||
|
||||
anchors.centerIn: parent
|
||||
visible: false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ClientService
|
||||
function onMessageRecieved(value) {
|
||||
root.textAnswer = value
|
||||
}
|
||||
|
||||
function onErrorRecieved(value) {
|
||||
root.textError = value
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 30
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
width: 200
|
||||
height: 50
|
||||
radius: 10
|
||||
|
||||
color: handler.pressed ? "#a5a5a5" : "#bebebe"
|
||||
|
||||
MagicText {
|
||||
id: btnText
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: qsTr("Ask question")
|
||||
color: "black"
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
id: handler
|
||||
|
||||
onTapped: animationTimeout.start()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: answer.closingAnimation
|
||||
function onStopped() {
|
||||
answer.animationText = ""
|
||||
answer.visible = false
|
||||
waitingAnimation.visible = true
|
||||
waitingAnimation.runAnimation = true
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: waitingAnimation
|
||||
function onRunAnimationChanged() {
|
||||
if (!waitingAnimation.runAnimation) {
|
||||
answer.visible = true
|
||||
answer.openingAnimation.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: animationTimeout
|
||||
|
||||
interval: 3000
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: ClientService.setMessage()
|
||||
|
||||
onRunningChanged: {
|
||||
if (running) {
|
||||
answer.closingAnimation.start()
|
||||
ClientService.sendRequest()
|
||||
} else {
|
||||
waitingAnimation.runAnimation = false
|
||||
waitingAnimation.visible = false
|
||||
answer.animationText = root.textError === "" ? root.textAnswer : root.textError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer: MagicText {
|
||||
text: root.textError === "" ? "" : "Please, start server: ../magic8ball/SimpleGrpcServer"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
width: 11
|
||||
height: width
|
||||
|
||||
color: "#264BAF"
|
||||
radius: 100
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool runAnimation: false
|
||||
|
||||
anchors.centerIn: parent
|
||||
width: 100
|
||||
height: width
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Row {
|
||||
id: scene
|
||||
anchors.centerIn: parent
|
||||
spacing: 12
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
ProgressDot {}
|
||||
}
|
||||
}
|
||||
|
||||
ScaleAnimator on scale {
|
||||
id: openning
|
||||
target: root
|
||||
from: 0.3
|
||||
to: 1
|
||||
duration: 1000
|
||||
running: runAnimation
|
||||
onStopped: closing.start()
|
||||
easing.amplitude: 6.0
|
||||
easing.period: 2.5
|
||||
}
|
||||
|
||||
ScaleAnimator on scale {
|
||||
id: closing
|
||||
target: root
|
||||
from: 1
|
||||
to: 0.3
|
||||
duration: 1000
|
||||
running: false
|
||||
onStopped: {
|
||||
if (runAnimation)
|
||||
openning.start()
|
||||
}
|
||||
easing.amplitude: 6.0
|
||||
easing.period: 2.5
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "clientservice.h"
|
||||
|
||||
#include <QGrpcHttp2Channel>
|
||||
#include <QGrpcInsecureChannelCredentials>
|
||||
|
||||
using namespace qtgrpc::examples;
|
||||
|
||||
ClientService::ClientService(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_client(new ExampleService::Client),
|
||||
m_response(new AnswerResponse)
|
||||
{
|
||||
connect(m_client.get(), &qtgrpc::examples::ExampleService::Client::errorOccurred,
|
||||
this, &ClientService::errorOccurred);
|
||||
|
||||
auto channel = std::shared_ptr<QAbstractGrpcChannel>(new QGrpcHttp2Channel(
|
||||
QUrl("http://localhost:50051",
|
||||
QUrl::StrictMode),
|
||||
QGrpcInsecureChannelCredentials()
|
||||
| QGrpcInsecureCallCredentials()));
|
||||
|
||||
m_client->attachChannel(channel);
|
||||
}
|
||||
|
||||
void ClientService::errorOccurred()
|
||||
{
|
||||
qWarning() << "Connection error occurred. Have you started server part:"
|
||||
" ../magic8ball/SimpleGrpcServer?";
|
||||
|
||||
emit errorRecieved("No connection\nto\nserver");
|
||||
}
|
||||
|
||||
void ClientService::sendRequest()
|
||||
{
|
||||
// clean error on UI before new request
|
||||
emit errorRecieved("");
|
||||
|
||||
qtgrpc::examples::AnswerRequest request;
|
||||
request.setMessage("sleep");
|
||||
|
||||
m_client->answerMethod(request, m_response.get());
|
||||
}
|
||||
|
||||
void ClientService::setMessage()
|
||||
{
|
||||
emit messageRecieved(m_response.get()->message());
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#ifndef CLIENT_SERVICE_H
|
||||
#define CLIENT_SERVICE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <qqmlregistration.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "exampleservice.qpb.h"
|
||||
#include "exampleservice_client.grpc.qpb.h"
|
||||
|
||||
class ClientService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
public:
|
||||
explicit ClientService(QObject *parent = nullptr);
|
||||
Q_INVOKABLE void sendRequest();
|
||||
Q_INVOKABLE void setMessage();
|
||||
void errorOccurred();
|
||||
|
||||
signals:
|
||||
void messageRecieved(const QString &value);
|
||||
void errorRecieved(const QString &value);
|
||||
|
||||
private:
|
||||
std::unique_ptr<qtgrpc::examples::ExampleService::Client> m_client;
|
||||
std::unique_ptr<qtgrpc::examples::AnswerResponse> m_response;
|
||||
};
|
||||
|
||||
#endif // CLIENT_SERVICE_H
|
|
@ -0,0 +1,82 @@
|
|||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# Qt6::Grpc module is not used directly in this project. But this allows to find Qt6::Grpc's
|
||||
# dependencies without setting extra cmake module paths.
|
||||
find_package(Qt6 COMPONENTS Grpc)
|
||||
find_package(WrapgRPCPlugin)
|
||||
find_package(WrapgRPC)
|
||||
|
||||
if(NOT TARGET WrapgRPC::WrapgRPCPlugin OR NOT TARGET WrapProtoc::WrapProtoc
|
||||
OR NOT TARGET WrapgRPC::WrapLibgRPC)
|
||||
message(WARNING "Dependencies of QtGrpc test server not found. Skipping.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(proto_files "${CMAKE_CURRENT_LIST_DIR}/../proto/exampleservice.proto")
|
||||
set(out_dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(generated_files
|
||||
"${out_dir}/exampleservice.pb.h" "${out_dir}/exampleservice.pb.cc"
|
||||
"${out_dir}/exampleservice.grpc.pb.h" "${out_dir}/exampleservice.grpc.pb.cc")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${generated_files}
|
||||
COMMAND
|
||||
$<TARGET_FILE:WrapProtoc::WrapProtoc>
|
||||
ARGS
|
||||
--grpc_out "${out_dir}"
|
||||
--cpp_out "${out_dir}"
|
||||
-I "${CMAKE_CURRENT_LIST_DIR}/../proto/"
|
||||
--plugin=protoc-gen-grpc=$<TARGET_FILE:WrapgRPC::WrapgRPCPlugin>
|
||||
"${proto_files}"
|
||||
WORKING_DIRECTORY ${out_dir}
|
||||
DEPENDS "${proto_files}"
|
||||
COMMENT "Generating gRPC ${target} sources..."
|
||||
COMMAND_EXPAND_LISTS
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set_source_files_properties(${generated_files} PROPERTIES GENERATED TRUE)
|
||||
add_library(ServerRunner_grpc_gen STATIC ${generated_files})
|
||||
target_include_directories(ServerRunner_grpc_gen
|
||||
PRIVATE
|
||||
${out_dir}
|
||||
WrapgRPC_INCLUDE_PATH
|
||||
)
|
||||
|
||||
target_link_libraries(ServerRunner_grpc_gen
|
||||
PRIVATE
|
||||
WrapProtobuf::WrapLibProtobuf
|
||||
WrapgRPC::WrapLibgRPC
|
||||
)
|
||||
|
||||
add_library(MagicServerRunner
|
||||
STATIC
|
||||
serverrunner.cpp
|
||||
serverrunner.h
|
||||
)
|
||||
|
||||
target_include_directories(MagicServerRunner PRIVATE ${out_dir})
|
||||
|
||||
target_link_libraries(MagicServerRunner
|
||||
PRIVATE
|
||||
ServerRunner_grpc_gen
|
||||
WrapgRPC::WrapLibgRPC
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
qt_add_executable(SimpleGrpcServer
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(SimpleGrpcServer PRIVATE
|
||||
Qt6::Core
|
||||
MagicServerRunner
|
||||
)
|
||||
|
||||
install(TARGETS SimpleGrpcServer
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "serverrunner.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <memory>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
auto server = std::make_unique<ExampleServer>();
|
||||
server->run();
|
||||
return a.exec();
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "serverrunner.h"
|
||||
#include "exampleservice.grpc.pb.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include <grpc++/grpc++.h>
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <random>
|
||||
|
||||
namespace {
|
||||
|
||||
using grpc::Server;
|
||||
using grpc::ServerBuilder;
|
||||
using grpc::ServerContext;
|
||||
using grpc::ServerWriter;
|
||||
using grpc::Status;
|
||||
using qtgrpc::examples::AnswerRequest;
|
||||
using qtgrpc::examples::AnswerResponse;
|
||||
using qtgrpc::examples::ExampleService;
|
||||
|
||||
static const std::array<std::string_view, 10> answers = {"Yes",
|
||||
"Yep",
|
||||
"Most\nlikely",
|
||||
"It is\ncertain",
|
||||
"No",
|
||||
"Nope",
|
||||
"Try later",
|
||||
"Are you\nsure?",
|
||||
"Maybe",
|
||||
"Very\ndoubtful"};
|
||||
|
||||
// Generates random index value.
|
||||
static int generateRandomIndex()
|
||||
{
|
||||
static std::uniform_int_distribution<int> dist(0, answers.size() - 1);
|
||||
return dist(*QRandomGenerator::global());
|
||||
}
|
||||
|
||||
// Logic and data behind the server's behavior.
|
||||
class ExampleServiceServiceImpl final : public qtgrpc::examples::ExampleService::Service
|
||||
{
|
||||
grpc::Status answerMethod(grpc::ServerContext *, const AnswerRequest *request,
|
||||
AnswerResponse *response) override;
|
||||
};
|
||||
}
|
||||
|
||||
Status ExampleServiceServiceImpl::answerMethod(grpc::ServerContext *,
|
||||
const AnswerRequest *request,
|
||||
AnswerResponse *response)
|
||||
{
|
||||
if (request->message() == "sleep")
|
||||
QThread::msleep(2000);
|
||||
|
||||
response->set_message(std::string(answers[generateRandomIndex()]));
|
||||
return Status();
|
||||
}
|
||||
|
||||
void ExampleServer::run()
|
||||
{
|
||||
std::string serverUri("127.0.0.1:50051");
|
||||
ExampleServiceServiceImpl service;
|
||||
|
||||
grpc::ServerBuilder builder;
|
||||
builder.AddListeningPort(serverUri, grpc::InsecureServerCredentials());
|
||||
builder.RegisterService(&service);
|
||||
|
||||
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
|
||||
if (!server) {
|
||||
qDebug() << "Creating grpc::Server failed.";
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Server listening on " << serverUri;
|
||||
server->Wait();
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#ifndef SERVER_RUNNER_H
|
||||
#define SERVER_RUNNER_H
|
||||
|
||||
class ExampleServer
|
||||
{
|
||||
public:
|
||||
void run();
|
||||
};
|
||||
|
||||
#endif // SERVER_RUNNER_H
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (C) 2023 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
|
||||
|
||||
#include "clientservice.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
QQmlApplicationEngine engine;
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
|
||||
&app, [](){
|
||||
QCoreApplication::exit(-1);
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
engine.loadFromModule("qtgrpc.examples.magic8ball", "Main");
|
||||
return app.exec();
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package qtgrpc.examples;
|
||||
|
||||
message AnswerRequest {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
message AnswerResponse {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
service ExampleService {
|
||||
rpc answerMethod(AnswerRequest) returns (AnswerResponse) {}
|
||||
}
|
|
@ -31,9 +31,12 @@ depends += qtdoc qtcore qtnetwork qtwidgets qtprotobuf qtcmake
|
|||
|
||||
{headerdirs,sourcedirs} += ..
|
||||
|
||||
url.examples = "https://code.qt.io/cgit/qt/qtgrpc.git/tree/examples/\1?h=$QT_VER"
|
||||
|
||||
exampledirs += ../../../examples/grpc
|
||||
|
||||
imagedirs += images
|
||||
imagedirs += images \
|
||||
../../../examples/grpc/doc/images
|
||||
|
||||
navigation.landingpage = "Qt GRPC"
|
||||
navigation.cppclassespage = "Qt GRPC C++ Classes"
|
||||
|
|
|
@ -10,3 +10,14 @@
|
|||
|
||||
\brief The Qt GRPC module provides support for communicating with gRPC services.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\group qtgrpc-examples
|
||||
\ingroup all-examples
|
||||
\keyword Qt GRPC Examples
|
||||
\title Qt GRPC Examples
|
||||
\brief A collection of examples for \l {Qt GRPC C++ Classes}
|
||||
|
||||
These examples demonstrate how to generate code using the protobuf and gRPC schemas,
|
||||
and use it in your projects.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue