Hello World gRPC Server

In our last post we discussed the basics of gRPC, gave an example flow, and discussed why you would want to use it over different communication protocols. In this post we will create a Hello World server using gRPC. This will involve the mixed-use of both gRPC and cl-protobufs. The code here can be found in my Hello World gRPC Github repo and specifically in the first commit.

HelloWorld Service

Defining the Protocol

Before we start writing code we must define:

  1. The messages we wish to send from the client to server and back.
  2. The server and method name.

We will start off simple. The request will simply contain a string name and the response will contain a string message. Creating these messages has been discussed before and is not very interesting. The new portion is the server. In Proto parlance a server is a service and that service contains a set of callable methods called RPCs.

Looking at the proto file in our Hello World repo we see:

service HelloWorld {
  rpc SayHello(HelloRequest) returns (HelloReply) {}
}

This says we are creating a server named HelloWorld. It will export one callable method called SayHello, which will accept one (serialized) HelloRequest proto message and respond with one HelloReply proto message.

The Protocol, Macroexpanded

We’ve created our proto file. Next we add it to a lisp library with an ASD file, along with all of the defsystem requirements to process the proto file. We detailed an example in our post Proto Over HTTPS if you need a refresher. Now  we would like to create a server that can be called. To understand how to do this we will go over the Service generated code, so please load your ASD file (or just follow along).

Cl-protobufs expands the proto service into several callable functions in the CL-PROTOBUFS.{FILENAME}-RPC package; for us this is CL-PROTOBUFS.HELLO-RPC. For each RPC it creates two functions:

  1. CL-PROTOBUFS.HELLO-RPC:CALL-SAY-HELLO
    1. Channel Argument
      1. An object defined in gRPC
    2. The REQUEST argument
      1. CL-PROTOBUFS.HELLO-REQUEST message
  2. CL-PROTOBUFS.HELLO-RPC:SAY-HELLO
    1. CL-PROTOBUFS.HELLO-REQUEST message
    2. CALL Call object

The CL-PROTOBUFS.HELLO-RPC:CALL-SAY-HELLO function will let clients call our service. The channel is created with the gRPC library and we will discuss this later. The request message is a HELLO-REQUEST object.

The CL-PROTOBUFS.HELLO-RPC:SAY-HELLO is a generic function. The user will have to implement a method overriding this generic. It takes a  (deserialized) CL-PROTOBUFS.HELLO-REQUEST message and a gRPC call object created by the gRPC library.

gRPC Objects

There are two internal book-keeping objects we need to talk about: the CHANNEL object and the CALL object. 

CHANNEL

The channel is an object created by gRPC over which a user can send messages. There are several options for channels – please see the gRPC documentation. We will see a brief example below when we call our complete server.

Call

The call object contains metadata about a call created by the gRPC server, such as whether the call has been canceled. It is currently unused, but will be more useful in the future. For now it will remain ignored in our server implementation.

Server Implementation

Now the fun part: cl-protobufs created the scaffolding for making a server and gRPC created the scaffolding for hosting a server and servicing calls, but we need to implement our server. All we have to do is implement our RPC stub (SAY-HELLO) and start the server!

Implementing our RPC

Since the cl-protobufs scaffolding creates a generic function we just make a method implementing that generic:

(defmethod hello-rpc:say-hello ((request hello:hello-request) call)
  ;; The RPC contains useful data for more intricate requests.
  (declare (ignore call))
  (hello:make-hello-reply
   :message (concatenate 'string "Hello " (hello:hello-request.name request))))

Notice we don’t have any serialization calls, this is all done by the gRPC/cl-protobufs scaffolding. Instead, we make the protos and implement our logic.

Starting our Server

Starting our server requires:

  1. Calling (grpc:init-grpc)
    1. This is done once to initialize pieces of gRPC.
  2. Calling grpc::run-grpc-proto-server

The grpc::run-grpc-proto-server needs, at a minimum, the host:port and the Service symbol, here cl-protobufs.hello:hello-world. It offers more functionality, allowing for SSH, user defined number of threads, etc. See the gRPC code for details.

Full Example

Now that we have created our server we will show an example of starting the server and calling it. First clone the Hello World Repo. To start the server just load the grpc-server  package defined in that repo and call grpc-server:main. Your server has been started! You must specify the hostname and port, in our example we use 127.0.0.1 and 8080 defined in constants +hostname+ and +port-number+ in server.lisp.

(defun main ()
  ;; Before we use gRPC we need to init-grpc, this sets up
  ;; low-level gRPC internals.
  (grpc:init-grpc)
  ;; This starts the server.
  (grpc::run-grpc-proto-server
   “127.0.0.1:8080”
   cl-protobufs.hello:hello-world))

Next we need to call the server. In a REPL, load the grpc-server example. This is just to get the #:grpc and #:cl-protobufs.hello-rpc and #:cl-protobufs.hello packages. Next call:

(grpc:with-insecure-channel
             (channel "127.0.0.1:8080")
           (cl-protobufs.hello-rpc:call-say-hello
            channel
            (cl-protobufs.hello:make-hello-request :name "Bob")))

We will discuss the grpc:with-insecure-channel function in the next post. Just note that here we specify a binding argument – channel – and the host and port. Finally, we call our server using cl-protobufs.hello-rpc:call-say-hello over the channel with a protocol buffer message. This returns:

#S(CL-PROTOBUFS.HELLO:HELLO-REPLY
   :%%SKIPPED-BYTES NIL
   :%%BYTES NIL
   :%%IS-SET #*
   :%-MESSAGE #S(CL-PROTOBUFS.IMPLEMENTATION::ONEOF
                 :VALUE "Hello Bob"
                 :SET-FIELD 0))

Wrapping Up

Here we have seen the creation of a full gRPC server, and we called it with a gRPC client seeing it receive and respond with Protocol Buffer messages over a wire. This requires none of the serialization and deserialization scaffolding we created in our previous HTTP servers as we get it for free! In future posts we will discuss client calls as well as bidirectional streaming.


Thanks goes to Ron Gut and Carl Gay for making edits and comments.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s