Github   Telegram
Loading...
Searching...
No Matches
gRPC client and service

Before you start

Make sure that you can compile and run core tests and read a basic example Writing your first HTTP server.

Step by step guide

In this example, we will write a client side and a server side for a simple GreeterService from greeter.proto (see the schema below). Its single SayHello method accepts a name string and replies with a corresponding greeting string.

Installation

We generate and link to a CMake library from our .proto schema:

include(GrpcTargets)
add_grpc_library(${PROJECT_NAME}_proto PROTOS samples/greeter.proto)
target_link_libraries(${PROJECT_NAME} ${PROJECT_NAME}_proto)

Register the necessary ugrpc components:

.Append<ugrpc::server::ServerComponent>()

The client side

Wrap the generated api::GreeterServiceClient in a component that exposes a simplified interface:

// A user-defined wrapper around api::GreeterServiceClient that provides
// a simplified interface.
class GreeterClient final : public components::LoggableComponentBase {
public:
static constexpr std::string_view kName = "greeter-client";
GreeterClient(const components::ComponentConfig& config,
: LoggableComponentBase(config, context),
// ClientFactory is used to create gRPC clients
client_factory_(
context.FindComponent<ugrpc::client::ClientFactoryComponent>()
.GetFactory()),
// The client needs a fixed endpoint
client_(client_factory_.MakeClient<api::GreeterServiceClient>(
config["endpoint"].As<std::string>())) {}
std::string SayHello(std::string name);
static yaml_config::Schema GetStaticConfigSchema();
private:
ugrpc::client::ClientFactory& client_factory_;
api::GreeterServiceClient client_;
};

A single request-response RPC handling is simple: fill in request and context, initiate the RPC, receive the response.

std::string GreeterClient::SayHello(std::string name) {
api::GreetingRequest request;
request.set_name(std::move(name));
// Deadline must be set manually for each RPC
auto context = std::make_unique<grpc::ClientContext>();
context->set_deadline(
engine::Deadline::FromDuration(std::chrono::seconds{20}));
// Initiate the RPC. No actual actions have been taken thus far besides
// preparing to send the request.
auto stream = client_.SayHello(request, std::move(context));
// Complete the unary RPC by sending the request and receiving the response.
// The client should call `Finish` (in case of single response) or `Read`
// until `false` (in case of response stream), otherwise the RPC will be
// cancelled.
api::GreetingResponse response = stream.Finish();
return std::move(*response.mutable_greeting());
}

Fill in the static config entries for the client side:

# yaml
# Creates gRPC clients
grpc-client-factory:
# The TaskProcessor for blocking connection initiation
task-processor: grpc-blocking-task-processor
# Optional channel parameters for gRPC Core
# https://grpc.github.io/grpc/core/group__grpc__arg__keys.html
channel-args: {}
# Our wrapper around the generated client for GreeterService
greeter-client:
# The service endpoint (URI). We talk to our own service,
# which is kind of pointless, but works for an example
endpoint: '[::1]:8091'
# yaml
task_processors:
grpc-blocking-task-processor: # For blocking gRPC channel creation
thread_name: grpc-worker
worker_threads: 2

The server side

Implement the generated api::GreeterServiceBase. As a convenience, a derived api::GreeterServiceBase::Component class is provided for easy integration with the component system.

class GreeterServiceComponent final
: public api::GreeterServiceBase::Component {
public:
static constexpr std::string_view kName = "greeter-service";
GreeterServiceComponent(const components::ComponentConfig& config,
: api::GreeterServiceBase::Component(config, context),
prefix_(config["greeting-prefix"].As<std::string>()) {}
void SayHello(SayHelloCall& call, api::GreetingRequest&& request) override;
static yaml_config::Schema GetStaticConfigSchema();
private:
const std::string prefix_;
};

A single request-response RPC handling is simple: fill in the response and send it.

void GreeterServiceComponent::SayHello(
api::GreeterServiceBase::SayHelloCall& call,
api::GreetingRequest&& request) {
// Authentication checking could have gone here. For this example, we trust
// the world.
api::GreetingResponse response;
response.set_greeting(fmt::format("{}, {}!", prefix_, request.name()));
// Complete the RPC by sending the response. The service should complete
// each request by calling `Finish` or `FinishWithError`, otherwise the
// client will receive an Internal Error (500) response.
call.Finish(response);
}

Fill in the static config entries for the server side:

# yaml
# Common configuration for gRPC server
grpc-server:
# The single listening port for incoming RPCs
port: 8091
# Our GreeterService implementation
greeter-service:
task-processor: main-task-processor
greeting-prefix: Hello

int main()

Finally, we register our components and start the server.

int main(int argc, char* argv[]) {
const auto component_list =
.Append<samples::http_cache::HttpCachedTranslations>()
.Append<samples::http_cache::GreetUser>()
.Append<server::handlers::TestsControl>()
return utils::DaemonMain(argc, argv, component_list);
}

Build and Run

To build the sample, execute the following build steps at the userver root directory:

mkdir build_release
cd build_release
cmake -DCMAKE_BUILD_TYPE=Release ..
make userver-samples-grpc_service

The sample could be started by running make start-userver-samples-grpc_service. The command would invoke testsuite start target that sets proper paths in the configuration files and starts the service.

To start the service manually run ./samples/grpc_service/userver-samples-grpc_service -c </path/to/static_config.yaml> (do not forget to prepare the configuration files!).

The service is available locally at port 8091 (as per our static_config.yaml).

Full sources

See the full example at: