V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
gwy15
V2EX  ›  问与答

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

  •  
  •   gwy15 · 2020-09-09 18:11:33 +08:00 · 1125 次点击
    这是一个创建于 1546 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我想实现一个 “根据不同的 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 还是正常替换的。

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

    6 条回复    2020-09-09 18:53:11 +08:00
    superrichman
        1
    superrichman  
       2020-09-09 18:29:19 +08:00 via iPhone
    location 又没有定义$1 的值,后面那节就是空的,重定向没毛病
    gwy15
        2
    gwy15  
    OP
       2020-09-09 18:43:09 +08:00   ❤️ 1
    @superrichman 谢谢回答,但是好像不是这样的 XD

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

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

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

    因为你本来就可以通过 $uri 这个变量访问到 URL,完全没必要再匹配一次。
    “^” 是匹配开头,单独一个“^”也就等价于“匹配任意字符串”。
    superrichman
        6
    superrichman  
       2020-09-09 18:53:11 +08:00 via iPhone
    @gwy15 是我理解错了,学到新东西了谢谢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1294 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 18:01 · PVG 02:01 · LAX 10:01 · JFK 13:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.