今天在配置 nginx 缓存时,遇到一个棘手的问题,开发反馈需要实现缓存特定路径下的数据,并且要能手动清除缓存。缓存配置和清除的办法在前面已经有介绍,但是给出的路径中间有一位会变动,也就是说需要 location 正则匹配区分。 “众所周知”,location 区块匹配正则表达式时,内部 proxy_pass 如果继续配置使用 request_uri 改写功能(即 http://somewhere/path 形式)会报错不可用,那要怎么解决这个需求呢?事实情况并没有这么简单。

测试使用环境为 nginx-1.15.8 ,近几年的版本应该都一致。

开发的需求

有 uri :

1
https://domain/api/v2/app/.../24/data

需要转到后端服务器为:

1
http://domain/app/.../24/data

其中只需要缓存 .../24/data 内容,但是不能对 .../24.../24/something 做缓存,数字位 “24” 会随着查询数据不同变化。

那么条件限定死了:

  1. 不可能通过数字前面内容来匹配缓存 path
  2. 要匹配可变数字,并且数字后面是固定 /data
  3. proxy_pass需要改写 path ,rewrite 不符合需求。

失败的尝试

可能是之前的知识固化了思维,我以为在 location 后写正则,再去改写 path ,且不说怎么把把变化的数字传递给 upstream_addr,nginx 不是不允许这样做,会直接给出报错吗?

那么如果使用 if 来限定缓存的 path 是不是就可以了,遗憾的是 nginx 也会报错。查阅 nginx.org 文档说明可以发现,proxy_cache 不能用在 if 语句内。

正确的用法

为什么 rewrite 规则可以使用 正则,而且用的很好,nginx 却不给 location 添加支持呢?这时候灵光一闪想到可不可以用 $1 变量来代替可变的数字,试了再说 …

修改配置文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
  ...
location ~ /api/v2/app/.../([0-9]+)/data {
  	proxy_redirect off;
    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_pass               http://domain/app/.../$1/data;

    proxy_buffering          on;# 避免全局配置关闭代理缓存
    proxy_cache              cache_zone1;   # 定义的 cache_zone 名称
    proxy_cache_key          "$host$request_uri";   # 定义缓存存放目录的子目录命名规则
    add_header Cache         "$upstream_cache_status"; # 增加缓存头信息,便于判断是否命中
    proxy_cache_valid        200 304 301 302 2h;
    proxy_cache_valid        404 1m;
    proxy_cache_valid        any 5m;
}
  ...

nginx reload !!

结果竟然没有报错,简单添加一个变量的,就实现了原来觉着不可能实现的需求。

后续测试

测试使用正则表达式匹配,后面改写路径为 “/”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# cat test.conf
server {
    location ~ /.*  {
        proxy_pass http://epurs.com/;
    }
}
# nginx -t
2019/05/09 17:13:30 [emerg] 7685#7685: "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 /etc/nginx/conf.d/test.conf:3
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 /etc/nginx/conf.d/test.conf:3
nginx: configuration file /etc/nginx/nginx.conf test failed

报错。

测试使用正则表达式匹配,使用变量匹配 request_uri,后面改写路径为 “/$1”

1
2
3
4
5
6
7
8
9
# cat test.conf
server {
    location ~ /(.*)  {
        proxy_pass http://epurs.com/$1;
    }
}
# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

可用。

后续多次测试后发现,当使用正则表达式匹配时,使用不存在的变量 $dontexist作为改写路径:

1
2
3
4
# nginx -t
2019/05/09 17:31:09 [emerg] 7787#7787: unknown "dontexist" variable
nginx: [emerg] unknown "dontexist" variable
nginx: configuration file /etc/nginx/nginx.conf test failed

报错。

但是如果变量是数字表示,即一个空变量,测试是可以通过的。

结论

可见,nginx 也不是不能用正则来匹配规则,网上经常提到 location 用法中说到的 正则匹配时,后面不能再用 “http://domain/path” 其实是错误的说法。

真实情况是,如果前面使用了正则表达式匹配出现多种可能路径,那么后面也需要一个对应的变量来保证路径也是可变的。这个变量只要真实存在哪怕为空,只要不是不存在的变量,就可以通过通过 nginx -t 检查。

常用的比如 $1request_uri 使用起来可以搭配正则表达式实现灵活转发。 当然要注意使用正则时,优先级并不高的问题。