我在用nginx作为反向代理,我有 2 条规则,例如:
location ~ ^/indirect {
rewrite ^/indirect(.*) /foobar$1;
}
location ~ ^/foobar {
set $url http://example.com/something/index.php?var1=hello&access=$scheme://$host$uri;
proxy_pass $url;
}
因此,如您所见,我将$uri
变量作为参数传递给代理页面(该$uri
变量是 nginx 变量,请参阅http core module
请参阅文档。
问题是,如果我访问http://example.com/foobar/hello%20world
,$uri
变量包含 /foobar/hello world
(如您所见,%20
已被替换为其 url 解码值,一个空格)。然后,nginx 在执行 proxy_pass 行之前返回 http 状态代码 400(错误请求)(那里的后端未联系)。
还有可用的变量$request_uri
,它保存客户端发出的原始请求 URI,因此在这种情况下它将保存正确的值以及序列%20
。但我不能使用它,因为如果客户端通过路径/indirect
,$request_uri
将包含/indirect/...
,而我希望access
传递给后端的参数始终是/foobar/...
。
有多个indirect
类似规则(这是针对 DAV/calDAV/cardDAV 服务器的,并且有多个客户端连接到多个路径,所以我需要这些indirect
类似规则),因此在那里执行是不可行的proxy_pass
,并且有客户端直接转到该/foobar
路径。
那么有没有什么办法可以$uri
不经过 url 解码就可以得到它呢?
可能不可接受的事情:
- 发送双重编码的请求,因为请求可能来自我无法控制的客户端
- 在每个间接规则和“直接”规则中多次指定最终 URL,因为这会导致维护问题。
答案1
使用nginx/1.2.1
,我无法重现您的问题%20
,一旦解码为空格,就会导致任何400 Bad Request
在 nginx 中;也许是来自上游?
无论如何,使用通过提供的有限状态自动机实际上并不困难rewrite
指令停止$uri
包含解码的请求,但仍执行请求的各种转换。
这个想法是,当你$uri
就地改变时,它不会被重新解码。而且,如你所知,我们已经在 中有了未解码的$request_uri
。剩下的就是简单地将一个设置为另一个,然后就结束了。
server {
listen 2012;
location /a {
rewrite ^/a(.*) /f$1 last;
}
location /i {
rewrite ^ $request_uri;
rewrite ^/i(.*) /f$1 last;
return 400; #if the second rewrite won't match
}
location /f {
set $url http://127.0.0.1:2016/s?v=h&a=$scheme://$host$uri;
proxy_pass $url;
}
}
server {
listen 2016;
return 200 $request_uri\n;
}
是的,rewrite ^ $request_uri;
上面的部分确实起到了作用:
% echo localhost:2012/{a,i,f}/h%20w | xargs -n1 curl
/s?v=h&a=http://localhost/f/h w
/s?v=h&a=http://localhost/f/h%20w
/s?v=h&a=http://localhost/f/h w
%
(如果您也不想对“直接”内容进行解码,那么最简单的方法可能就是将其也设为“间接”。)
答案2
我发现的唯一方法是使用HttpSetMiscModule像这样:
location ~
^/indirect {
set_escape_uri $key $1;
rewrite ^/indirect(.*) /foobar$key;
}
location ~ ^/foobar {
set_escape_uri $key $uri;
set $url http://example.com/something/index.php?var1=hello&access=$scheme://$host$key;
proxy_pass $url;
}
如果有人知道更好的方法(无需使用外部模块编译 nginx,因为我没有 root 权限)请告诉我!