Overview:
In this article, I would like to show you a gRPC Web Example for a browser application to talk to a backend gRPC server. We had already discussed enough of various gRPC API types in this blog. You can take a look at them if you are new.
- Protobuf / Protocol Buffers – A Simple Introduction
- gRPC Unary API In Java – Easy Steps
- gRPC Server Streaming API In Java
gRPC Web:
As we might be already aware, gRPC is a RPC framework implementation from google for client-server application development. gRPC uses HTTP2 as network protocol and protocol-buffers to define the API and data models for the application interaction. Inter-Microservices communication using gRPC is very easy to implement. But what about browsers? Can they talk to the backend server using the proto files?
Yes, using gRPC Web, we could use the same proto file for the browser applications to interact with backend gRPC server to develop a full stack gRPC application.
Sample Application:
To keep things simple, I would like to re-use an existing application, we had developed earlier for the backend gRPC application. This is a simple a calculator-service which exposes a couple of simple APIs.
- findSquare – a unary API to find the square for the given number.
- findFactors – a server-streaming API which is capable of sending multiple responses for a single request. That is, If I send 12, it would respond with all the factors 2, 3, 4, 6 which can divide the given number w/o any remainder.
message Input {
int32 number = 1;
}
message Output {
int64 result = 1;
}
service CalculatorService {
// unary
rpc findSquare(Input) returns (Output);
// server stream
rpc findFactors(Input) returns (stream Output);
}
Our backend server is already developed which exposes the API. Now our browser/client-side application has to interact with this gRPC server to send the request and get the response. That is what we are going to discuss here.
Browsers HTTP2 Support:
Before we continue, we need to discuss the current limitations of browsers. Browsers do support HTTP2 to get the static files like images, javascript, css etc. But for any XMLHttpRequest/Ajax calls, browsers still use HTTP/1.1. It is a limitation of browsers as of now. Because of this limitation, browser / client side javascript can not directly talk to the backend gRPC server as we normally talk to the backend REST API. So, we need to work around this limitation and use envoy as a proxy. Envoy’s role here is to get the HTTP/1.1 requests from the browsers and convert to appropriate gRPC requests to the backend servers and respond to the client.
I am skipping the envoy configuration in this article. But it is available in GitHub. You can find the link at the end of this article.
gRPC Web – Client Side Application:
- Generate gRPC Client:
Let’s use the above proto file to develop our client side application. Our client should be able to send a request and receive the response. The response could be a simple request-response or streaming-response. In order to auto generate gRPC-client code, we need to have following binaries in our PATH variable. So download them and rename them with the following names and keep them in your local machine. Ensure that these binaries path are included in the system PATH variable.
To generate the client code, run the following command.
protoc --js_out=import_style=commonjs:output
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:output
calculator.proto
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:output
- this option creates the gRPC client for browsers to send gRPC requests and receive the response.
- import_style=commonjs is used so that we could use require(….) to access the gRPC client in our browser javascript file.
- mode=grpcwebtext indicates that payload request and response would be in a base64 encoded text format. (Remember that we still use HTTP/1.1)
- :output is a directory where to save the auto generated files. It could be anything.
- This option would create this file – calculator_grpc_web_pb.js
--js_out=import_style=commonjs:output
- This option creates the JS models for the Request and Response. (as per our proto files, Input and Output objects).
- This would create calculator_pb.js
- Create Frontend HTML:
I am going to create a simple HTML using bootstrap to call the findSquare and findFactors APIs as shown here. The STOP button is to stop the streaming API when you do not want to receive any more responses for the request.
- Create package.json
We need to bring grpc-web and google-protobuf modules to develop the frontend application. So you need to have npm installed in your machine. Create a project which contains package.json with the following dependencies.
{
"name": "calculator-client",
"version": "1.0.0",
"description": "",
"main": "client.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"google-protobuf": "^3.19.4",
"grpc-web": "^1.3.1"
}
}
- Create client.js
Now we could develop our application by importing the above auto generated js files. Create client.js file with the following content.
const {Input, Output} = require('./calculator/calculator_pb.js');
const {CalculatorServiceClient} = require('./calculator/calculator_grpc_web_pb.js');
// here 8080 is envoy port
// envoy will forward to grpc server and respond to client
var client = new CalculatorServiceClient('http://localhost:8080');
gRPC Web Unary:
We have everything ready to call our backend gRPC server. In this case, when we click on the ‘Find‘ button for the Square Input, we would make the unary call.
const squareInput = document.getElementById('square-input');
const squareFind = document.getElementById('square-button');
squareFind.addEventListener('click', () => {
var input = new Input();
input.setNumber(squareInput.value);
client.findSquare(input, {}, (err, r) => {
addResponse(r);
});
});
gRPC Web Streaming:
To make a server-streaming request, try as shown here.
const factorInput = document.getElementById('factor-input');
const factorFind = document.getElementById('factor-button');
factorFind.addEventListener('click', () => {
var input = new Input();
input.setNumber(factorInput.value);
var stream = client.findFactors(input, {});
stream.on('data', (r) => {
addResponse(r);
});
stream.on('status', (status) => {
console.log(status.code);
});
stream.on('end', (end) => {
});
document.getElementById('factor-stop').addEventListener('click', () => {
stream.cancel();
});
});
The addResponse method adds the response to the UI as bootstrap alert. Check GitHub for the complete code.
Once the client.js is ready, use module bundler like WebPack/Browserify. For ex: below command will bundle our client.js and their dependencies into bundle.js for HTML to include.
browserify src/client.js -o bundle.js
<script src="./bundle.js"></script>
Demo:
Summary:
We were able to successfully develop a simple gRPC Web application which can talk to the backend gRPC server. We were also able to make both unary and streaming calls. Using the gRPC Web Streaming we could receive updates from our backend server periodically.
The source code is available here.
Learn more on gRPC: