Provide and consume gRPC services on Open Liberty

gRPC is an open source remote procedural call (RPC) framework that connects clients and services in language-agnostic way. You can provide and consume gRPC services from your web applications that run on Open Liberty.

What is gRPC?

The gRPC framework was designed to efficiently connect cloud-native applications. It enables services and clients that use different networks, platforms, and coding languages to call and implement functions through a common service definition contract. For example, you can create a gRPC service in Java with clients in Python and Go. Each application must worry only about calling or implementing functions in its own language because gRPC facilitates the transmission of the calls and generates the code in each language.

Like other RPC frameworks, gRPC defines a service contract with explicit methods, parameters, and return types. On the server side, a server implements the service interface. The client is provided with stubs that define the same interfaces that are provided by the server, so the client application can invoke remote service methods as if they were local. gRPC uses HTTP/2 for transport, which supports high-performance bidirectional connections.

gRPC uses protocol buffers as its interface design language (IDL). Protocol buffers allow for simple service definitions and provide a language independent means to serialize structured data in remote procedural calls. They define messages that can be compiled into any supported programming language so that you can transfer data structures between endpoints even when those endpoints are working in different programming languages.

gRPC and Open Liberty

You can build and deploy gRPC-enabled applications on Open Liberty to provide new gRPC services or to consume external gRPC services. To enable support for gRPC, add the gRPC or gRPC Client feature to your server.xml file.

gRPC services

The gRPC feature enables web applications to define and provide gRPC services. It works by scanning web applications for gRPC service implementations, through implementors of the io.grpc.BindableService class. The web application must include the protocol buffer compiler-generated code for the services it intends to provide. The service class must also provide a no-argument constructor. The web application does not need to include any core gRPC libraries; they are provided by the Open Liberty runtime. Any configurations that are specified in the grpc configuration element in your server.xml file or on the services themselves by the @GrpcService annotation are applied to the service when it is started. After a gRPC service is scanned and started, it becomes accessible to remote gRPC clients on the configured HTTP ports.

If you configure gRPC in your server.xml file, you must specify the target attribute for the grpc element. This attribute configures a target filter so the runtime can map the configuration to the applicable gRPC service. Similarly, any serverInterceptor classes that are specified in the serverInterceptors attribute must be configured and packaged such that any targeted gRPC services can load the configured ServerInterceptor class.

You can secure gRPC service connections with transport layer security (TLS). You can configure authorization by specifying the @RolesAllowed, @DenyAll, and @PermitAll annotations on service implementations. For more information about configuring role-based authorization, see Authorization.

gRPC Clients

The gRPC Client feature enables web applications to consume gRPC services. It provides access to a Netty gRPC client and the related libraries. A web application must provide a client implementation and stubs, and can make outbound calls with a io.grpc.ManagedChannel class, without needing to provide the supporting gRPC client libraries.

You can configure gRPC clients by enabling the gRPC Client feature and specifying the grpcClient element in your server.xml file. This configuration requires you to specify the host attribute so Open Liberty can map the configuration to the applicable gRPC client call at run time. You can also optionally configure the path attribute to specify a remote gRPC service path, but this filter is only applicable to the values of the headersToPropagate attribute. Any values that are specified in the clientInterceptors attribute must be configured and packaged such that any referenced gRPC clients can load the configured ClientInterceptor class.

During application startup, Open Liberty scans for any grpcClient element configuration in the server.xml file. When the application code creates a io.grpc.ManagedChannel class, Open Liberty internally creates a wrapped ManagedChannel instance that applies the grpcClient element configuration and returns that wrapped ManagedChannel instance to the client. The client application then uses the ManagedChannel instance to invoke its gRPC stubs. Any Open Liberty-specific data that is needed, such as propagated headers, are passed along.

You can also configure gRPC clients programmatically with the io.grpc.ManagedChannelBuilder API. For further programmatic configuration, applications can access the io.grpc.netty.NettyChannelBuilder class by opting in to third-party classloader visibility.

gRPC client calls can be made with TLS. You can configure TLS either by mapping an sslRef attribute in the grpcClient configuration to an existing SSL configuration ID, or by configuring an outbound TLS filter. You can configure the headersToPropagate attribute to propagate security tokens, for example, by specifying headersToPropagate="authorization" or headersToPropagate="cookie".

Try it out

You can try gRPC with Open Liberty by adding the gRPC feature to your server.xml file and implementing this basic Hello World service.

package com.ibm.ws.grpc;

import com.ibm.ws.grpc.beans.GreetingBean;

import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;
import io.grpc.stub.StreamObserver;

public class HelloWorldService extends GreeterGrpc.GreeterImplBase {

    public HelloWorldService(){}

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

In this example, the application must provide the helloworld protobuf definition along with the protobuf compiler output. You don’t need to provide any other libraries with the application. After the helloworld greeter service is started, it is accessible on the server HTTP endpoints.

For a client example, you can define a basic servlet that uses gRPC by adding the gRPC Client feature to your server.xml file and implementing the following code.

package com.ibm.ws.grpc;

import io.grpc.examples.helloworld.GreeterGrpc;
import io.grpc.examples.helloworld.HelloReply;
import io.grpc.examples.helloworld.HelloRequest;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
...
@WebServlet(name = "grpcClient", urlPatterns = { "/grpcClient" }, loadOnStartup = 1)
public class GrpcClientServlet extends HttpServlet {

        ManagedChannel channel;
        private GreeterGrpc.GreeterBlockingStub greetingService;

        private void startService(String address, int port)
        {
            channel = ManagedChannelBuilder.forAddress(address , port).usePlaintext().build();
            greetingService = GreeterGrpc.newBlockingStub(channel);
        }

        private void stopService()
        {
            channel.shutdownNow();
        }

        @Override
        protected void doGet(HttpServletRequest reqest, HttpServletResponse response)
            throws ServletException, IOException
        {

            // set user, address, port params
        }

        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException
        {

        // grab user, address, port params
        startService(address, port);
        HelloRequest person = HelloRequest.newBuilder().setName(user).build();
        HelloReply greeting = greetingService.sayHello(person);

        // send the greeting in a response
        stopService();
        }
    }
}

Similar to the service example, the application must provide only the helloworld protobuf definition and the protobuf compiler output. All the required gRPC client libraries are provided by the gRPC Client feature.