Browse Source

GRPC client initial version

- Add gRPC client generation
- Extend support library with grpc client related classes
- Add HTTP2 channel implementation based on Qt
Alexey Edelev 6 years ago
parent
commit
4744755d37

+ 9 - 10
src/generator/classgeneratorbase.cpp

@@ -45,6 +45,12 @@ ClassGeneratorBase::ClassGeneratorBase(std::string fullClassName, std::unique_pt
     assert(mNamespaces.size() > 0);
     mClassName = mNamespaces.back();
     mNamespaces.erase(mNamespaces.end() - 1);
+    for (size_t i = 0; i < mNamespaces.size(); i++) {
+        if (i > 0) {
+            mNamespacesColonDelimited = mNamespacesColonDelimited.append("::");
+        }
+        mNamespacesColonDelimited = mNamespacesColonDelimited.append(mNamespaces[i]);
+    }
 }
 
 void ClassGeneratorBase::printPreamble()
@@ -66,7 +72,7 @@ void ClassGeneratorBase::printNamespaces(const std::vector<std::string> &namespa
 
 void ClassGeneratorBase::printClassDeclaration()
 {
-    mPrinter.Print({{"classname", mClassName}}, Templates::ClassDefinitionTemplate);
+    mPrinter.Print({{"classname", mClassName}}, Templates::ProtoClassDefinitionTemplate);
 }
 
 void ClassGeneratorBase::encloseClass()
@@ -98,16 +104,9 @@ void ClassGeneratorBase::printPrivate()
 
 void ClassGeneratorBase::printMetaTypeDeclaration()
 {
-    std::string namespaces;
-    for(size_t i = 0; i < mNamespaces.size(); i++) {
-        if(i > 0) {
-            namespaces = namespaces.append("::");
-        }
-        namespaces = namespaces.append(mNamespaces[i]);
-    }
-    mPrinter.Print({{"classname", mClassName}, {"namespaces", namespaces}},
+    mPrinter.Print({{"classname", mClassName}, {"namespaces", mNamespacesColonDelimited}},
                    Templates::DeclareMetaTypeTemplate);
-    mPrinter.Print({{"classname", mClassName}, {"namespaces", namespaces}},
+    mPrinter.Print({{"classname", mClassName}, {"namespaces", mNamespacesColonDelimited}},
                    Templates::DeclareComplexListTypeTemplate);
 }
 

+ 1 - 0
src/generator/classgeneratorbase.h

@@ -53,6 +53,7 @@ protected:
     ::google::protobuf::io::Printer mPrinter;
     std::string mClassName;
     std::vector<std::string> mNamespaces;
+    std::string mNamespacesColonDelimited;
 
     void printPreamble();
     void printNamespaces();

+ 7 - 0
src/generator/classsourcegeneratorbase.cpp

@@ -48,3 +48,10 @@ void ClassSourceGeneratorBase::printClassHeaderInclude()
     utils::tolower(includeFileName);
     mPrinter.Print({{"type_lower", includeFileName}}, Templates::InternalIncludeTemplate);
 }
+
+void ClassSourceGeneratorBase::printUsingNamespaces(const std::unordered_set<std::string> &namespaces)
+{
+    for(auto ns : namespaces) {
+        mPrinter.Print({{"namespace", ns}}, Templates::UsingNamespaceTemplate);
+    }
+}

+ 3 - 0
src/generator/classsourcegeneratorbase.h

@@ -27,6 +27,7 @@
 
 #include <google/protobuf/io/printer.h>
 #include <memory>
+#include <unordered_set>
 #include "classgeneratorbase.h"
 
 namespace qtprotobuf {
@@ -41,6 +42,8 @@ public:
 
 protected:
     void printClassHeaderInclude();
+    void printUsingNamespaces(const std::unordered_set<std::string> &namespaces);
+
 };
 
 } //namespace qtprotobuf

+ 24 - 0
src/generator/clientgenerator.cpp

@@ -45,3 +45,27 @@ ClientGenerator::ClientGenerator(const ServiceDescriptor *service, std::unique_p
 {
     mClassName += "Client";
 }
+
+void ClientGenerator::printClientClass()
+{
+    mPrinter.Print({{"classname", mClassName}, {"parent_class", "qtprotobuf::AbstractClient"}}, Templates::ClassDefinitionTemplate);
+}
+
+void ClientGenerator::printConstructor()
+{
+    Indent();
+    mPrinter.Print({{"classname", mClassName}}, Templates::ConstructorTemplate);
+    Outdent();
+}
+
+void ClientGenerator::printClientIncludes()
+{
+    printIncludes();
+
+    std::unordered_set<std::string> includeSet;
+    includeSet.insert("abstractclient");
+    includeSet.insert("asyncreply");
+    for(auto type : includeSet) {
+        mPrinter.Print({{"type_lower", type}}, Templates::InternalIncludeTemplate);
+    }
+}

+ 7 - 2
src/generator/clientgenerator.h

@@ -47,14 +47,19 @@ public:
 
     void run() {
         printPreamble();
-        printIncludes();
+        printClientIncludes();
         printNamespaces();
-        printClassName();
+        printClientClass();
         printPublic();
+        printConstructor();
         printMethodsDeclaration(Templates::ClientMethodDeclarationSyncTemplate, Templates::ClientMethodDeclarationAsyncTemplate);
         encloseClass();
         encloseNamespaces();
     }
+private:
+    void printClientClass();
+    void printConstructor();
+    void printClientIncludes();
 };
 
 

+ 8 - 4
src/generator/clientsourcegenerator.cpp

@@ -44,7 +44,6 @@ ClientSourceGenerator::ClientSourceGenerator(const google::protobuf::ServiceDesc
 
 void ClientSourceGenerator::printMethods()
 {
-    Indent();
     for(int i = 0; i < mService->method_count(); i++) {
         const MethodDescriptor* method = mService->method(i);
         std::string inputTypeName = method->input_type()->full_name();
@@ -55,12 +54,17 @@ void ClientSourceGenerator::printMethods()
                                                          {"return_type", outputTypeName},
                                                          {"method_name", method->name()},
                                                          {"param_type", inputTypeName},
-                                                         {"param_name", ""},
-                                                         {"return_name", ""}
+                                                         {"param_name", "arg"},
+                                                         {"return_name", "ret"}
                                                         };
         mPrinter.Print(parameters, Templates::ClientMethodDefinitionSyncTemplate);
         mPrinter.Print(parameters, Templates::ClientMethodDefinitionAsyncTemplate);
     }
-    Outdent();
 }
 
+void ClientSourceGenerator::printConstructor()
+{
+    mPrinter.Print({ {"classname", mClassName},
+                     {"parent_class", "AbstractClient"},
+                     {"service_name", mService->name()} }, Templates::ConstructorDefinitionSyncTemplate);
+}

+ 3 - 3
src/generator/clientsourcegenerator.h

@@ -37,14 +37,14 @@ public:
                           std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> out);
     void run() override {
         printClassHeaderInclude();
-        printNamespaces();
+        printUsingNamespaces({"qtprotobuf", mNamespacesColonDelimited});
+        printConstructor();
         printMethods();
-        encloseNamespaces();
     }
 
 protected:
     void printMethods();
