StackLeader Blog

Post Image
Jun 24, 2016

Implementing gRPC Service and Client


Overview

This post expands on our previous blog on generating gRPC code with Maven, and explains how to use the generated api jar to implement a gRPC service and client. This example uses the StackLeader dockerized karaf container to illustrate running the service and connecting to the service with the client. This is largely done to simplify the process of running the example.

Source Code

The source code for this example is available on github.

Project Structure

Tree View

Implementing the Service

To implement the service, we need to extend the generated GreeterGrpc.AbstractGreeter class.

GreeterService.java
package com.stackleader.training.grpc.helloworld.server.internal;

import com.stackleader.training.grpc.helloworld.api.GreeterGrpc;
import com.stackleader.training.grpc.helloworld.api.HelloReply;
import com.stackleader.training.grpc.helloworld.api.HelloRequest;
import io.grpc.BindableService;
import io.grpc.stub.StreamObserver;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(immediate = true)
public class GreeterService extends GreeterGrpc.AbstractGreeter implements BindableService{

    private static final Logger LOG = LoggerFactory.getLogger(GreeterService.class);

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        LOG.info("sayHello endpoint received request from " + request.getName());
        HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + request.getName()).build();
        responseObserver.onNext(reply);
        responseObserver.onCompleted();
    }

}

Implementing the Server and Registering our Service Implementation

To expose our service implementation, a gRPC server endpoint needs to be constructed. In this case, we have registered our service implementation as an implementation of the gRPC BindableService interface, and as a result we can easily inject this implementation into our Server class for registration. Additionally, we have registered our HelloWorldServer class as implementing an empty GrpcServer interface in order to provide a lifecycle hook for our service client (i.e. we want our client implementation to wait for the server before it tries to make a connection).

HelloWorldServer.java
package com.stackleader.training.grpc.helloworld.server.internal;

import com.stackleader.training.grpc.helloworld.server.GrpcServer;
import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.netty.NettyServerBuilder;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(immediate = true)
public class HelloWorldServer implements GrpcServer {

    private static final Logger LOG = LoggerFactory.getLogger(HelloWorldServer.class);
    private final int port = 5000;
    private Server server;
    private BindableService greeterService;

    @Activate
    public void activate() {
        start();
    }

    private void start() {
        try {
            server = NettyServerBuilder
                    .forPort(port)
                    .addService(greeterService)
                    .build()
                    .start();
            LOG.info("Server started, listening on {}", port);
            CompletableFuture.runAsync(() -> {
                try {
                    server.awaitTermination();
                } catch (InterruptedException ex) {
                    LOG.error(ex.getMessage(), ex);
                }
            });
        } catch (IOException ex) {
            LOG.error(ex.getMessage(), ex);
        }
    }

    @Reference
    public void setGreeterService(BindableService greeterService) {
        this.greeterService = greeterService;
    }

    @Deactivate
    public void deactivate() {
        if (server != null) {
            server.shutdown();
        }
    }

}

Implementing the Client

In order to implement a gRPC client, we need to create an instance of one of the stub classes implementing the GreeterBlockingClient class or the GreeterFutureClient class. For this example, we have chosen to instantiate a GreeterGrpc.GreeterBlockingStub instance. As mentioned above, in order to ensure the gRPC server has been started, we inject the GrpcServer service into our HelloWorldClient component. After the gRPC Server is injected we make a connection to the server and invoke the sayHello method.

HelloWorldClient.java

package com.stackleader.training.grpc.helloworld.client;

import com.stackleader.training.grpc.helloworld.api.GreeterGrpc;
import com.stackleader.training.grpc.helloworld.api.HelloReply;
import com.stackleader.training.grpc.helloworld.api.HelloRequest;
import com.stackleader.training.grpc.helloworld.server.GrpcServer;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import io.grpc.okhttp.OkHttpChannelBuilder;
import java.util.concurrent.TimeUnit;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(immediate = true)
public class HelloWorldClient {

    private static final Logger LOG = LoggerFactory.getLogger(HelloWorldClient.class);

    private final String host = "localhost";
    private final int port = 5000;
    private ManagedChannel channel;
    private GreeterGrpc.GreeterBlockingStub blockingStub;

    @Activate
    public void activate() {
        channel = OkHttpChannelBuilder.forAddress(host, port)
                .usePlaintext(true)
                .build();
        blockingStub = GreeterGrpc.newBlockingStub(channel);
        try {
            greet("world");
            shutdown();
        } catch (InterruptedException ex) {
            LOG.error(ex.getMessage(), ex);
        }
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    /**
     * Say hello to server.
     */
    public void greet(String name) {
        LOG.info("Will try to greet " + name + " ...");
        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
        HelloReply response;
        try {
            response = blockingStub.sayHello(request);
        } catch (StatusRuntimeException e) {
            LOG.warn("RPC failed: {0}", e.getStatus());
            return;
        }
        LOG.info("Greeting: " + response.getMessage());
    }

    @Reference
    public void setGrpcServer(GrpcServer grpcServer) {
        //ensures the server has started before we attempt to connect
    }
}

Running the example in the StackLeader Karaf container

For convenience, we have made available an open source Docker container that will be used in the Karaf blog posts.

stackleader-karaf-container

Step 1

Clone and build the project from github (i.e. mvn clean install from the root of the project).

Step 2

Pull down the latest stackleader karaf-container

docker pull stackleader/karaf:latest
Step 3

Start the karaf shell

docker run -it -v ~/.m2:/opt/karaf/.m2 stackleader/karaf shell
Install the Demo Feature File

From the karaf shell run the following commands.

repo-add mvn:com.stackleader/com.stackleader.training.grpc.helloworld.feature/0.0.1/xml/features
feature:install com.stackleader.training.grpc.helloworld.feature
Step 4

View the logs to confirm the client was able to communicate to the server. From the karaf shell run the log:tail command.

log:tail

Expected Results

Log Output