JS `gRPC-Web` 协议转换与 `Proxy` 层设计:浏览器到 gRPC 服务

各位观众老爷,大家好!我是今天的主讲人,很高兴和大家聊聊 gRPC-Web 协议转换与 Proxy 层设计,也就是让你的浏览器能愉快地和 gRPC 服务“眉来眼去”。

我们今天的主题是:浏览器到 gRPC 服务:JS gRPC-Web 协议转换与 Proxy 层设计

好,废话不多说,咱们直接上干货!

一、 为什么我们需要 gRPC-Web?

首先,我们要搞清楚一个问题:为啥浏览器不能直接和 gRPC 服务通信?

答案很简单:浏览器不支持 gRPC 的 HTTP/2 binary 协议。

传统的 gRPC 使用 HTTP/2 作为传输协议,并且数据采用 Protocol Buffers (protobuf) 进行序列化。这对于后端服务之间的高效通信简直是完美CP,但是浏览器表示:“我看不懂,我不玩!”

浏览器主要使用 HTTP/1.1 和 XMLHttpRequest (XHR) 或 Fetch API 进行网络通信,并且更喜欢 JSON 这种“人类友好型”的数据格式。

因此,我们需要一个“翻译”,一个“中间人”,来让浏览器和 gRPC 服务能够互相理解。 这就是 gRPC-Web 的用武之地。

二、 gRPC-Web 是什么?

gRPC-Web 是一种协议和库,它允许 Web 浏览器应用像调用原生 gRPC 服务一样,直接调用 gRPC 服务。

它通过以下方式实现:

  1. 协议转换: gRPC-Web 客户端会将浏览器发送的 HTTP 请求转换为 gRPC 兼容的请求,并发送到 gRPC-Web Proxy。
  2. Proxy: gRPC-Web Proxy 接收到请求后,将其转换为标准的 gRPC 请求,并转发到后端的 gRPC 服务。
  3. 反向转换: gRPC 服务返回的响应,Proxy 会将其转换为 gRPC-Web 客户端可以理解的格式,然后发送回浏览器。

简单来说,gRPC-Web 就像一个“翻译官”,在浏览器和 gRPC 服务之间架起一座桥梁。

三、 核心组件

gRPC-Web 方案中,主要有以下几个核心组件:

  1. gRPC 服务: 这是你的后端服务,使用 gRPC 协议。
  2. gRPC-Web 客户端库: 这是一个 JavaScript 库,用于在浏览器中调用 gRPC 服务。它负责将 JavaScript 对象序列化为 protobuf 格式,并处理与 Proxy 的通信。
  3. gRPC-Web Proxy: 这是一个服务器端组件,负责接收 gRPC-Web 客户端的请求,将其转换为 gRPC 请求,并转发到后端的 gRPC 服务。常见的 Proxy 实现有 Envoy、grpc-web 官方提供的 grpcwebproxy
  4. Protocol Buffers (protobuf): 用于定义 gRPC 服务接口和消息格式。

可以用表格来总结:

组件 作用 技术栈
gRPC 服务 提供后端服务,使用 gRPC 协议 Golang, Java, Python, C++ 等
gRPC-Web 客户端 在浏览器中调用 gRPC 服务,负责协议转换和通信 JavaScript
gRPC-Web Proxy 接收 gRPC-Web 请求,转换为 gRPC 请求,转发到后端服务,并进行反向转换 Envoy, grpcwebproxy (Go)
protobuf 定义 gRPC 服务接口和消息格式,用于代码生成 Protobuf IDL

四、 gRPC-Web Proxy 的选择和设计

gRPC-Web Proxy 是整个方案的核心。选择合适的 Proxy 并进行合理的设计,对于系统的性能、可扩展性和安全性至关重要。

1. 常见的 Proxy 实现

  • Envoy: 一个高性能的开源边缘和服务代理,功能非常强大,支持各种协议和配置,可以作为 gRPC-Web Proxy 使用。优点是性能好,可扩展性强,缺点是配置相对复杂。
  • grpcwebproxy grpc-web 官方提供的 Proxy,用 Go 语言编写,配置简单,易于上手。优点是简单易用,缺点是功能相对有限。

2. Proxy 的设计要点

  • 性能: Proxy 位于浏览器和 gRPC 服务之间,任何性能瓶颈都会影响用户体验。因此,Proxy 必须具有高性能,能够快速处理请求和响应。
  • 可扩展性: 随着业务的发展,请求量会不断增加。Proxy 应该具有良好的可扩展性,能够轻松应对高并发的场景。
  • 安全性: Proxy 应该提供必要的安全措施,例如 TLS 加密、身份验证和授权,以保护数据安全。
  • 监控和日志: Proxy 应该提供完善的监控和日志功能,方便我们了解系统的运行状态,及时发现和解决问题。
  • 配置管理: Proxy 的配置应该易于管理,方便我们进行修改和更新。

3. Proxy 的部署方式

Proxy 可以部署在以下位置:

  • 反向代理服务器后面: 例如 Nginx 或 Apache。这种方式可以利用反向代理服务器的负载均衡、缓存和安全功能。
  • Kubernetes 集群中: 可以使用 Kubernetes 的 Ingress 或 Service Mesh 来管理 Proxy。这种方式可以提高系统的可靠性和可扩展性。
  • 云平台: 可以使用云平台的 API Gateway 或 Load Balancer 来管理 Proxy。这种方式可以简化部署和运维。