-
+    void printConstructor();
     const ::google::protobuf::ServiceDescriptor* mService;
 };
 

+ 1 - 1
src/generator/protobufclassgenerator.cpp

@@ -300,7 +300,7 @@ bool ProtobufClassGenerator::isComplexType(const FieldDescriptor *field)
 
 void ProtobufClassGenerator::printConstructor()
 {
-    mPrinter.Print({{"classname", mClassName}}, Templates::ConstructorTemplate);
+    mPrinter.Print({{"classname", mClassName}}, Templates::ProtoConstructorTemplate);
     mPrinter.Print(Templates::ConstructorContentTemplate);
 }
 

+ 1 - 8
src/generator/protobufsourcegenerator.cpp

@@ -43,14 +43,7 @@ ProtobufSourceGenerator::ProtobufSourceGenerator(const google::protobuf::Descrip
 
 void ProtobufSourceGenerator::printRegisterBody()
 {
-    std::string namespaces;
-    for(size_t i = 0; i < mNamespaces.size(); i++) {
-        if(i > 0) {
-            namespaces = namespaces.append("::");
-        }
-        namespaces = namespaces.append(mNamespaces[i]);
-    }
-    mPrinter.Print({{"classname", mClassName}, {"namespaces", namespaces}}, Templates::ComplexTypeRegistrationTemplate);
+    mPrinter.Print({{"classname", mClassName}, {"namespaces", mNamespacesColonDelimited}}, Templates::ComplexTypeRegistrationTemplate);
 }
 
 void ProtobufSourceGenerator::printFieldsOrdering() {

+ 0 - 2
src/generator/servicegeneratorbase.cpp

@@ -52,8 +52,6 @@ void ServiceGeneratorBase::printIncludes()
 {
     std::unordered_set<std::string> includeSet;
 
-    includeSet.insert("asyncreply");
-
     for(int i = 0; i < mService->method_count(); i++) {
         const MethodDescriptor* method = mService->method(i);
         std::string inputTypeName = method->input_type()->name();

+ 17 - 11
src/generator/templates.cpp

@@ -58,11 +58,11 @@ const char *Templates::ComplexListTypeUsingTemplate = "using $classname$List = Q
 const char *Templates::EnumTypeUsingTemplate = "using $enum$List = QList<$enum$>;\n";
 
 const char *Templates::NamespaceTemplate = "\nnamespace $namespace$ {\n";
-
+const char *Templates::UsingNamespaceTemplate = "using namespace $namespace$;\n";
 const char *Templates::NonProtoClassDefinitionTemplate = "\nclass $classname$ : public QObject\n"
                                                          "{\n"
                                                          "    Q_OBJECT\n";
-const char *Templates::ClassDefinitionTemplate = "\nclass $classname$ final : public qtprotobuf::ProtobufObject<$classname$>\n"
+const char *Templates::ProtoClassDefinitionTemplate = "\nclass $classname$ final : public qtprotobuf::ProtobufObject<$classname$>\n"
                                                  "{\n"
                                                  "    Q_OBJECT\n";
 
@@ -74,7 +74,8 @@ const char *Templates::PublicBlockTemplate = "\npublic:\n";
 const char *Templates::PrivateBlockTemplate = "\nprivate:\n";
 const char *Templates::EnumDefinitionTemplate = "enum $enum$ {\n";
 const char *Templates::EnumFieldTemplate = "$enumvalue$ = $value$,\n";
-const char *Templates::ConstructorTemplate = "$classname$(QObject *parent = nullptr) : ProtobufObject(parent)\n";
+const char *Templates::ProtoConstructorTemplate = "$classname$(QObject *parent = nullptr) : ProtobufObject(parent)\n";
+const char *Templates::ConstructorTemplate = "$classname$();\n";
 const char *Templates::CopyConstructorTemplate = "$classname$(const $classname$ &other) : ProtobufObject() {\n";
 const char *Templates::MoveConstructorTemplate = "$classname$($classname$ &&other) : ProtobufObject() {\n";
 const char *Templates::CopyFieldTemplate = "m_$property_name$ = other.m_$property_name$;\n";
@@ -129,19 +130,24 @@ const char *Templates::DeclareComplexListTypeTemplate = "Q_DECLARE_METATYPE($nam
 
 const char *Templates::QEnumTemplate = "Q_ENUM($type$)\n";
 
+const char *Templates::ClassDefinitionTemplate = "\nclass $classname$ : public $parent_class$\n"
+                                                 "{\n";
 const char *Templates::ClientMethodDeclarationSyncTemplate = "Q_INVOKABLE void $method_name$(const $param_type$ &$param_name$, $return_type$ &$return_name$);\n";
 const char *Templates::ClientMethodDeclarationAsyncTemplate = "Q_INVOKABLE void $method_name$(const $param_type$ &$param_name$, const qtprotobuf::AsyncReply<$return_type$> &reply);\n";
 const char *Templates::ServerMethodDeclarationTemplate = "Q_INVOKABLE virtual $return_type$ $method_name$(const $param_type$ &$param_name$) = 0;\n";
 
 
-const char *Templates::ClientMethodDefinitionAsyncTemplate = "void $classname$::$method_name$(const $param_type$ &$param_name$, $return_type$ &$return_name$)\n"
-                                                        "{\n"
-                                                        "    //TODO: call transport method to serialize this method\n"
-                                                        "}\n";
-const char *Templates::ClientMethodDefinitionSyncTemplate = "void $classname$::$method_name$(const $param_type$ &$param_name$, const qtprotobuf::AsyncReply<$return_type$> &reply)\n"
-                                                        "{\n"
-                                                        "    //TODO: call transport method to serialize this method\n"
-                                                        "}\n";
+const char *Templates::ConstructorDefinitionSyncTemplate = "$classname$::$classname$() : $parent_class$(\"$service_name$\")\n"
+                                                           "{\n"
+                                                           "}\n";
+const char *Templates::ClientMethodDefinitionSyncTemplate = "void $classname$::$method_name$(const $param_type$ &$param_name$, $return_type$ &$return_name$)\n"
+                                                            "{\n"
+                                                            "    call(\"$method_name$\", $param_name$, $return_name$);\n"
+                                                            "}\n";
+const char *Templates::ClientMethodDefinitionAsyncTemplate = "void $classname$::$method_name$(const $param_type$ &$param_name$, const qtprotobuf::AsyncReply<$return_type$> &reply)\n"
+                                                             "{\n"
+                                                             "    //TODO: call transport method to serialize this method\n"
+                                                             "}\n";
 
 const std::unordered_map<::google::protobuf::FieldDescriptor::Type, std::string> Templates::TypeReflection = {
     {::google::protobuf::FieldDescriptor::TYPE_DOUBLE, "double"},

+ 5 - 0
src/generator/templates.h

@@ -47,7 +47,9 @@ public:
     static const char *ComplexListTypeUsingTemplate;
     static const char *EnumTypeUsingTemplate;
     static const char *NamespaceTemplate;
+    static const char *UsingNamespaceTemplate;
     static const char *NonProtoClassDefinitionTemplate;
+    static const char *ProtoClassDefinitionTemplate;
     static const char *ClassDefinitionTemplate;
     static const char *PropertyTemplate;
     static const char *MessagePropertyTemplate;
@@ -57,6 +59,7 @@ public:
     static const char *PrivateBlockTemplate;
     static const char *EnumDefinitionTemplate;
     static const char *EnumFieldTemplate;
+    static const char *ProtoConstructorTemplate;
     static const char *ConstructorTemplate;
     static const char *CopyConstructorTemplate;
     static const char *MoveConstructorTemplate;
@@ -88,6 +91,8 @@ public:
     static const char *QEnumTemplate;
 
     //Service templates
+    static const char *ConstructorDefinitionSyncTemplate;
+
     static const char *ClientMethodDeclarationSyncTemplate;
     static const char *ClientMethodDeclarationAsyncTemplate;
     static const char *ServerMethodDeclarationTemplate;

+ 12 - 4
src/lib/CMakeLists.txt

@@ -1,8 +1,8 @@
-find_package(Qt5 COMPONENTS Core REQUIRED)
+find_package(Qt5 COMPONENTS Core Network REQUIRED)
 
 set(SUPPORT_LIBRARY_TARGET qtprotobufsupport)
 
-include_directories(${Qt5Core_INCLUDE_DIRS})
+include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS})
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
 set(CMAKE_AUTOMOC ON)
 set(CMAKE_AUTORCC ON)
@@ -17,17 +17,25 @@ file(GLOB SOURCES universallistmodelbase.cpp
     protobufobject.cpp
     qtprotobuf.cpp
     qtprotobuflogging.cpp
-    asyncreply.cpp)
+    asyncreply.cpp
+    abstractchannel.cpp
+    http2channel.cpp
+    abstractclient.cpp)
 
 file(GLOB HEADERS universallistmodelbase.h
     universallistmodel.h
     protobufobject.h
     qtprotobuftypes.h
     qtprotobuf.h
-    asyncreply.h)
+    asyncreply.h
+    abstractchannel.h
+    http2channel.h
+    abstractclient.h)
 
 add_library(${SUPPORT_LIBRARY_TARGET} ${SOURCES})
 set_target_properties(${SUPPORT_LIBRARY_TARGET} PROPERTIES PUBLIC_HEADER "${HEADERS}")
+target_link_libraries(${SUPPORT_LIBRARY_TARGET} Qt5::Core Qt5::Network)
+
 install(TARGETS ${SUPPORT_LIBRARY_TARGET}
     ARCHIVE DESTINATION lib
     PUBLIC_HEADER DESTINATION include)

+ 33 - 0
src/lib/abstractchannel.cpp

@@ -0,0 +1,33 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 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 "abstractchannel.h"
+
+using namespace qtprotobuf;
+
+AbstractChannel::AbstractChannel()
+{
+
+}

+ 69 - 0
src/lib/abstractchannel.h

@@ -0,0 +1,69 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 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 <QString>
+#include <QByteArray>
+
+namespace qtprotobuf {
+
+class AbstractChannel
+{
+public:
+    /*!
+     * \brief The Channel StatusCodes
+     */
+    enum StatusCodes {
+        Ok = 0,                 //!< No error
+        Cancelled = 1,          //!< The operation was cancelled, typically by the caller
+        Unknown = 2,            //!< Unknown error
+        InvalidArgument = 3,    //!< The client specified an invalid argument
+        DeadlineExceeded = 4,   //!< The deadline expired before the operation could complete
+        NotFound = 5,           //!< Some requested entity (e.g., file or directory) was not found
+        AlreadyExists = 6,      //!< The entity that a client attempted to create (e.g., file or directory) already exists. 	409 Conflict
+        PermissionDenied = 7,   //!< The caller does not have permission to execute the specified operation. PERMISSION_DENIED must not be used for rejections caused by exhausting some resource (use RESOURCE_EXHAUSTED instead for those errors). PERMISSION_DENIED must not be used if the caller can not be identified (use UNAUTHENTICATED instead for those errors). This error code does not imply the request is valid or the requested entity exists or satisfies other pre-conditions. 	403 Forbidden
+        Unauthenticated = 16,   //!< The request does not have valid authentication credentials for the operation
+        ResourceExhausted = 8,  //!< Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space
+        FailedPrecondition = 9, //!< The operation was rejected because the system is not in a state required for the operation's execution
+        Aborted = 10,           //!< The operation was aborted, typically due to a concurrency issue such as a sequencer check failure or transaction abort
+        OutOfRange = 11,        //!< The operation was attempted past the valid range
+        Unimplemented = 12,     //!< The operation is not implemented or is not supported/enabled in this service
+        Internal = 13,          //!< Internal errors. This means that some invariants expected by the underlying system have been broken.
+        Unavailable = 14,       //!< The service is currently unavailable
+        DataLoss = 15,          //!< Unrecoverable data loss or corruption
+    };
+
+    virtual StatusCodes call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret) = 0;
+
+protected:
+    AbstractChannel();
+    virtual ~AbstractChannel() = default;
+
+private:
+    Q_DISABLE_COPY(AbstractChannel)
+};
+
+}

