前几天我们新开发了一个服务,需要将之前的几个接口转发到新服务的新接口上,很自然的我们只需要做一次统一转发处理接口。
我们是基于 Nginx+Lua+Openresty 构建的统一接口网关,所以处理这个问题非常简便,只需要在nginx conf中针对相应接口做一次转发即可,这里使用到 location URL 规则匹配和 proxy_pass 。
但是当我设置 location 匹配规则之后(因为有多个接口,所以匹配规则是前缀匹配),因为在转发之前,我们还有一个逻辑是处理请求的,所以调用了 lua 插件,但是就因为 lua 插件里面对于 proxy_pass 的处理,导致不能转发完整 URL 以及 URL 后面的 query 参数。
举个例子:
location /old_service/v1/old_api/ { rewrite_by_lua_file “rule.lua”; proxy_pass http://new_server/new_service/v1/module/; }
接口: /old_service/v1/old_api/get_info->/new_service/v1/module/get_info; /old_service/v1/old_api/query_info->/new_service/v1/module/query_info;
有问题的时候: new_service 接收到的接口是:/new_service/v1/module 报404-找不到相应的接口。
问题就出在:rewrite_by_lua_file “rule.lua”;
搜索查找到这篇文章,知道了其中的缘由了。
来自 openresty 讨论区:记使用proxy_pass时遇到的一个关于路径中传参的一个有趣问题
以下是讨论详细内容:
Tom: hi,章老师,同学们,大家好
最近在用proxy_pass时,遇到一个比较有趣的事。
1.1
location /proxy/lua {
set_by_lua_file $proxy_server /code/lua/server.lua;
proxy_pass $proxy_server;
}
/code/lua/server.lua文件内容:
local url = “tom.test.web.com/proxy/lua” return url
然后发起请求:http://localhost/proxy/lua?name=tom&age=20 并在远程机器上监控Access日志, 接收到的请求为:“GET /proxy/lua HTTP/1.0”,参数没有传过来
然后把lua文件修改为: 1.2.
local url = “tom.test.web.com” return url
重新发起请求:http://localhost/proxy/lua?name=tom&age=20 并在远程机器上监控Access日志, 接收到的请求为:“GET /proxy/lua?name=tom&age=20 HTTP/1.0”,参数传递过来了。
不解,遂继续以下试验,不用lua:
2.
2.1
location /proxy/lua {
proxy_pass tom.test.web.com;
}
发起请求:http://localhost/proxy/lua?name=tom&age=20 远程机器上查看Access日志:“GET /proxy/lua?name=tom&age=20 HTTP/1.0”,参数传递过来了。
2.2
location /proxy/lua {
proxy_pass tom.test.web.com/proxy/lua;
}
发起请求:http://localhost/proxy/lua?name=tom&age=20 监控日志:“GET /proxy/lua?name=tom&age=20 HTTP/1.0” 参数传递过来了。
到这里就开始感到很奇怪了,同一个URL,通过lua传递时,发现参数没有传递过来,而直接写在proxy_pass后面的参数居然传递过来了。
然后觉得可能是变量问题,继续用Nginx变量重新进行试验:
3.1
location /proxy/lua {
set $proxy_server "tom.test.web.com/proxy/lua";
proxy_pass $proxy_server;
}
发起请求:http://localhost/proxy/lua?name=tom&age=20 监控远程机器日志:“GET /proxy/lua HTTP/1.0” 参数没有传过来
3.2
location /proxy/lua {
set $proxy_server "tom.test.web.com";
proxy_pass $proxy_server;
}
发起请求:http://localhost/proxy/lua?name=tom&age=20 监控远程机器日志:“GET /proxy/lua?name=tom&age=20 HTTP/1.0” 参数传过来了
由以上试验得出,通过变量设置proxy_pass的URL时,如果URL后面除了域名还有路径,则参数传递不过去, 故又进行了如下试验: 加参数进行试验: 4. 4.1.
location /proxy/lua {
set $proxy_server "tom.test.web.com/proxy/lua?name=tom&age=20";
proxy_pass $proxy_server;
}
发起请求:http://localhost/proxy/lua?name=tom&age=20 监控远程机器日志:“GET /proxy/lua?name=tom&age=20 HTTP/1.0”
4.2.
location /proxy/lua {
set $proxy_server "tom.test.web.com?name=tom&age=20";
proxy_pass $proxy_server;
}
发起请求:http://localhost/proxy/lua?name=tom&age=20 监控远程机器日志:“GET /proxy/lua?name=tom&age=20 HTTP/1.0” 4.3
location /proxy/lua {
proxy_pass tom.test.web.com?name=tom&age=20;
}
发起请求:http://localhost/proxy/lua?name=tom&age=20 监控远程机器日志:请求未过来,页面400 Bad Request
总结下上面的试验得到的结论如下: 1.通过变量设置proxy_pass的URL时,如果URL后面除了域名还有路径("/“也是路径),则参数传递不过去,如果想把参数能传递过去,必须显示接收参数并往后面传递;而不通过变量设置的proxy_pass则没有这个问题,见试验3。 2.直接设置URL时,域名后面不能直接跟“?”,会报400错误
疑惑如下:
1.为什么通过变量设置proxy_pass的URL,且后面跟有路径时,参数会传递不过去?而直接设置proxy_pass,参数就能传递过去?这块章老师或其他同学能给详细解释下么?(见2.2和3.1) 2.直接设置proxy_pass 的URL时,域名后面直接跟“?”,为什么报400错误,而通过变量设置proxy_pass的URL,域名后面直接跟“?”就不会有这个错误?(见4.2和4.3)
麻烦了解的同学详细解释下。
刘永明:
proxy_pass是会把$request_uri补上的;4.3 的写法本身是无法通过-t检查的吧 ? 你做了那么多测试,干脆也把post做下吧?
Tom:
正常通过检查的,我用的版本号:openresty/1.5.11.1 嗯,但是通过变量设置proxy_pass的URL时,却没有把传过来的参数补上,这块和非变量设置的URL有差异,我查阅了下Nginx的源码,没有发现这块有啥区别,想咨询下深入研究源码的同学,帮解答一下.
Zexuan Luo:
关于第一个问题: 这部分代码逻辑,位于 http/modules/ngx_http_proxy_module.c#ngx_http_proxy_create_request
/* the request line */
b->last = ngx_copy(b->last, method.data, method.len);
*b->last++ = ' ';
u->uri.data = b->last;
// proxy_lengths 这个属性,只会在 proxy_pass 中使用变量的情况下才会设置。
// 参见同一文件下的 ngx_http_proxy_pass和 ngx_http_proxy_eval 函数。
// 注意这里的 uri 指端口到参数中的一段,也就是请求首行 /test?blahblah 这一部分
if (plcf->proxy_lengths && ctx->vars.uri.len) {
b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
} else if (unparsed_uri) {
// 这部分处理 HTTP 0.9 的
b->last = ngx_copy(b->last, r->unparsed_uri.data, r->unparsed_uri.len);
} else {
// 如果 proxy_pass 用的是字面量,会连同参数一起拷贝进来
if (r->valid_location) {
b->last = ngx_copy(b->last, ctx->vars.uri.data, ctx->vars.uri.len);
}
if (escape) {
ngx_escape_uri(b->last, r->uri.data + loc_len,
r->uri.len - loc_len, NGX_ESCAPE_URI);
b->last += r->uri.len - loc_len + escape;
} else {
b->last = ngx_copy(b->last, r->uri.data + loc_len,
r->uri.len - loc_len);
}
if (r->args.len > 0) {
*b->last++ = '?';
b->last = ngx_copy(b->last, r->args.data, r->args.len);
}
}
不同版本具体内容会有所出入。不过遇到变量就只拷贝变量的展开后的值,看了下提交信息,这个逻辑自从 Nginx 支持 proxy_pass variable 开始就没变过。 文档里面提到,使用变量意味着不再附带原来 uri,看来连同 args 也不会带上。
SO 上有一个相关的讨论:http://stackoverflow.com/questions/8130692/with-nginx-how-to-forward-query-parameters
关于第二个问题: 再看看回答第一个时贴出的代码,你会发现,所谓的拷贝参数,就是把 ?xxx 这部分内容 copy 到 proxy_pass 字面量上去。 所以配置
location /proxy {
proxy_pass http://localhost:8080/test?a=c;
}
然后请求 curl -i “localhost:8080/proxy?a” 在 access.log 里会看到 /test?a=c?a 这么一个玩意儿。
至于为何 400,也许跟这个有关?
茶歇驿站
一个可以让你停下来看一看,在茶歇之余给你帮助的小站。
这里的内容主要是后端技术,个人管理,团队管理,以及其他个人杂想。