![根据“内容类型”的 nginx 请求路由将 grpc 与 REST API 请求分离](https://linux22.com/image/791986/%E6%A0%B9%E6%8D%AE%E2%80%9C%E5%86%85%E5%AE%B9%E7%B1%BB%E5%9E%8B%E2%80%9D%E7%9A%84%20nginx%20%E8%AF%B7%E6%B1%82%E8%B7%AF%E7%94%B1%E5%B0%86%20grpc%20%E4%B8%8E%20REST%20API%20%E8%AF%B7%E6%B1%82%E5%88%86%E7%A6%BB.png)
我有一个使用两个级联 nginx 实例的现有设置:
- 外层 nginx 只是进行 TLS 终止并转发到内层 nginx
- 内部 nginx 将请求路由到不同的应用程序(REST API)
到目前为止,我们只使用 HTML 页面和 REST API。外部 nginx 的路由非常简单,它主要提供静态 HTML,其他流量转发到内部 nginx。
现在我们还想支持 grpc API。grpc 的问题在于请求的路径由 grpc 接口协议定义,例如“acme.myapi/MyMethod”。外nginx 不应依赖于该接口定义,因为当添加新的 grpc 接口时,外部 nginx 不需要更改,并且 REST API 和 grpc 之间的 url 路径甚至可以重叠,例如两者都可以以“/acme/”开头。此外,我们无法控制 proto 包名称的名称,因此也无法控制 url 路径。
grpc 路由内nginx 使用:authority
header 来路由到 grpc 应用程序。添加新应用程序时,配置会动态更新。
所有 grpc 调用都应重定向到内部 nginx 的特定端点(grpc://127.0.0.1:50051),REST API 调用应使用另一个端点(https://127.0.0.1:4443/) 内部 nginx 实例。
从 nginx 配置的角度来看,最简单的方法是对外部 nginx 使用两个不同的端点(不同的端口)。不幸的是,在防火墙中打开另一个端口的组织工作量非常大,并且不被接受,所以我需要对所有协议使用相同的端点。
这是我目前使用的配置,它可以解决上面描述的问题:
location ~ ^acme\. { # "acme" is the grpc packagename
grpc_pass grpc://127.0.0.1:50051;
grpc_set_header "Host" $host;
grpc_read_timeout 1200s;
grpc_send_timeout 1200s;
client_body_timeout 1200s;
client_max_body_size 0;
}
location / {
proxy_pass https://127.0.0.1:4443/;
[...] # setting lots of other options
}
这是我想要使用的配置(简化到最低限度),但根据如果是邪恶的…这不能使用:
location / {
if ($http_content_type ~ ^application/grpc)
{
grpc_pass grpc://127.0.0.1:50051;
grpc_set_header "Host" $host;
grpc_read_timeout 1200s;
grpc_send_timeout 1200s;
client_body_timeout 1200s;
client_max_body_size 0;
}
# normally I would use 'else' here, but this is not supported
if ($http_content_type !~ ^application/grpc)
{
# also set other REST API specific config options here, left out for brevity
proxy_pass https://127.0.0.1:4443/;
}
我从 nginx 收到以下错误:
nginx: [emerg] "grpc_set_header" directive is not allowed here in /etc/nginx/conf.d/my.conf.locations:80
最后,我需要能够将其用作$http_content_type
选择器来代替(或另外)使用 url 路径。
有没有什么好的解决方案可以满足这个要求?
答案1
我认为我找到了一个很好的解决方案,并且if
只允许使用 for rewrite
which。我需要将任何条件映射到 URL 路径,因此我只能使用location
语句来匹配它。在我的例子中,我将content-type ~ ^application/grpc
标头映射到位置/grpc
,并在location
部分中删除/grpc
前缀。
if ($http_content_type ~ ^application/grpc) {
rewrite "(.*)" "/grpc$1" break; # add "/grpc" to url
}
location /grpc/ {
internal; # not reachable from outside, just for the rewritten location
rewrite "^/grpc(.*)" "$1" break; # remove "/grpc" from url
grpc_pass grpc://127.0.0.1:4080;
#grpc_set_header ":authority" $host; # unfortunately this doesn't work
grpc_set_header "Host" $host; # workaround because ":authority" can't be set
grpc_read_timeout 3153600000s;
grpc_send_timeout 3153600000s;
client_body_timeout 3153600000s;
client_max_body_size 0;
}
REST API部分location
不受影响。
第一次测试有效,但如果我遗漏了任何副作用,我需要深入研究。