gRPC between Web and Server.
This is a simple gRPC communication between a web client and server with an Envoy proxy.
I’m using the gRPC-Web library for this. gRPC-Web is a JavaScript client library that allows web applications to talk to gRPC services. Since browsers don’t support HTTP/2 or the binary protocol used by standard gRPC, gRPC-Web provides a way to bridge the gap by using HTTP/1.1 or HTTP/2 and encoding gRPC messages in a way that browsers can handle. Here’s how gRPC-Web works:
1- The client sends requests to the server using gRPC-Web, which typically uses HTTP/1.1 or HTTP/2. Metadata (like headers) can be attached to the request, such as for authentication (e.g., JWT tokens).
The request is encoded in gRPC-Web format, typically using base64 encoding for the binary gRPC payload. The client sends this request over HTTP/1.1 or HTTP/2.
2- Envoy (or another reverse proxy like Nginx) sits between the gRPC-Web client and the gRPC server. Envoy receives the gRPC-Web request, decodes the gRPC-Web payload, and forwards it as a standard gRPC request to the gRPC server using HTTP/2.
The gRPC server processes the request as if it were a native gRPC request, using HTTP/2.
3- The gRPC server processes the incoming gRPC request, does the business logic, and generates a response (in this case the Go written application). The response is encoded in the standard gRPC format and sent back to Envoy.
4- Envoy receives the gRPC response, encodes it in gRPC-Web format (typically base64), and sends it back to the gRPC-Web client over HTTP/1.1 or HTTP/2. Any metadata in the gRPC response, such as status codes, is translated.
5- The gRPC-Web client decodes the response and uses the web application.
NOTE: client-side and bi-directional streaming is not currently supported (see streaming roadmap)
Advantages of gRPC-Web
- Browser Compatibility: Allows modern web applications to interact with gRPC services without needing native support for HTTP/2 and binary protocols.
- Efficiency: Leverages the performance and efficiency of gRPC while adapting it for the web.
Demo Quick Start
docker compose up -d
then open browser http://localhost:8081 (note: this demo uses 8081 and 8080 ports)
Project Structure
- client/: Contains the gRPC-Web client code.
- server/: Contains the gRPC server code written in Go.
- envoy/: envoy configuration file.
Prerequisites
proto
syntax = "proto3";
package helloworld;
option go_package = "./proto"; // Add this line
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Server
package main
import (
"context"
"google.golang.org/grpc/reflection"
"log"
"net"
pb "github.com/ehsaniara/gRPC-web-example/proto"
"google.golang.org/grpc"
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
// Register reflection service on gRPC server.
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
log.Println("Server is running on port 50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Envoy config
Envoy has built-in support for grpc_web, here is a code snip of the Envoy proxy config file.
...
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
...
Web Client
// Import the generated gRPC-Web client stubs and message classes
import {GreeterClient} from './generated/helloworld_grpc_web_pb';
import {HelloRequest} from './generated/helloworld_pb';
// Create an instance of the Greeter client
const client = new GreeterClient('http://localhost:8080');
// Function to send a greeting request
function sayHello(name) {
// Create a new request
const request = new HelloRequest();
request.setName(name);
// Call the sayHello method on the Greeter client
client.sayHello(request, {}, (err, response) => {
if (err) {
console.error('Error:', err.message);
document.getElementById('output').textContent = 'Error: ' + err.message;
} else {
console.log('Greeting:', response.getMessage());
document.getElementById('output').textContent = 'Greeting: ' + response.getMessage();
}
});
}
// Example usage: sending a request when the page loads
document.addEventListener('DOMContentLoaded', () => {
const name = 'World';
sayHello(name);
});
run generate.sh to generate *_pb.js
files, you need to have grpc-web
and protoc-gen-js
(its already generated helloworld_grpc_web_pb.js
and helloworld_pb.js
):
$ npm i grpc-web
$ npm install -g protoc-gen-js
Here is a GitHub project related to this project