Getting started with gRPC – part II: the code

In a previous post I wrote a summary of the things I learned about gRPC. Here I will talk about the prototype app I wrote to test gRPC and gRPC-Web.

About my prototype

As I mentioned before, I wrote a prototype app that tests if the string that the user typed in the browser is a palindrome. I started my experiment based on the example from the official gRPC guide for Python and what that service does is to say hello: when the client runs, it sends a Hello request to the server who in turns responds back with a Hello. In that example, both the server and the client are Python implementations but I wanted to see if that would work in the browser so I started changing it to be a Python server and a JS client. Spoiler alert: it does! 😎

gRPC service definition

So, starting with the .proto file, in my prototype I have the Greeter service that implements these two RPC methods: sayHello and isPalindromic. Each of these methods will send a gRPC message as request and expects a gRPC message as response.

// The greeting service definition.
service Greeter {
  // Sends a greeting - RPC method
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Checks if entry is palindromic- another RPC method
  rpc IsPalindromic (HelloRequest) returns (CheckReply) {}
}

The messages structure is defined below.

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

// The response message containing check result
message CheckReply {
  bool result = 1;
}

And that’s basically it for the .proto file.

Generate gRPC classes with protoc

Next, I used protoc to compile the protobufs to be used by my Python server and my JS client – and here is one other advantage of using proto buffer and gRPC: you write your definition once and use a command line tool to generate the classes for multiple languages. If your target language is supported by gRPC, one command  will take care of that for you, no need to rewrite that library yourself.

So, for example, this is what the command looks like for python (side note here: you must have gRPC and gRPC tools installed in your system to use these tools.):

python -m grpc_tools.protoc -I. --python_out=./server --grpc_python_out=./server helloworld.proto

The parameters will vary slightly depending on the target language you need. The output of this command will be two pb files for each target language you run the compiler: one file with the gRPC classes for the service and one for the messages. These files should not be edited. We will not look into these now but you can refer to my repo or the documentation to see what these files look like.

Using the generated classes in the implementation

Python server

Now the fun part: we can use these generated protbufs classes, the pb files, in our implementation. This is what part of my Python server code looks like – the full server code is here:

import grpc
import helloworld_pb2
import helloworld_pb2_grpc
...
class Greeter(helloworld_pb2_grpc.GreeterServicer):

    def SayHello(self, request, context):
        return helloworld_pb2.HelloReply(message='Hello, %s!' % request.name)

    def IsPalindromic(self, request, context):
        name = request.name.lower()
        return helloworld_pb2.CheckReply(result=all(name[i] == name[-(i + 1)] for i in [0, len(name) -1]))

Things to note here:

– import gRPC and the pb files

– Create the service class and its methods

In the other part of this script I create and run the server method. Here I added a couple of things for convenience since I am running this all on Docker: I wanted to be able to run separate containers for the client and for the server, so I added the IP check. You will notice that the grpc server runs on a “insecure port”. That’s for development mode only and all the examples I found use this insecure port so for production some more research needs to be done.

That’s basically it for the server part.

JS client

My client is in JavaScript so I had to compile the gRPC classes again (using protoc), this time for JS so I can import them in my code. Here’s the code:

const {HelloRequest, CheckReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');

const client = new GreeterClient('http://localhost:8080');
const request = new HelloRequest();
const check = new CheckReply();

let nameField = document.querySelector('#name-field');
let helloName = document.querySelector('.hello-name');
let nameTyped = document.querySelector('.name');
let checkResult = document.querySelector('.check');
const sendButton = document.querySelector('.send-button');

function sendName() {
  request.setName(nameField.value);
  check.setResult(nameField.value);
  nameTyped.textContent = nameField.value;
  nameField.value = '';
  client.sayHello(request, {}, (err, response) => {
    console.log(`From gRPC: ${response.getMessage()}`);
    helloName.textContent = response.getMessage();
  });
  client.isPalindromic(request, {}, (err, response) => {
    console.log(`From gRPC - is Palindromic?: ${response.getResult()}`);
    checkResult.textContent = response.getResult() ? ' is a palindrome.' : ' is NOT a palindrome.'
  });
}

sendButton.addEventListener('click', sendName);

I’m using vanilla JS and this is a very simple code – the index.html file can be found here (also very simple). I imported the pb files, created an instance of the client and of the request and I also added an input field and buttons with event listeners attached so I can trigger the request and output the response in the browser (and some good ol’ console logs for debugging ^^’ ).

This is what it looks like in the browser:

⭐ ⭐ ⭐ This app is on Docker so it’s super easy to run and test locally: just clone my repo and run docker-compose up -d on the project folder (full instructions here).

Notes on gRPC-Web

  • When generating the gRPC classes for JS with the compiler command line tool you can choose between CommonJS or Closure (default). There’s no support for ES6-style imports yet so I used webpack to resolve the imports at compile time. Browserify and Closure compiler should work also.
  • If you check the repo closely you will see that I’m using a proxy (Envoy). If the client tries to access the server directly you will get a ‘net::ERR_INVALID_HTTP_RESPONSE’ error in the browser. It turns out that one of Envoy’s functions is to also handle the HTTP requests translating the client calls into gRPC calls and sending them to the server.

Conclusion

This was a really interesting project to work on. It required a bit of a mind shift from using REST to gRPC: you don’t think about manipulating the resources anymore but think about results that need to be accomplished instead.

It was quite simple to get started: install the dependencies, write the service definition (the proto file) and off you go to write the implementation. The the command line tool will generate classes for any language you would like to implement on. Personally I think this alone is a great advantage as it doesn’t force whole teams to use the same language; in a world of microservices, each team can choose the tools and languages they prefer and generate the classes for the language they will use.

I’m curious to see if gRPC will become more popular in the years to come.

The post Getting started with gRPC – part II: the code was originally published at flaviabastos.ca