[Example] Added gRPC C++ generic API example (#35411)

Closes #35411

PiperOrigin-RevId: 599930218
pull/35530/head
Esun Kim 1 year ago committed by Copybara-Service
parent fe75f2b5ff
commit 32c5d1c160
  1. 40
      examples/cpp/generic_api/BUILD
  2. 70
      examples/cpp/generic_api/CMakeLists.txt
  3. 36
      examples/cpp/generic_api/README.md
  4. 112
      examples/cpp/generic_api/greeter_client.cc
  5. 143
      examples/cpp/generic_api/greeter_server.cc

@ -0,0 +1,40 @@
# Copyright 2023 the gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
cc_binary(
name = "greeter_client",
srcs = ["greeter_client.cc"],
defines = ["BAZEL_BUILD"],
deps = [
"//:grpc++",
"//examples/protos:helloworld_cc_grpc",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/flags:parse",
],
)
cc_binary(
name = "greeter_server",
srcs = ["greeter_server.cc"],
defines = ["BAZEL_BUILD"],
deps = [
"//:grpc++",
"//examples/protos:helloworld_cc_grpc",
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/flags:parse",
"@com_google_absl//absl/strings:str_format",
],
)

@ -0,0 +1,70 @@
# Copyright 2023 the gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# cmake build file for C++ helloworld example.
# Assumes protobuf and gRPC have been installed using cmake.
# See cmake_externalproject/CMakeLists.txt for all-in-one cmake build
# that automatically builds all the dependencies before building helloworld.
cmake_minimum_required(VERSION 3.8)
project(GenericAPI C CXX)
include(../cmake/common.cmake)
# Proto files
get_filename_component(hw_proto "../../protos/helloworld.proto" ABSOLUTE)
get_filename_component(hw_proto_path "${hw_proto}" PATH)
# Generated sources
set(hw_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.cc")
set(hw_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.pb.h")
set(hw_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.cc")
set(hw_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/helloworld.grpc.pb.h")
add_custom_command(
OUTPUT "${hw_proto_srcs}" "${hw_proto_hdrs}" "${hw_grpc_srcs}" "${hw_grpc_hdrs}"
COMMAND ${_PROTOBUF_PROTOC}
ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
--cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
-I "${hw_proto_path}"
--plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
"${hw_proto}"
DEPENDS "${hw_proto}")
# Include generated *.pb.h files
include_directories("${CMAKE_CURRENT_BINARY_DIR}")
# hw_grpc_proto
add_library(hw_grpc_proto
${hw_grpc_srcs}
${hw_grpc_hdrs}
${hw_proto_srcs}
${hw_proto_hdrs})
target_link_libraries(hw_grpc_proto
${_REFLECTION}
${_GRPC_GRPCPP}
${_PROTOBUF_LIBPROTOBUF})
# Targets greeter_(client|server)
foreach(_target
greeter_client greeter_server)
add_executable(${_target} "${_target}.cc")
target_link_libraries(${_target}
hw_grpc_proto
absl::flags
absl::flags_parse
${_REFLECTION}
${_GRPC_GRPCPP}
${_PROTOBUF_LIBPROTOBUF})
endforeach()

@ -0,0 +1,36 @@
# Generic API Example
## Overview
While generated stub code is often the simpler and best choice for sending and handling API calls,
generic APIs offer unique advantages in specific scenarios, such as proxy implementation.
Their ability to manage multiple message types with a single function makes them particularly handy
in these cases. This example demonstrates how to use generic APIs to achieve this flexibility.
This example implements `greeter_callback_client` and `greeter_callback_server` using the generic APIs.
Therefore, looking at the difference would be helpful to understand how to use generic APIs.
### Try it!
Once you have working gRPC, you can build this example using either bazel or cmake.
Run the server, which will listen on port 50051:
```sh
$ ./greeter_server
```
Run the client (in a different terminal):
```sh
$ ./greeter_client
```
If things go smoothly, you will see the client output:
```
### Send: SayHello(name=World)
Ok. ReplyMessage=Hello World
### Send: SayHello(name=gRPC)
Ok. ReplyMessage=Hello gRPC
```

@ -0,0 +1,112 @@
// Copyright 2023 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/strings/str_format.h"
#include <grpcpp/generic/generic_stub.h>
#include <grpcpp/grpcpp.h>
#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif
ABSL_FLAG(std::string, target, "localhost:50051", "Server address");
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;
using ProtoGenericStub =
::grpc::TemplatedGenericStub<::google::protobuf::Message,
::google::protobuf::Message>;
class GreeterClient {
public:
GreeterClient(std::shared_ptr<Channel> channel)
: stub_(new ProtoGenericStub(channel)) {}
// Assembles the client's payload, sends it and prints the response back
// from the server.
void SayHello(const std::string& user) {
// Data we are sending to the server.
HelloRequest request;
request.set_name(user);
// Container for the data we expect from the server.
HelloReply reply;
// Context for the client. It could be used to convey extra information to
// the server and/or tweak certain RPC behaviors.
ClientContext context;
// The actual RPC.
std::mutex mu;
std::condition_variable cv;
bool done = false;
Status status;
std::cout << absl::StrFormat("### Send: SayHello(name=%s)", user)
<< std::endl;
// Send a unary call using a generic stub. Unlike generated subs,
// this requires to specify the name of call.
stub_->UnaryCall(&context, "/helloworld.Greeter/SayHello",
grpc::StubOptions(), &request, &reply, [&](Status s) {
status = std::move(s);
std::lock_guard<std::mutex> lock(mu);
done = true;
cv.notify_one();
});
std::unique_lock<std::mutex> lock(mu);
while (!done) {
cv.wait(lock);
}
// Handles the reply
if (status.ok()) {
std::cout << absl::StrFormat("Ok. ReplyMessage=%s", reply.message())
<< std::endl;
} else {
std::cout << absl::StrFormat("Failed. Code=%d Message=%s",
status.error_code(), status.error_message())
<< std::endl;
}
}
private:
// Instead of `Greeter::Stub`, it uses `ProtoGenericStub` to send any calls.
std::unique_ptr<ProtoGenericStub> stub_;
};
int main(int argc, char** argv) {
absl::ParseCommandLine(argc, argv);
// Instantiate the client. It requires a channel, out of which the actual RPCs
// are created. This channel models a connection to an endpoint specified by
// the argument "--target=" which is the only expected argument.
std::string target_str = absl::GetFlag(FLAGS_target);
// We indicate that the channel isn't authenticated (use of
// InsecureChannelCredentials()).
GreeterClient greeter(
grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));
greeter.SayHello("World");
greeter.SayHello("gRPC");
return 0;
}

@ -0,0 +1,143 @@
// Copyright 2023 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <memory>
#include <string>
#include <unordered_set>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/strings/str_format.h"
#include "absl/synchronization/mutex.h"
#include <grpcpp/grpcpp.h>
#include <grpcpp/health_check_service_interface.h>
#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "helloworld.grpc.pb.h"
#endif
ABSL_FLAG(uint16_t, port, 50051, "Server port for the service");
using grpc::ByteBuffer;
using grpc::CallbackGenericService;
using grpc::CallbackServerContext;
using grpc::GenericCallbackServerContext;
using grpc::ProtoBufferReader;
using grpc::ProtoBufferWriter;
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerGenericBidiReactor;
using grpc::Status;
using grpc::StatusCode;
using helloworld::HelloReply;
using helloworld::HelloRequest;
// Logic and data behind the server's behavior.
class GreeterServiceImpl final : public CallbackGenericService {
ServerGenericBidiReactor* CreateReactor(
GenericCallbackServerContext* context) override {
if (context->method() == "/helloworld.Greeter/SayHello") {
// Let the SayHello reactor handle this now on.
return new SayHelloReactor();
} else {
// Forward this to the implementation of the base calss returning
// UNIMPLEMENTED.
return CallbackGenericService::CreateReactor(context);
}
}
class SayHelloReactor : public ServerGenericBidiReactor {
public:
SayHelloReactor() { StartRead(&request_); }
private:
Status OnSayHello(const HelloRequest& request, HelloReply* reply) {
if (request.name() == "") {
return Status(StatusCode::INVALID_ARGUMENT, "name is not specified");
}
reply->set_message(absl::StrFormat("Hello %s", request.name()));
return Status::OK;
}
void OnDone() override { delete this; }
void OnReadDone(bool ok) override {
if (!ok) {
return;
}
Status result;
// Deserialize a request message
HelloRequest request;
result = grpc::GenericDeserialize<ProtoBufferReader, HelloRequest>(
&request_, &request);
if (!result.ok()) {
Finish(result);
return;
}
// Call the SayHello handler
HelloReply reply;
result = OnSayHello(request, &reply);
if (!result.ok()) {
Finish(result);
return;
}
// Serialize a reply message
bool own_buffer;
result = grpc::GenericSerialize<ProtoBufferWriter, HelloReply>(
reply, &response_, &own_buffer);
if (!result.ok()) {
Finish(result);
return;
}
StartWrite(&response_);
}
void OnWriteDone(bool ok) override {
Finish(ok ? Status::OK
: Status(StatusCode::UNKNOWN, "Unexpected failure"));
}
ByteBuffer request_;
ByteBuffer response_;
};
private:
absl::Mutex mu_;
};
void RunServer(uint16_t port) {
std::string server_address = absl::StrFormat("0.0.0.0:%d", port);
GreeterServiceImpl service;
grpc::EnableDefaultHealthCheckService(true);
ServerBuilder builder;
// Listen on the given address without any authentication mechanism.
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
// Register "service" as the instance through which we'll communicate with
// clients. In this case it corresponds to an *synchronous* service.
builder.RegisterCallbackGenericService(&service);
// Finally assemble the server.
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
// Wait for the server to shutdown. Note that some other thread must be
// responsible for shutting down the server for this call to ever return.
server->Wait();
}
int main(int argc, char** argv) {
absl::ParseCommandLine(argc, argv);
RunServer(absl::GetFlag(FLAGS_port));
return 0;
}
Loading…
Cancel
Save