请教一个 nginx rewrite 指令匹配到的部分消失的问题

2020-09-09 18:11:33 +08:00
 gwy15

我想实现一个 “根据不同的 header 中的 Accept-Language 返回不同的文件” 功能,我写的配置文件如下:

map $http_accept_language $locale {
    default         "en-US";
    ~*en            "en-US";
    ~*zh            "zh-CN";
}

server {
    listen 80;
    server_name _;

    location / {
        rewrite_log on;
        rewrite ^/(.*)$ /prerendered/$locale/$1;
    }

    location /prerendered/en-US {
        root /usr/share/nginx/html;
        # try_files $uri $uri/ $uri.html /prerendered/en-US/index.html =404;
    }

    location /prerendered/zh-CN {
        root /usr/share/nginx/html;
        # try_files $uri $uri/ $uri.html /prerendered/zh-CN/index.html =404;
    }
}

大体思路就是利用 map 定义一个 $locale 变量,然后 rewrite 到对应目录。

文件结构:

/usr/share/nginx/html/prerendered
├── en-US                                content:
│   ├── a.html                           a en-US
│   └── index.html                       index en-US
└── zh-CN
    ├── a.html                           a zh-CN
    └── index.html                       index zh-CN

curl 命令和结果:

$ curl http://127.0.0.1/a.html
a en-US
$ curl http://127.0.0.1/a.html -H 'Accept-Language: en'
index en-US
$ curl http://127.0.0.1/a.html -H 'Accept-Language:zh'
index zh-CN

从第二个命令开始,路径就被错误地重定向到了 /prerendered/en-US/,后面的 a.html 消失了。

nginx 对第二个命令的日志:

2020/09/09 09:45:16 [notice] 29#29: *4 "^/(.*)$" matches "/a.html", client: 172.17.0.1, server: _, request: "GET /a.html HTTP/1.1", host: "127.0.0.1"
2020/09/09 09:45:16 [notice] 29#29: *4 rewritten data: "/prerendered/en-US/", args: "", client: 172.17.0.1, server: _, request: "GET /a.html HTTP/1.1", host: "127.0.0.1"

可以看到,这个 rewrite 规则是命中了的,但是替换的时候后面的 $1 却没有替换上;而且只有 Accept-Language 包含 zh/en 才会出现,如果是 空 或者es(默认 map 到 en ),a.html 还是正常替换的。

求助各位这是哪里出现的问题?

958 次点击
所在节点    问与答
6 条回复
superrichman
2020-09-09 18:29:19 +08:00
location 又没有定义$1 的值,后面那节就是空的,重定向没毛病
gwy15
2020-09-09 18:43:09 +08:00
@superrichman 谢谢回答,但是好像不是这样的 XD

在 stackoverflow 上有大佬解释了下,大概是;
rewrite 部分匹配到的组,在计算 replacement 时由于懒计算会再进行 map 部分的匹配,如果正则匹配成功 $1 就会被重置,因此 $1 会变为空串。这里可以用命名组解决:

rewrite ^/(?<myuri>.*)$ /prerendered/$locale/$myuri last;
ysc3839
2020-09-09 18:44:39 +08:00
直接用 try_files 是没问题:
```
location / {
try_files /prerendered/$locale$uri =404;
}
```

对于“匹配任意 URL 然后进行 rewrite”的情况,使用 try_files 的话性能更好。rewrite 应该在需要匹配复杂规则时才使用。
gwy15
2020-09-09 18:46:54 +08:00
@ysc3839 这里是我简化过的可重现 demo,实际上 rewrite 是在一个 if 块里面,匹配到爬虫 UA 才会 rewrite 。谢谢你。
ysc3839
2020-09-09 18:52:41 +08:00
再者,就算要用 rewrite 实现“匹配任意 URL”,直接用
rewrite ^ /prerendered/$locale$uri;
即可

因为你本来就可以通过 $uri 这个变量访问到 URL,完全没必要再匹配一次。
“^” 是匹配开头,单独一个“^”也就等价于“匹配任意字符串”。
superrichman
2020-09-09 18:53:11 +08:00
@gwy15 是我理解错了,学到新东西了谢谢

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/705570

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX