跳到主要内容

grpc-web

gRPC 是一个基于 HTTP/2 和 Protocol Buffers 的高性能 RPC 框架,但浏览器本身不支持它。gRPC-Web 定义了一种浏览器兼容协议,用于通过 HTTP/1.1 或 HTTP/2 发送 gRPC 请求。

grpc-web 插件将 gRPC-Web 请求转换为原生 gRPC 调用,并将其转发到上游 gRPC 服务。


请求处理

grpc-web 插件使用特定的 HTTP 方法、内容类型和 CORS 规则处理客户端请求。

支持的 HTTP 方法

插件支持:

  • POST 用于 gRPC-Web 请求
  • OPTIONS 用于 CORS 预检检查

有关详细信息,请参阅 CORS 支持

支持的内容类型

插件识别以下内容类型:

  • application/grpc-web
  • application/grpc-web-text
  • application/grpc-web+proto
  • application/grpc-web-text+proto

它自动解码二进制或 Base64 文本格式的消息,并将其转换为上游服务器的标准 gRPC。有关详细信息,请参阅协议与 gRPC over HTTP2 的差异

CORS 处理

插件自动处理跨域请求。默认情况下:

  • 允许所有来源 (*)
  • 允许 POST 请求
  • 接受的请求头:content-type, x-grpc-web, x-user-agent
  • 暴露的响应头:grpc-status, grpc-message

示例

以下示例演示了如何配置和使用 grpc-web 插件与 gRPC-Web 客户端。

先决条件

在继续示例之前,请完成以下初步步骤以设置上游服务器和 gRPC-Web 客户端。

启动上游服务器

在 Docker 中启动 grpcbin 服务器作为示例上游:

docker run -d \
--name grpcbin \
-p 9000:9000 \
moul/grpcbin

生成 gRPC-Web 客户端代码

下载 protocol buffer 定义 hello.proto

curl -O https://raw.githubusercontent.com/moul/pb/refs/heads/master/hello/hello.proto

安装 protobufprotoc-gen-grpc-web

hello.proto 生成 gRPC-Web 客户端代码:

protoc \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:. \
hello.proto

你应该在当前目录中看到生成的两个文件:用于 protocol buffers 消息类的 hello_pb.js 和用于 gRPC-Web 客户端存根的 hello_grpc_web_pb.js

创建客户端

创建一个 Node.js 项目并安装所需的依赖项:

npm init -y
npm install xhr2 grpc-web google-protobuf

创建客户端文件:

client.js
const XMLHttpRequest = require('xhr2');
const { HelloServiceClient } = require('./hello_grpc_web_pb');
const { HelloRequest } = require('./hello_pb');

global.XMLHttpRequest = XMLHttpRequest;

function sayHello(){
const client = new HelloServiceClient('http://127.0.0.1:9080/grpc/web', null, {
format: 'text',
});
const req = new HelloRequest();
req.setGreeting('jack');

const call = client.sayHello(req, {}, (err, resp) => {
if (err) {
console.error('grpc error:', err.code, err.message);
} else {
console.log('reply:', resp.getReply());
}
});

call.on('metadata', (metadata) => {
console.log('Response headers:', metadata);
});
}

function lotsOfReplies() {
const client = new HelloServiceClient('http://127.0.0.1:9080/grpc/web', null, {
format: 'text',
});
const req = new HelloRequest();
req.setGreeting('rep');
const stream = client.lotsOfReplies(req, {});

stream.on('metadata', (metadata) => {
console.log('Response headers:', metadata);
});
}

lotsOfReplies()
sayHello()

稍后你可以运行客户端 node client.js,通过网关向 gRPC 服务器发送一元和服务器流式请求。

代理 gRPC-Web(前缀匹配路由)

以下示例演示了如何配置和使用 grpc-web 插件与之前设置的 gRPC-Web 客户端。

创建一个启用 grpc-web 插件的路由,如下所示:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "grpc-web-route",
// Annotate 1
"uri": "/grpc/web/*",
"plugins": {
// Annotate 2
"grpc-web": {}
},
"upstream": {
// Annotate 3
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
// Annotate 4
"192.168.10.103:9000": 1
}
}
}'

❶ 配置 uri 以匹配 client.js 中请求的路由的前缀。

❷ 启用 grpc-web 插件。

❸ 将上游协议设置为 grpc

❹ 替换为你的上游服务器地址。

理解 URI

在 APISIX 3.15.0 之前和 API7 企业版 3.8.21 之前的版本中,路由 URI 必须使用前缀匹配,因为 gRPC-Web 客户端在请求 URI 中包含包名、服务名和方法名。在这些版本中使用绝对 URI 匹配将导致请求无法匹配路由。后续版本支持[绝对 URI 路由](#代理 gRPC-Web(绝对 URI))。

在此示例中,路由 URI 必须配置为 /grpc/web/* 才能正确匹配客户端请求(例如 /grpc/web/hello.HelloService/SayHello)。使用更广泛的前缀(如 /grpc/*)将导致网关无法正确提取完整的服务路径,从而导致错误(例如 unknown service web/hello.HelloService)。

运行客户端以向网关路由发送请求:

node client.js

你应该看到来自上游 gRPC 服务器的回复:

Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
reply: hello jack

代理 gRPC-Web(绝对 URI)

此示例适用于 APISIX 3.15.0 及更高版本,以及 API7 企业版 3.8.21 及更高版本。

当使用绝对 URI 时,网关不会自动剥离 URI 路径前缀。为了将请求正确转发到上游 gRPC 服务器,请使用 proxy-rewrite 插件调整请求路径。

创建一个启用 grpc-web 插件的路由,如下所示:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "grpc-web-route",
// Annotate 1
"uri": "/grpc/web/hello.HelloService/SayHello",
"plugins": {
"grpc-web": {},
"proxy-rewrite": {
// Annotate 2
"uri": "/hello.HelloService/SayHello",
// Annotate 3
"set_ngx_uri": "true"
}
},
"upstream": {
// Annotate 4
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
// Annotate 5
"192.168.10.103:9000": 1
}
}
}'

❶ 配置 uri 使用绝对路径,包括基本前缀和 gRPC 完整服务路径。

❷ 配置 proxy-rewrite 以重写路径并剥离路由前缀。

❸ 将 set_ngx_uri 设置为 true 以将请求的路径更新为 proxy-rewrite 插件中定义的 URI。如果不设置此项,网关将无法将请求正确转发到上游,从而导致错误(例如 unknown service grpc/web/hello.HelloService)。

❹ 将上游协议设置为 grpc

❺ 替换为你的上游服务器地址。

运行客户端以向网关路由发送请求:

node client.js

你应该看到来自上游 gRPC 服务器的回复:

Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
Response headers: {
...
'access-control-allow-origin': '*',
'access-control-expose-headers': 'grpc-message,grpc-status'
}
reply: hello jack