Ditching REST with gRPC-web and Envoy

Sushil Kumar
The Startup
Published in
6 min readOct 21, 2019

--

Ever since gRPC was introduced it became really popular among API developer community. The reason gRPC became so popular was its support for polyglot implementation (server and client can be written in separate languages), its focus on the performance from get go (gRPC is based on HTTP/2) and a great tool set (gRPC use protobuf for message and service descriptions and clients can be generated automatically without writing single line of code).

Till some years back, this goodness was limited to mobiles and servers and was not available to front end developers and they still had to use REST interfaces. This was due to the HTTP/2 limitations in the browsers. For example

  1. A request in browser can’t be forced to be HTTP/2.
  2. HTTP/2 frames are not directly available to the libraries.

In comes grpc-web, an effort by Google and Improbable to implement a gRPC spec for browsers. This spec use HTTP/1.x with a gateway proxy such as Envoy (which transparently translates HTTP/1.x to HTTP/2 that is expected by gRPC server).

gRPC-web only supports Unary and Server-side streaming in grpcwebtext mode (we’ll cover what this means in subsequent sections)

Source : https://grpc.io/blog/state-of-grpc-web/

In this post we will implement a gRPC server in golang and implement the client in JavaScript using gRPC-web. We will see examples of both unary and server-side streaming API calls.

So lets get started.

Table of Content

  1. Prerequisites
  2. Protobuf definitions
  3. Implementing Unary server API
  4. Consuming Unary API
  5. Fixing communication errors with Envoy
  6. Implementing Server-side streaming
  7. Conclusion

Prerequisites

We need to install few tools to successfully plough through this post.

  1. VS Code or code editor of your choice to write our codes.
  2. Golang compiler and tools.
  3. Python3 — We’ll use python’s HTTP server to serve our client.
  4. Node (npm comes pre-bundled with node) and npx tools.
  5. Docker — We’ll be running Envoy proxy as a docker container.
  6. protoc the protobuf compiler.
  7. Golang gRPC package and protobuf protoc plugin.
  8. gRPC-web’s protoc-gen-grpc-webplugin.

Follow the installation instructions for each individual tool. I’m skipping the instructions here for the sake of brevity but do let me know if you face any errors, I’ll be happy to help.

This post is by no means an introduction post for any of the languages or frameworks used here. I’ll be linking introductory material for each of them for you to get up-to speed.

Protobuf definitions

We’ll start by creating the protobuf definition for our service. Create a calculator.proto file and place it in protos folder. For now, we’ll just add Unary service definition and evolve it to add Server-side streaming in the 2nd part.

grpc-web
├── protos
│ └── calculator.proto
├── server
└── client

We have added two messages namely AddRequest that takes up two numbers to add, AddResponse that returned the result of addition and a calculator service with one rpc call Addwhich takes in an AddRequest and returns an AddResponse.

Implementing Unary server API

We’ll compile calculator.proto file to go code with help of protoc and go protobuf plugin.

Create a calculatorpb folder inside the server folder and execute following command.

protoc calculator.proto --go_out=plugins=grpc:../server/calculatorpb/

This will generate a calculator.pb.go file and your directory structure will look like this.

grpc-web
├── protos
│ └── calculator.proto
├── server
│ └── calculatorpb
│ └── calculator.pb.go
└── client

Now initialize a go module by executing following command in the server folder.

go mod init github.com/kaysush/grpc-calculator

This will add a go.mod file to your server folder. Creating a module out of our server folder will make sure we can access the package in calculator.pb.go file in our server.go file which we’ll add in next step.

Lets implement the server now.

grpc-web
├── protos
│ └── calculator.proto
├── server
│ ├── calculatorpb
│ │ └── calculator.pb.go
│ ├── go.mod
│ └── server.go
└── client

Run the server.go

go run server.go

You should see the Starting Calculator server message on your screen.

The gRPC server is now ready to serve the requests on port 50551.

Consuming Unary API

Now that our server is running, lets implement our client using gRPC-web.

Compile the calculator.proto file for JavaScript using proto-gen-grpc-web plugin.

