Jay Ehsaniara
Posted on August 30, 2024
This project demonstrates how to set up a simple gRPC communication between a web client and server with an Envoy proxy.
I’m using the gRPC-Web library for this purpose. gRPC-Web is a JavaScript client library that allows web applications to interact with 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:
- The client sends requests to the server using gRPC-Web, which typically communicates via 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 a gRPC-Web format, typically using base64 encoding for the binary gRPC payload. The client sends this request over HTTP/1.1 or HTTP/2.
- Envoy (or another reverse proxy like Nginx) acts as an intermediary 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 for communication.
The gRPC server processes the incoming gRPC request, performs the necessary business logic, and generates a response (in this example it’s the Go written application). The response is encoded in the standard gRPC format and sent back to Envoy.
Envoy receives the gRPC response, encodes it in the gRPC-Web format (typically using base64), and sends it back to the gRPC-Web client over HTTP/1.1 or HTTP/2. Any metadata included in the gRPC response, such as status codes, is translated appropriately.
The gRPC-Web client decodes the response and converts it into a usable format within the web application. The web application processes the response, updating the UI or handling errors as needed.
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.
Here is a GitHub project for it:
https://github.com/ehsaniara/gRPC-web-example
proto file
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 side (go)
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
...
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
...
JS Web Client (webpack)
// 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);
});
Here is a GitHub project related to this project
Posted on August 30, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.