本文所描述的内容,代理地址基于http://127.0.0.1:8080,location块基于/api。

proxy_pass

pass_pass [address];

参数 说明
address 需要代理的目标地址

如果代理目标地址是/结尾,那么/api则不会添加到目标地址的路径中。例如proxy_pass http://127.0.0.1:8080/;,那么访问proxy_address/api/xxx/xxx,nginx实际请求的是http://127.0.0.1:8080/xxx/xxx。

如果代理目标地址不是/结尾,那么/api会一并添加到路径中。例如proxy_pass http://127.0.0.1:8080;,那么访问proxy_address/api/xxx/xxx,nginx实际请求的是http://127.0.0.1:8080/api/xxx/xxx。

rewrite

rewrite regex replacement [flag];

参数 说明
regex 正则表达式,用于匹配请求的 URL
replacement 匹配后替换的 URL
flag

可选参数,控制重写行

last:本次重写完成后,重新匹配新的 URL(类似 return)

break:停止匹配后续 rewrite 规则,但继续执行后续 location 配置

redirect:返回 302 临时重定向

permanent:返回 301 永久重定向

rewrite ^/api/(.*) /$1 break;匹配/api/后面的路径,同时将新的路径/$1按照proxy_pass的规则添加到proxy_pass地址的后面,当rewrite没有匹配到时,执行默认的proxy_pass规则。

rewrite若要与proxy_pass合用,rewrite必须使用break标志,否则rewrite会覆盖proxy_pass的配置,rewrite应位于proxy_pass之前。

另外补充rewrite,proxy_pass和俩兄弟try_files,return的优先级问题,优先级从高到低依次是:rewrite,return,try_files,proxy_pass。

实际问题

在开发过程中,遇到了一个存在websocket服务的情况,而且websocket地址位于http://127.0.0.1:8080/,也就是服务的根路径上。最开始使用了如下的nginx配置,精准匹配一个子路径/api,这个路径代理http://127.0.0.1/,而其他请求则通过模糊匹配/api去处理,虽然可行,但是这样的配置很麻烦,要写两个location块用于处理一个反代业务。

    # Websocket Proxy
    location = /api {
        # Set up for WebSocket connections
        proxy_pass http://127.0.0.1:8080/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeout settings
        proxy_connect_timeout 3s;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;

        keepalive_timeout 65;
        keepalive_requests 100000;

        # Disable buffering for real-time data
        proxy_buffering off;

        # Performance tuning
        proxy_set_header Connection keep-alive;
        tcp_nodelay on;
    }

    location /api {
        # Set up for WebSocket connections
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        rewrite ^/api/(.*)$ /$1 break;
    }

改进以后的配置如下。

    # Websocket Proxy
    location /api {
        rewrite ^/api/(.*)$ /$1 break;
        # Set up for WebSocket connections
        proxy_pass http://127.0.0.1:8080/;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeout settings
        proxy_connect_timeout 3s;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;

        keepalive_timeout 65;
        keepalive_requests 100000;

        # Disable buffering for real-time data
        proxy_buffering off;

        # Performance tuning
        proxy_set_header Connection keep-alive;
        tcp_nodelay on;
    }

改进后的配置在访问/api时,如果路径前缀不包含/api/,则不会触发rewrite规则,紧接着触发proxy_pass存在后缀/时的规则。如果路径前缀包含/api/,则触发rewrite规则,同时修改后的路径由proxy_pass继续处理。

以上的配置也可以将location /api改写为locaton /api/,或者是将proxy_pass https://127.0.0.1:8090/;改写为proxy_pass https://127.0.0.1:8090;,都是可以的,相应的逻辑按照pass_proxy和rewrite的规则进行解释即可。

只是如果最后改写为location /api和proxy_pass https://127.0.0.1:8090;,那么在访问路径的时候,就必须与代理前的path一致。比如代理前需要通过http://127.0.0.1:8080/访问ws服务,那么也就必须通过proxy_address/api/访问ws服务。