+ 39 - 0
src/lib/abstractclient.cpp

@@ -0,0 +1,39 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 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 "abstractclient.h"
+
+using namespace qtprotobuf;
+
+AbstractClient::AbstractClient(const QString &service, QObject *parent) : QObject(parent)
+  , m_service(service)
+{
+
+}
+
+void AbstractClient::attachChannel(std::shared_ptr<AbstractChannel> channel)
+{
+    m_channel = channel;
+}

+ 71 - 0
src/lib/abstractclient.h

@@ -0,0 +1,71 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 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 <memory>
+#include <QObject>
+#include <QByteArray>
+#include <QDebug>
+
+#include "abstractchannel.h"
+
+namespace qtprotobuf {
+
+class AbstractChannel;
+
+class AbstractClient : public QObject //TODO: QObject is not really required yet
+{
+public:
+    void attachChannel(std::shared_ptr<AbstractChannel> channel);
+
+protected:
+    AbstractClient(const QString &service, QObject *parent = nullptr);
+
+    template<typename A, typename R>
+    AbstractChannel::StatusCodes call(const QString &method, const A &arg, R &ret) {
+        if (!m_channel) {
+            return AbstractChannel::Unknown;
+        }
+
+        QByteArray retData;
+        AbstractChannel::StatusCodes status = m_channel->call(method, m_service, QByteArray(), retData);
+        qCritical() << "Answer: " << retData << status;
+        if (status != AbstractChannel::Ok) {
+            return status;
+        }
+        qCritical() << "Answer: " << retData;
+        ret.deserialize(retData.mid(0, 5));
+        return status;
+    }
+
+private:
+    Q_DISABLE_COPY(AbstractClient)
+
+    std::shared_ptr<AbstractChannel> m_channel;
+    QString m_service;
+};
+
+}