protoc calculator.proto --js_out=import_style=commonjs,binary:../client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:../client

This will generate calculator_pb.js (which has message definitions) and calculator_grpc_web_pb.js (which has our client implementation).

grpc-web
├── protos
│ └── calculator.proto
├── server
│ ├── calculatorpb
│ │ └── calculator.pb.go
│ ├── go.mod
│ └── server.go
└── client
├── calculator_grpc_web_pb.js
└── calculator_pb.js

Add a package.json file to client folder with following content.

{
"name": "grpc-calculator",
"version": "0.1.0",
"description": "gRPC-Web Calculator",
"devDependencies": {
"@grpc/proto-loader": "^0.3.0",
"google-protobuf": "^3.6.1",
"grpc": "^1.15.0",
"grpc-web": "^1.0.0",
"webpack": "^4.16.5",
"webpack-cli": "^3.1.0"
}
}

Add a client.js file which has our client implementation connecting to our gRPC server.

Remember how I told you that you don’t need to write any code to implement the client with gRPC.

Now let us package our JavaScript code into a minified file.

npm install
npx webpack client.js

This should download all of our dependencies mentioned in package.json file and then package client.js into dist/main.js with all the dependencies.

Create an index.html file which reference our dist/main.js file and makes the gRPC call.

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<title>Calculator - gRPC-Web</title>
<script src="dist/main.js"></script>
</head>
<body>
</body>
</html>

Your directory structure should look like this now.

grpc-web
├── protos
│ └── calculator.proto
├── server
│ ├── calculatorpb
│ │ └── calculator.pb.go
│ ├── go.mod
│ └── server.go
└── client
├── dist
│ └── main.js
├── calculator_grpc_web_pb.js
├── calculator_pb.js
├── client.js
├── package.json
└── index.html

From client directory start a python server to start serving our files.

python3 -m http.server 8081 &

Now go to http://localhost:8081 and open the console.

You should see error in the console. You’ll get one or all of following errors.

  1. Cross Origin Request Blocked
  2. ERR_CONNECTION_REFUSED

There are two issues with our current server and client implementation.

  1. Our server does not allow CORS requests and
  2. Even if it did support CORS, gRPC-web do not support HTTP/2 hence we need a gateway proxy like Envoy to translate our HTTP/1.x requests originating from gRPC-web for our gRPC server.

Fixing communication errors with Envoy

As discussed in previous section, we need to put Envoy between our client and server for transparent translation between HTTP/1.x and HTTP/2.

We’ll be using docker to run our envoy proxy. We’ll be using standard envoy.yaml from gRPC-web hello world demo (of course with ports changed as per our implementation)

Create the docker file.

FROM envoyproxy/envoy:latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml

Create a docker image.

docker build -t my-envoy:1.0 .

Now run the envoy docker container.

docker run -d -p 8080:8080 -p 9901:9901 --network=host my-envoy:1.0

Once envoy is running we need to change port in our client.js. Now instead of connecting to gRPC server directly on 50551 we’ll be connecting to envoy on 8080.

Re-package the client.js again.

npx webpack client.js

Re-load the browser at http://localhost:8081 and open the console.

You should now see your output.

Wohoo! Success.

Implementing Server-side streaming

Edit the calculator.proto file and add a server-side streaming rpc call to generate Fibonacci numbers.

protoc calculator.proto --go_out=plugins=grpc:../server/calculatorpb/Re-compile our proto files for both go and js.protoc calculator.proto --js_out=import_style=commonjs,binary:../client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:../client

Add server implementation in server.go.

Add the streaming call in our client.js.

Re package the client.

npx webpack client.js

Re-load the browser and you should see the fibonacci numbers streaming to your client.

Wohoo! Streaming Success

Conclusion

gRPC-web is a really mature spec for connecting to exiting gRPC APIs directly from browsers without any REST interface in between. With microservices increasingly using gRPC , gRPC-web fits really nicely into a REST free API world.

In case you find any error in my code or have any question in general, feel free to drop a comment.

Till then Happy Coding! :)

--

--

Sushil Kumar
The Startup

A polyglot developer with a knack for Distributed systems, Cloud and automation.