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-webapplication/grpc-web-textapplication/grpc-web+protoapplication/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
安装 protobuf 和 protoc-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
创建客户端文件:
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。
❹ 替换为你的上游服务器地址。
在 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