In the last decade, REST APIs have become the standard option for applications and systems to communicate with each other. However, in 2015, Google introduced the concept of a modern open source remote procedure call, gRPC, which provides the same functionality of REST APIs with faster, lighter, and more flexible services and communications. As a result, Google Remote Procedure Call, or gRPC, is now supported by most programming languages. In this lesson, you'll learn about gRPC in the context of Go.
In the simplest terms, gRPC is a modern, open source remote procedure call (RPC) framework that can run anywhere. An RPC is a function within an application that can be executed remotely by another application. RPCs are particularly common in distributed systems where one computer wants to invoke methods or functions hosted on another machine in the distributed system.
gRPC is similar to a REST API in that you are exposing services hosted on a server to clients. There are a few differences between gRPC and REST:
GET
, POST
, PUT
, and DELETE
), whereas gRPC uses a level of abstraction through Protobuf to allow more flexible communication.To illustrate all these concepts in more detail, in this lesson you will implement a chat service using gRPC. Specifically, you will create a gRPC server and a gRPC client and use Protobuf to create a chat service between both parties.
This lesson requires additional services that are not included in a typical Go installation. You will need to download and install the following applications as well as create a user directory:
Git is a distributed version control tool. You can verify if Git is installed on your computer by entering the following command at a command-line prompt:
git --version
If Git is installed on your system, this command will return the current version. If your system does not already have Git available, download and installation instructions are available through Git's downloads page at https://git-scm.com/downloads
.
You will also need to install gRPC on your system. You can install the gRPC services from a command line using the following command:
go get -u google.golang.org/grpc
If successful, when you run this command you won't see anything at the command prompt. Figure 24.1 shows that the current Microsoft Windows system has Git installed and also shows installing gRPC. There isn't much to see when installing these services.
Protobuf is an open source cross-platform mechanism developed by Google that is used to serialize structured data. You can think of Protobuf as a lighter and faster version of XML. As with XML, you can decide the structure of your data (using proto files; more on this later), then use Protobuf to read and write structured data into a variety of streams. Typically, Protobuf is used by programs to store data or to communicate with other applications.
Install the protocol compiler plug-in for Go (protoc-gen-go
) using the following command:
go get github.com/golang/protobuf/protoc-gen-go
This Go get
instruction will download and save the files to a new directory on your computer. If you are using macOS or Linux, you can enter the following command to set a path so that the plug-in can find the code files:
export PATH="$PATH:$(go env GOPATH)/bin"
This setting should already be in place if you are using Windows and you installed Go using the provided MSI file.
Next, check to make sure that the files are installed. You can do so by navigating to that directory and looking in the pkg/mod
subdirectory. The default directories are:
C:Users\%
USERNAME
%Go
Users/<
username
>/go
Install the protocol buffer compiler (protoc), version 3 or later:
https://github.com/protocolbuffers/protobuf
. You'll find Terminal commands for macOS and Linux on this page as well.https://github.com/protocolbuffers/protobuf/releases
. Look for a file whose name ends in win32
or win64
and download the version for your OS. (Most Windows users should choose win64
.) For example, as of this writing, the file was named protoc-3.19.4-win64.zip
.After downloading the file, open the compressed folder and copy the bin
and include
subdirectories to your user
/Go
folder. This will add protoc to your Go installation. You'll see an existing bin
subdirectory in Go; paste the new version to the same location. Select Merge, if necessary, to merge the folders.
For this lesson, all program files should be saved to the usr/Go/src
location created by downloading the files.
/usr/local/go/src
.C:Users\%
USERNAME
%Gosrc
.Verify this folder is on your computer. Also check that the bin
subdirectory includes two files:
protoc.exe
protoc-gen-go.exe
Once you have everything set up and you've confirmed your user directories are created and contain the appropriate files, then you are ready to continue. In the first step, you will create a simple server using the net
package that will listen for TCP connections on port 10000. This is the most basic version of a server.
Create a new file named server.go
and save it to your src
folder. Add the code in Listing 24.1 to the new file.
At this point the code does not do much. It listens on the local network address using the net.Listen
function. The first input of net.Listen
is the network (tcp
in this case), and the second parameter is the port or address, in this case 10000
. Note that the network must be one of the following values:
"tcp"
"tcp4"
"tcp6"
"unix"
"unixpacket"
The program should run without error and open a server connection. It will print the value for the listener similar to the following, but nothing else will happen:
C:UsersMRBRADLEYLgosrc>go run server.go
&{0xc00014ea00 {<nil> 0}}
Now you will modify the server as a gRPC server using the grpc
package you downloaded earlier. This is shown in Listing 24.2.
The server.go
program shown in Listing 24.2 has now been updated with a few changes. First, you add the grpc
package to the import
statements. You also add some output text that will indicate you are making a connection to the server. Finally, you create the gRPC server using grpc.NewServer
and register the endpoints you want to expose before serving this over the existing TCP connection, using the listener
you created earlier.
At this point, if you save and run the program, you should see the print output to confirm the connection:
Our first gRPC server
So far, our server is still not useful. You need to expose some services that clients can use to communicate with the server. As we mentioned earlier, gRPC uses the Protocol Buffers (Protobuf) data format to allow applications to communicate with each other. In the previous step, you created the gRPC server application. In this step, you will create a Protobuf file that you'll use to define how other applications can communicate with the server you just created.
Create a file named chat.proto
(proto
is the extension for Protobuf files) and include the code shown in Listing 24.3. Save this file in the same folder as your server file from Listing 24.2.
The proto file exposes the services that your gRPC will provide. First, you define the syntax used in the file. In our example, you are using proto3:
syntax = "proto3";
You then define the name of the package that you want to create. In our example, the package name is chat
:
package chat;
Next, you identify the location where the new service will be hosted—in this case, a subdirectory named chat
, which you'll create momentarily. Finally, you define a message type named Message
and a service
called ChatService
. This service calls rpc
of SayHello
, which takes as input a Message
and returns the message.
You then add a subdirectory named chat
to the current directory (where the code files are) and then, from the command prompt, run the following command:
protoc --go_out=plugins=grpc:chat chat.proto
After you execute this command, you should see a file named chat.pb.go
in the chat
subdirectory. There will be no output to the command window itself.
Now that you have the necessary files for your chat service, you need to instruct the gRPC server to expose that service. Update your server program to include the new code shown in Listing 24.4.
The new code creates a chat server and exposes the service to the gRPC server. The RegisterChatServiceServer
function references a function in the autogenerated chat.pb.go
file you created using protoc in the previous step. The fact that protoc automatically generates the required functions is an advantage to using this tool.
If you try to run this program, you will receive feedback messages that chat
is undefined:
# command-line-arguments
.server.go:22:10: undefined: chat.Server
Let's create chat
next.
In this step, you will implement the SayHello
method that you defined in the proto3 file in Listing 24.3. This method will accept messages from clients.
In the chat
subdirectory, create a file named chat.go
with the code in Listing 24.5.
This code performs several steps. First, you create a chat
package that you can reference from other programs. You then import the log
and context
packages. You use log
to log incoming messages from the clients. You include the package context
because the SayHello
function will take as input a Context
type and a Message
type, which are provided in this package.
You then create a struct type, Server
, with no fields. This Server
type represents the receiver argument for the SayHello
method. At runtime, that will be the gRPC server.
The SayHello
method takes as input a Message
type and returns a Message
type as well. It also returns an error
type in case of an error. Within the SayHello
method, you log the message that you received (in
is the variable of type Message
and in.Body
is the text).
Each time you receive a message, ideally you will want to send a message back to the client. In this example, you return a new message variable with the text "Hello from the Server!"
and you return the error as nil
.
Essentially, this code will send the message "Hello from the Server!"
back to the client each time the server receives a message. You could implement further services by adding their definitions in the proto file and then implementing them in the chat.go
function using the same pattern.
With a server built, now you need to build the client. In this step, you'll create a client that can communicate with the server you created earlier. The code for client.go
is in Listing 24.6.
The code in client.go
performs a number of tasks. It starts by creating a client connection variable called conn
:
var conn *grpc.ClientConn
The variable is then used to create a connection to the local address on port 10000 by calling the grpc.Dial
method:
conn, err := grpc.Dial(":10000", grpc.WithInsecure())
Error checking verifies that the connection is created and that an error is not returned. If an error is returned (err
is not equal to nil
), then the error is logged using the log.lFatalf
function. You then provide the code to make sure you close the connection. The closing is deferred so that the closing won't happen until you are done using your connection.
With the connection established, you next create a client from the chat service. The NewChatServiceClient
function is autogenerated from the proto file. You pass it your connection, conn
. This establishes your client as c
.
You send a message from this client (c
) to the server using SayHello
. The message you are sending says, "Hello from the Client!"
. In calling SayHello
, you are also passing context.Background()
for the first argument as a context
. Background()
means Background returns a non-nil
, empty Context
. It is never canceled, has no values, and has no deadline. It is typically used by the main
function, initialization, and tests, and as the top-level Context
for incoming requests.
Another context that can be used is context.Package()
. The package context
defines the Context
type, which carries deadlines, cancelation signals, and other request-scoped values across API boundaries and between processes. Incoming requests to a server should create a Context
, and outgoing calls to servers should accept a Context
.
After sending the "Hello from the Client!"
message with SayHello
, you receive the response in the variable response
and an error code. You then print this response from the server.
On one terminal, run the server and look for it to display the message so that you know it is running. Then run the client in a separate Terminal window to see the applications communicate with each other. You should see results similar to Figure 24.2.
In this lesson, we presented an example of how to use gRPC to have a server and client share messages. As you saw, gRPC is lightweight and more flexible than REST services. gRPC is supported by most programming languages, including Go. With the code from this lesson, you now have the foundation to build your own servers and clients that can interact.
The following exercises are provided to allow you to experiment with the tools and concepts presented in this lesson. For the exercises, write a program that meets the specified requirements and verify that the program runs as expected. The exercises are:
Using the code that you developed in this lesson, create a chat assistant where a client can send keyword requests and the server provides an appropriate response. For example:
weather
, the server will respond with a weather-related message.market
, the server will respond with a message related to the stock market.As a first attempt, focus on a single function and do that well. You can build in additional functions later.
Update your solution from Exercise 24.1. Using the REST API lesson, add a functionality so that when the client asks about the weather, the server will query the weather API found at www.weatherapi.com
.
Update your solution from Exercise 24.1 by adding real stock quotes. Use publicly available APIs to access individual stock prices. When the client sends an index as a message, the server should return the price.