使用 nginx 重定向捕获的正则表达式组

Redirecting captured regex group using nginx

使用以下 nginx 位置指令

  location ~* (.*)(\/graphql)$ {
    proxy_pass http://my-backend:80/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
  }

我希望像 https://example.com/anything/graphql 这样的 URL 被重定向到 http://my-backend:80/ 但事实并非如此,因为每当我尝试访问 [=19] 时 nginx 都会给我一个 404 错误=] 以 /graphql 结尾。错误将是

[error] 31#31: *1 no resolver defined to resolve my-backend, client: 172.18.0.1, server: localhost, request: "GET /anything/graphql HTTP/2.0", host: "localhost"

这是一个已知的 nginx 限制,您不能在正则表达式位置内为 proxy_pass 指定 URI。如果你想做类似

的事情
proxy_pass http://my-backend/graphql;

您遇到以下 nginx 错误:

nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in ...

你有两个选择。第一个是使用变量将您想要的任何内容作为 URI 传递给上游,可以是您选择的方式,或者如果它是常量字符串,则明确指定您的端点,如

set $endpoint graphql;
proxy_pass http://my_backend/$endpoint;

但是这种方法有一个您已经面临的缺点 - 如果您的上游是通过主机名而不是 IP 地址指定的,则需要定义 resolver。也就是说,

proxy_pass http://localhost/$endpoint;

配置行需要解析器,而

proxy_pass http://127.0.0.1/$endpoint;

不需要它。 $endpoint 变量仅用于 URI 部分并不重要。如果您想知道 resolver 的需要是什么,让我引用博客 post 中最相关的部分 mentioned:

Linux, POSIX and the like offer only one way to get an IP from a name: gethostbyname. If you take time to read the man page (always a safe thing to do... ;)) you'll realise there is a lot to do to resolve a name: open files, ask NIS or YP what they think about it, ask a DNS server (may be in a few different ways). And all this is synchronous. Now that you are used to the nginx way, you know how bad this is and you don't want to go down the ugly synchronous way. So Igor, faithful to himself, reimplemented a DNS lookup (with an in-memory cache, mind you) just to avoid calling this ugly blocking gethostbyname... And that's why we have this extra resolver directive. Yes, coding the fastest web server comes at a price...

当没有其他方法只能使用 proxy_pass 的变量时,使用 public DNS 就像 8.8.8.8 是一种选择,但是应该设置你自己的本地 DNS性能更高(或者您将与该 8.8.8.8 主机进行持续的流量交换)。然而,另一种选择。您可以将 location 块中的 URI 重写为所需的 URI,并使用 proxy_pass 而不使用任何其他变量:

location ~ /graphql$ {
    rewrite ^ /graphql break;
    proxy_pass http://my-backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

在大多数情况下,这是一种更方便的方法,因为使用这样的配置您根本不需要任何指定的 resolver

如您所见,我在这里没有使用任何捕获组,因为使用它们来解决您的特定情况没有任何实际意义。给定的正则表达式与您的正则表达式完全相同(同时性能稍高)。然而,有些情况下你真的需要使用它们,如果真正需要使用捕获组的人会读到这篇文章,他必须被警告做像

这样的直接方法
location ~ (/graphql)$ {
    rewrite ^  break;
   ...

行不通。原因是每当正则表达式模式匹配操作取代时,编号的捕获都会被重新评估,而 rewrite 指令正是执行此类操作的指令之一(使 </code> 成为空字符串)。解决方案是使用 <a href="https://www.regular-expressions.info/named.html" rel="nofollow noreferrer">named capture groups</a> 代替:</p> <pre><code>location ~ (?<url>/graphql)$ { rewrite ^ $url break; ...

或者可以重用正则表达式模式(我认为性能会稍差):

location ~ /graphql$ {
    rewrite (/graphql)$  break;
    ...