五、 代码示例:使用 grpcwebproxy

为了让大家更直观地了解 gRPC-Web 的使用,我们以 grpcwebproxy 为例,演示一个简单的例子。

1. 定义 protobuf 文件

首先,我们需要定义一个 protobuf 文件,描述 gRPC 服务的接口和消息格式。

syntax = "proto3";

package example;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

这个 protobuf 文件定义了一个 Greeter 服务,包含一个 SayHello 方法,接收 HelloRequest 消息,返回 HelloReply 消息。

2. 生成 gRPC 代码

使用 protoc 命令生成 gRPC 代码,包括服务端和客户端代码。

protoc --go_out=. --go_opt=paths=source_relative 
    --go-grpc_out=. --go-grpc_opt=paths=source_relative 
    example.proto

这个命令会生成 example.pb.goexample_grpc.pb.go 两个文件,包含 gRPC 服务的接口和消息类型的定义。

3. 实现 gRPC 服务

使用生成的 gRPC 代码,实现 gRPC 服务。

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "google.golang.org/grpc"
    pb "path/to/your/generated/code" // 替换成你生成的代码路径
)

const (
    port = ":50051"
)

type server struct {
    pb.UnimplementedGreeterServer
}

func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    fmt.Printf("Server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

这个代码实现了一个简单的 gRPC 服务,接收 HelloRequest 消息,返回 HelloReply 消息,其中包含了 "Hello " + name 的问候语。

4. 启动 gRPC 服务

编译并运行 gRPC 服务。

go run main.go

5. 配置 grpcwebproxy

下载并配置 grpcwebproxy

# 下载 grpcwebproxy
go install github.com/grpc-ecosystem/grpc-web/grpcwebproxy@latest

# 运行 grpcwebproxy
grpcwebproxy --backend_addr=localhost:50051 --backend_tls=false --allow_all_origins
  • --backend_addr 指定后端的 gRPC 服务地址。
  • --backend_tls=false 表示后端服务不使用 TLS 加密。
  • --allow_all_origins 允许所有来源的跨域请求。生产环境请务必配置安全的 CORS 策略。

6. 创建 gRPC-Web 客户端

使用 gRPC-Web 客户端库,创建一个 JavaScript 客户端,用于调用 gRPC 服务。

首先,安装 gRPC-Web 客户端库。

npm install google-protobuf grpc-web

然后,创建 JavaScript 代码。

// Import the grpc-web library
const {GreeterClient} = require('./example_grpc_web_pb'); // 替换成你生成的代码路径
const {HelloRequest} = require('./example_pb'); // 替换成你生成的代码路径

// Create a new gRPC-Web client
const client = new GreeterClient('http://localhost:8080'); // grpcwebproxy 默认端口是 8080

// Create a new request
const request = new HelloRequest();
request.setName('World');

// Call the SayHello method
client.sayHello(request, {}, (err, response) => {
  if (err) {
    console.error(err);
  } else {
    console.log(response.getMessage()); // 输出 "Hello World"
  }
});

这个代码创建了一个 GreeterClient 实例,并调用 sayHello 方法,向 gRPC 服务发送 HelloRequest 消息,并打印返回的 HelloReply 消息。

7. 运行 gRPC-Web 客户端

在浏览器中运行 JavaScript 代码,或者使用 Node.js 运行。

node index.js

你应该能在控制台中看到 "Hello World" 的输出。

六、 生产环境的注意事项

在生产环境中部署 gRPC-Web 应用时,需要考虑以下几个方面:

  • CORS: gRPC-Web 应用通常需要跨域访问 gRPC 服务。因此,需要在 Proxy 上配置 CORS 策略,允许来自特定域的请求。
  • TLS: 为了保证数据安全,建议使用 TLS 加密传输数据。需要在 Proxy 上配置 TLS 证书,并启用 HTTPS。
  • 身份验证和授权: 为了保护 gRPC 服务,需要进行身份验证和授权。可以使用 JWT 或 OAuth 2.0 等协议进行身份验证,并使用 RBAC 或 ABAC 等模型进行授权。
  • 监控和日志: 为了了解系统的运行状态,需要收集 Proxy 和 gRPC 服务的监控指标和日志。可以使用 Prometheus、Grafana 和 ELK Stack 等工具进行监控和日志分析。
  • 错误处理: 需要对 gRPC 服务的错误进行处理,并向客户端返回友好的错误信息。

七、 总结

gRPC-Web 是一种让 Web 浏览器应用直接调用 gRPC 服务的有效方案。通过 gRPC-Web Proxy 进行协议转换,我们可以充分利用 gRPC 的高性能和强类型特性,构建高效可靠的 Web 应用。

选择合适的 Proxy 并进行合理的设计,对于系统的性能、可扩展性和安全性至关重要。在生产环境中部署 gRPC-Web 应用时,需要考虑 CORS、TLS、身份验证和授权、监控和日志以及错误处理等问题。

希望今天的讲座能帮助大家更好地理解 gRPC-Web 协议转换与 Proxy 层设计。谢谢大家!

各位还有什么问题吗?欢迎提问!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注