gRPC - Hello World App with Java



Let us now create a basic "Hello World" like app that will use gRPC along with Java.

.proto file

First let us define the "greeting.proto" file in common_proto_files

syntax = "proto3";
option java_package = "com.tp.greeting";
service Greeter {
   rpc greet (ClientInput) returns (ServerOutput) {}
}
message ClientInput {
   string greeting = 1;
   string name = 2;
}
message ServerOutput {
   string message = 1;
}

Let us now take a closer look at this code block and see the role of each line −

syntax = "proto3";

The "syntax" here represents the version of Protobuf that we are using. So, we are using the latest version 3 and the schema thus can use all the syntax which is valid for version 3.

package tutorial;

The package here is used for conflict resolution if, say, we have multiple classes/members with the same name.

option java_package = "com.tp.greeting";

This argument is specific to Java, i.e., the package where to auto-generate the code from the ".proto" file.

service Greeter {
   rpc greet(ClientInput) returns (ServerOutput) {}
}

This block represents the name of the service "Greeter" and the function name "greet" which can be called. The "greet" function takes in the input of type "ClientInput" and returns the output of type "ServerOutput". Now let us look at these types.

message ClientInput {
   string greeting = 1;
   string name = 2;
}

In the above block, we have defined the ClientInput which contains two attributes, "greeting" and the "name", both of them being strings. The client is supposed to send the object of type "ClientInput" to the server.

message ServerOutput {
   string message = 1;
}

In the above block, we have defined that, given a "ClientInput", the server would return the "ServerOutput" which will contain a single attribute "message". The server is supposed to send the object of type "ServerOutput" to the client.

Note that we already had the Maven setup done to auto-generating our class files as well as our RPC code. So, now we can simply compile our project −

mvn clean install

This should auto-generate the source code required for us to use gRPC. The source code would be placed under −

Protobuf class code: target/generated-sources/protobuf/java/com.tp.greeting
Protobuf gRPC code: target/generated-sources/protobuf/grpc-java/com.tp.greeting

Setting up gRPC server

Now that we have defined the proto file which contains the function definition, let us setup a server which can serve to call these functions.

Let us write our server code to serve the above function and save it in com.tp.grpc.GreetServer.java

Example

package com.tp.grpc;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import com.tp.greeting.GreeterGrpc;
import com.tp.greeting.Greeting.ClientInput;
import com.tp.greeting.Greeting.ServerOutput;

public class GreetServer {
   private static final Logger logger = Logger.getLogger(GreetServer.class.getName());
   private Server server;
   private void start() throws IOException {
      int port = 50051;
      server = ServerBuilder.forPort(port).addService(new GreeterImpl()).build().start();
       
      logger.info("Server started, listening on " + port);
 
      Runtime.getRuntime().addShutdownHook(new Thread() {
         @Override
         public void run() {
            System.err.println("Shutting down gRPC server");
            try {
               server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
               e.printStackTrace(System.err);
            }
         }
      });
   }
   static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
      @Override
      public void greet(ClientInput req, StreamObserver<ServerOutput> responseObserver) {
         logger.info("Got request from client: " + req);
         ServerOutput reply = ServerOutput.newBuilder().setMessage(
            "Server says " + "\"" + req.getGreeting() + " " + req.getName() + "\""
         ).build();
         responseObserver.onNext(reply);
         responseObserver.onCompleted();
      }
   }
   public static void main(String[] args) throws IOException, InterruptedException {
      final GreetServer greetServer = new GreetServer();
      greetServer.start();
      greetServer.server.awaitTermination();
   }
} 

The above code starts a gRPC server at a specified port and serves the functions and services which we had written in our proto file. Let us walk through the above code −

  • Starting from the main method, we create a gRPC server at a specified port.

  • But before starting the server, we assign the server the service which we want to run, i.e., in our case, the Greeter service.

  • For this purpose, we need to pass the service instance to the server, so we go ahead and create a service instance, i.e., in our case, the GreeterImpl.

  • The service instance needs to provide an implementation of the method/function which is present in the ".proto" file, i.e., in our case,the greet method.

  • The method expects an object of type as defined in the ".proto" file, i.e., in our case, the ClientInput.

  • The method works on the above input, does the computation, and then is supposed to return the mentioned output in the ".proto" file, i.e., in our case, the ServerOutput.

  • Finally, we also have a shutdown hook to ensure clean shutting down of the server when we are done executing our code.

Setting up gRPC client

Now that we have written the code for the server, let us setup a client which can call these functions.

Let us write our client code to call the above function and save it in com.tp.grpc.GreetClient.java

Example

package com.tp.grpc;

import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.tp.greeting.GreeterGrpc;
import com.tp.greeting.Greeting.ServerOutput;
import com.tp.greeting.Greeting.ClientInput;

public class GreetClient {
   private static final Logger logger = Logger.getLogger(GreetClient.class.getName());
   private final GreeterGrpc.GreeterBlockingStub blockingStub;
   
   public GreetClient(Channel channel) {
      blockingStub = GreeterGrpc.newBlockingStub(channel);
   }
   public void makeGreeting(String greeting, String username) {
      logger.info("Sending greeting to server: " + greeting + " for name: " + username);
      ClientInput request = ClientInput.newBuilder().setName(username).setGreeting(greeting).build();
      logger.info("Sending to server: " + request);
      ServerOutput response;
      try {
         response = blockingStub.greet(request);
      } catch (StatusRuntimeException e) {
         logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
         return;
      }
      logger.info("Got following from the server: " + response.getMessage());
   }
   
   public static void main(String[] args) throws Exception {
      String greeting = args[0];
      String username = args[1];
      String serverAddress = "localhost:50051";
	   ManagedChannel channel = ManagedChannelBuilder.forTarget(serverAddress)
         .usePlaintext()
         .build();
      try {
         GreetClient client = new GreetClient(channel);
         client.makeGreeting(greeting, username);
      } finally {
         channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
      }
   }
}

The above code starts a gRPC server at a specified port and serves the functions and services which we had written in our proto file. Let us walk through the above code −

  • Starting from main method, we accept two arguments, i.e., name and the greeting.

  • We setup a Channel for gRPC communication to our server.

  • Next, we create a blocking stub using the channel we created. This is where we have the service "Greeter" whose functions we plan to call.A stub is nothing but a wrapper that hides the remote call complexity from the caller.

  • Then, we simply create the expected input defined in the ".proto" file,i.e., in our case, the ClientInput.

  • We ultimately make the call and await result from the server.

  • Finally, we close the channel to avoid any resource leak.

So, that is our client code.

Client Server Call

Now, that we have defined our proto file, written our server, and the client code, let us proceed and execute this code and see things in actions.

For running the code, fire up two shells. Start the server on the first shell by executing the following command −

java -cp .\target\grpc-point-1.0.jar com.tp.grpc.GreetServer

We would see the following output −

Output

Jul 03, 2021 1:04:23 PM com.tp.grpc.GreetServer start
INFO: Server started, listening on 50051

The above output implies that the server has started.

Now, let us start the client.

java -cp .\target\grpc-point-1.0.jar com.tp.grpc.GreetClient 
Hello Jane

We would see the following output −

Output

Jul 03, 2021 1:05:59 PM com.tp.grpc.GreetClient greet
INFO: Sending greeting to server: Hello for name: Jane
Jul 03, 2021 1:05:59 PM com.tp.grpc.GreetClient greet
INFO: Sending to server: greeting: "Hello"
name: "Jane"

Jul 03, 2021 1:06:00 PM com.tp.grpc.GreetClient greet
INFO: Got following from the server: Server says "Hello Jane"

And now, if we open the server logs, we will get to see the following −

Jul 03, 2021 1:04:23 PM com.tp.grpc.GreetServer start
INFO: Server started, listening on 50051
Jul 03, 2021 1:06:00 PM com.tp.grpc.GreetServer$GreeterImpl 
greet
INFO: Got request from client: greeting: "Hello"
name: "Jane"

So, the client was able to call the server as expected and the server responded with greeting the client back.

Advertisements