+ 25 - 0
src/lib/asyncreply.cpp

@@ -1,2 +1,27 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 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 "asyncreply.h"
 

+ 155 - 0
src/lib/http2channel.cpp

@@ -0,0 +1,155 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 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 "http2channel.h"
+
+//FIXME: workaround for build issue
+#undef QT_LINKED_OPENSSL
+
+#include <QUrl>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QEventLoop>
+#include <QTimer>
+
+#include <unordered_map>
+
+using namespace qtprotobuf;
+
+namespace qtprotobuf {
+
+struct Http2ChannelPrivate {
+    QUrl url;
+    QNetworkAccessManager nm;
+};
+
+}
+
+namespace  {
+const static std::unordered_map<QNetworkReply::NetworkError, AbstractChannel::StatusCodes> StatusCodeMap = { { QNetworkReply::ConnectionRefusedError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::RemoteHostClosedError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::HostNotFoundError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::TimeoutError, AbstractChannel::DeadlineExceeded },
+                                                                { QNetworkReply::OperationCanceledError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::SslHandshakeFailedError, AbstractChannel::PermissionDenied },
+                                                                { QNetworkReply::TemporaryNetworkFailureError, AbstractChannel::Unknown },
+                                                                { QNetworkReply::NetworkSessionFailedError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::BackgroundRequestNotAllowedError, AbstractChannel::Unknown },
+                                                                { QNetworkReply::TooManyRedirectsError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::InsecureRedirectError, AbstractChannel::PermissionDenied },
+                                                                { QNetworkReply::UnknownNetworkError, AbstractChannel::Unknown },
+                                                                { QNetworkReply::ProxyConnectionRefusedError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::ProxyConnectionClosedError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::ProxyNotFoundError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::ProxyTimeoutError, AbstractChannel::DeadlineExceeded },
+                                                                { QNetworkReply::ProxyAuthenticationRequiredError, AbstractChannel::Unauthenticated },
+                                                                { QNetworkReply::UnknownProxyError, AbstractChannel::Unknown },
+                                                                { QNetworkReply::ContentAccessDenied, AbstractChannel::PermissionDenied },
+                                                                { QNetworkReply::ContentOperationNotPermittedError, AbstractChannel::PermissionDenied },
+                                                                { QNetworkReply::ContentNotFoundError, AbstractChannel::NotFound },
+                                                                { QNetworkReply::AuthenticationRequiredError, AbstractChannel::PermissionDenied },
+                                                                { QNetworkReply::ContentReSendError, AbstractChannel::DataLoss },
+                                                                { QNetworkReply::ContentConflictError, AbstractChannel::InvalidArgument },
+                                                                { QNetworkReply::ContentGoneError, AbstractChannel::DataLoss },
+                                                                { QNetworkReply::UnknownContentError, AbstractChannel::Unknown },
+                                                                { QNetworkReply::ProtocolUnknownError, AbstractChannel::Unknown },
+                                                                { QNetworkReply::ProtocolInvalidOperationError, AbstractChannel::Unimplemented },
+                                                                { QNetworkReply::ProtocolFailure, AbstractChannel::Unknown },
+                                                                { QNetworkReply::InternalServerError, AbstractChannel::Internal },
+                                                                { QNetworkReply::OperationNotImplementedError, AbstractChannel::Unimplemented },
+                                                                { QNetworkReply::ServiceUnavailableError, AbstractChannel::Unavailable },
+                                                                { QNetworkReply::UnknownServerError, AbstractChannel::Unknown }};
+
+const char *GrpcAcceptEncodingHeader = "grpc-accept-encoding";
+const char *AcceptEncodingHeader = "accept-encoding";
+const char *TEHeader = "te";
+const char *GrpcStatusHeader = "grpc-status";
+}
+
+Http2Channel::Http2Channel(const QString &addr, quint16 port) : AbstractChannel()
+  , d(new Http2ChannelPrivate)
+{
+    d->url.setScheme("http");
+    qCritical() << "Authority: " << (addr + ":" + QString::number(port));
+    d->url.setHost(addr, QUrl::StrictMode);
+    d->url.setPort(port);
+}
+
+Http2Channel::~Http2Channel()
+{
+    delete d;
+}
+
+AbstractChannel::StatusCodes Http2Channel::call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret)
+{
+    QUrl callUrl = d->url;
+    callUrl.setPath("/" + service + "/" + method);
+
+    qCritical() << "url: " << callUrl;
+    QNetworkRequest request(callUrl);
+    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/grpc");
+    request.setRawHeader(GrpcAcceptEncodingHeader, "deflate, gzip");
+    request.setRawHeader(AcceptEncodingHeader, "deflate, gzip");
+    request.setRawHeader(TEHeader, "trailers");
+
+    request.setAttribute(QNetworkRequest::Http2DirectAttribute, true);
+
+    QNetworkReply *reply = d->nm.post(request, QByteArray(5, '\0') + args);
+    QEventLoop loop;
+    QTimer timer;
+    loop.connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
+    loop.connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+
+    //TODO: Add configurable timeout logic
+    timer.setInterval(1000);
+    timer.start();
+
+    if (!reply->isFinished()) {
+        loop.exec();
+    }
+
+    timer.stop();
+
+    //Check if no network error occured
+    if (reply->error() != QNetworkReply::NoError) {
+        return StatusCodeMap.at(reply->error());
+    }
+
+    //Check if response timeout triggered
+    if (!reply->isFinished()) {
+        reply->abort();
+        return AbstractChannel::DeadlineExceeded;
+    }
+
+    //Check if server answer with error
+    StatusCodes grpcStatus = static_cast<StatusCodes>(reply->rawHeader(GrpcStatusHeader).toInt());
+    if (grpcStatus != StatusCodes::Ok) {
+        return grpcStatus;
+    }
+
+    ret = reply->readAll();
+    return StatusCodes::Ok;
+}

+ 50 - 0
src/lib/http2channel.h

@@ -0,0 +1,50 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 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 "abstractchannel.h"
+
+#include <QString>
+#include <QByteArray>
+
+namespace qtprotobuf {
+
+class Http2ChannelPrivate;
+
+class Http2Channel final : public AbstractChannel
+{
+public:
+    Http2Channel(const QString &addr, quint16 port);
+    ~Http2Channel();
+
+    StatusCodes call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret) override;
+
+private:
+    Q_DISABLE_COPY(Http2Channel)
+    Http2ChannelPrivate *d;
+};
+
+}

+ 9 - 0
tests/clienttest.cpp

@@ -24,8 +24,10 @@
  */
 
 #include "clienttest.h"
+#include <QCoreApplication>
 
 #include "testserviceclient.h"
+#include "http2channel.h"
 
 using namespace qtprotobufnamespace::tests;
 using namespace qtprotobuf::tests;
@@ -39,5 +41,12 @@ ClientTest::ClientTest()
 
 TEST_F(ClientTest, CheckMethodsGeneration)
 {
+    int argc = 0;
+    QCoreApplication app(argc, nullptr);
     TestServiceClient testClient;
+    testClient.attachChannel(std::make_shared<Http2Channel>("localhost", 50051));
+    SimpleStringMessage result;
+    SimpleStringMessage request;
+    request.setTestFieldString("Hello beach!");
+    testClient.testMethod(request, result);
 }

+ 1 - 1
tests/proto/testservice.proto

@@ -5,5 +5,5 @@ import "simpletest.proto";
 package qtprotobufnamespace.tests;
 
 service TestService {
-  rpc testMethod (SimpleIntMessage) returns (SimpleIntMessage) {}
+  rpc testMethod(SimpleStringMessage) returns (SimpleStringMessage) {}
 } 

+ 1 - 1
tests/servertest.cpp

@@ -33,7 +33,7 @@ using namespace qtprotobuf::tests;
 using namespace qtprotobuf;
 
 class TestServiceServerImpl : public TestServiceServer {
-    SimpleIntMessage testMethod(const SimpleIntMessage &) override { return SimpleIntMessage(); }
+    SimpleStringMessage testMethod(const SimpleStringMessage &) override { return SimpleStringMessage(); }
 };
 
 ServerTest::ServerTest()