前段时间接触到OpenResty,其中ngx_lua模块使得能够让lua代码运行于nginx中,处理请求,发送响应,就像用Java、PHP做web开发一样,正如Tomcat/Jetty是运行Java Servlet的容器,phpfpm是运行PHP代码的容器,ngx_lua模块使得nginx能够作为运行lua代码的容器,好强大有木有!
不过本文不打算用lua写处理请求,返回响应的web应用,而是想用ngx_lua模块提供的功能,做一些轻量级的应用——当nginx用作fastcgi代理服务器时,修改GET方法和POST方法的请求参数。该用途基于两个场景:
某app开发工程师粗心大意,某个接口用GET方法传过来的userId比应当传的userId小了10000,例如userId本来应该传10005的,却传的是5。app已发布,要再次发布新版本,由于apple审核也需几天,来不及。
又是某app开发工程师,某个接口用POST方法传过来的参数password,本来应该经过md5加密的(按照接口文档的约定),却没有,导致没法登录。
后端工程是表示不想修改后台代码了,想玩一玩ngx_lua模块,在请求参数传给后端代码之前,把相关参数做修改。
本实验的环境为:win7+openresty1.11.2.2+php7,php由fastcgi方式运行,采用CI框架。
先来看看nginx向phpfpm如何通过fastcgi协议传递环境变量的。
nginx配置文件fastcgi_params中的跟本实验相关的关键代码:
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_URI $request_uri;
第一行表示把nginx的内部变量$query_string传递给fastcgi的QUERY_STRING环境变量,第二行表示把nginx的内部变量$request_uri传递给fastcgi的REQUEST_URI环境变量。
nginx配置文件vhost.conf中的关键代码:
location / {
try_files $uri $uri/ /index.php;
}
location ~* \.php(.*)$ {
fastcgi_pass 127.0.0.1:9000;
include fastcgi__params;
}
第一个location表示如果所请求的$uri不存在,则重写到/index.php(正好匹配到第二个location),第二个location则把请求按fastcgi协议传递给监听于本地9000端口的phpfpm。
按此配置,访问 /user/info?userId=5&token=abcd
结果:
$_SERVER["QUERY_STRING"]: userId=5&token=abcd
$_SERVER["REQUEST_URI"]: /user/info?userId=5&token=abcd
nginx的内部变量已经如期传递给PHP了,可是事情没这么简单。
把location ~* .php(.*)$ 中跟fastcgi有关的代码全部注释掉,增加如下代码:
default_type text/html;
echo "request_uri: $request_uri
";
echo "uri: $uri
";
echo "query_string: $query_string
";
结果如下:
request_uri: /user/info?userId=5&token=abcd
uri: /index.php
query_string:
在location ~* .php(.*)$ 中,$query_string变量并没有值,只有$request_uri的值才是我们所预期的。
通过这次小实验我们可以有两个猜测:
可见,要修改GET请求参数的话,就必须修改$request_uri这个变量,可惜的是,nginx中,这个变量是不能修改的,更不幸的是,$uri和$query_string这两个变量也不能修改。我们可以自己创建一个变量,传递给REQUEST_URI环境变量。
第一步,把vhost.conf中 location ~ .php(.*)$ 的代码改为:
default_type text/html;
set_by_lua_file $rw_request_uri D:/phpStudy/nginx/htdocs_lua/set_request_uri.lua;
echo $rw_request_uri;
这段代码的功能是输出经过set_request_uri.lua返回的结果所设置的$rw_request_uri变量。
其中,set_request_uri.lua的代码如下:
function set_request_uri()
if ngx.var.request_method == "GET" then
local request_uri = ngx.var.request_uri;
local question_pos, _ = string.find(request_uri, '?')
if question_pos>0 then
local uri = string.sub(request_uri, 1, question_pos-1)
local args = ngx.decode_args(string.sub(request_uri, question_pos+1))
if args and args.userId then
args.userId = args.userId + 10000
return uri .. '?' .. ngx.encode_args(args)
else
return request_uri
end
else
return request_uri
end
else
return ngx.var.request_uri
end
end
return set_request_uri();
该函数的功能是:如果请求方法为GET,并且存在GET请求参数,而且存在userId变量,则把userId增加10000,然后重新组装成request_uri,否则返回原来的$request_uri变量。
访问 /user/info?userId=5&token=abcd,结果如下:
/user/info?userId=10005&token=abcd
的确是我们想要的结果!
第二步,修改fastcgi_params配置文件,把
fastcgi_param REQUEST_URI $request_uri;
改为
fastcgi_param REQUEST_URI $rw_request_uri;
把 location ~ .php(.*)$ 内的代码改为:
set_by_lua_file $rw_request_uri D:/phpStudy/nginx/htdocs_lua/set_request_uri.lua
fastcgi_pass 127.0.0.1:9000;
include fastcgi__params;
重启服务器,PHP代码不变,访问 /user/info?userId=5&token=abcd,结果:
$_SERVER["QUERY_STRING"]: userId=10005&token=abcd
$_SERVER["REQUEST_URI"]: /user/info?userId=10005&token=abcd
userId被成功修改!
修改post参数比修改get参数要简单点。
假设客户端传过来的POST数据的content-type是application/x-www-urlencoded类型。
在2.2的 set_by_lua_file一行代码后面增加:
access_by_lua_file D:/phpStudy/nginx/htdocs_lua/set_body.lua;
set_body.lua内容为:
function set_body()
ngx.req.read_body()
if ngx.var.request_method == "POST" then
local body = ngx.req.get_post_args();
if body and body.password then
body.password = ngx.md5(body.password)
ngx.req.set_body_data(ngx.encode_args(body))
end
end
end
return set_body();
该段代码的功能就是把POST body中的password字段的值替换成md5后的值。
用POST方法访问:/user/login?userId=5,Content-Type设为:application/x-www-form-urlencoded,POST数据设为:account=abc&password=123456,PHP代码打印出$_POST变量的值,结果:
Array
(
[password] => e10adc3949ba59abbe56e057f20f883e
[account] => abc
)
成功修改POST参数pasword的值!
通过以上代码,我们可以在不用修改后端Java、PHP的情况下,用lua代码,随意更改GET、POST参数,然后把修改后的参数传到后端,实现某些特殊的功能。
写到这么晚,也该睡觉了。
调试时,记得在nginx配置文件vhosts.conf的server块中增加如下指令:
lua_code_cache off;
即不缓存lua编译后的代码,免得每次修改lua代码都重启nginx。
另外,ngx_lua模块的文档可参考:https://github.com/openresty/lua-nginx-module。
感谢国人章亦春!写出功能这么强大的模块!