nginx系列之修改请求参数,nginx系列参数


1、前言

前段时间接触到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模块,在请求参数传给后端代码之前,把相关参数做修改。

2、实现

本实验的环境为:win7+openresty1.11.2.2+php7,php由fastcgi方式运行,采用CI框架。

2.1 研究fastcgi 环境变量传递

先来看看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<br/>";
echo "uri: $uri<br/>";
echo "query_string: $query_string<br/>";

结果如下:

request_uri: /user/info?userId=5&token=abcd
uri: /index.php
query_string: 

在location ~* .php(.*)$ 中,$query_string变量并没有值,只有$request_uri的值才是我们所预期的。

通过这次小实验我们可以有两个猜测:

  • 对比 location / 中的这行代码:try_files $uri $uri/ /index.php 与 location ~* .php(.*)$$uri和$query_string变量的值,猜测这两个变量的值应该是从try_files指令中传过来的参数解析出来的。
  • 在同样的location下,nginx内部变量$query_string并没有值,在PHP中却有正确的值,猜测PHP的$_SERVER[‘QUERY_STRING’]是从$_SERVER[‘REQUEST_URI’]解析而来的,而不是从fastcgi的环境变量QUERY_STRING获取的。

可见,要修改GET请求参数的话,就必须修改$request_uri这个变量,可惜的是,nginx中,这个变量是不能修改的,更不幸的是,$uri和$query_string这两个变量也不能修改。我们可以自己创建一个变量,传递给REQUEST_URI环境变量。

2.2 修改GET参数

第一步,把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被成功修改!

2.3 修改POST参数

修改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的值!

3、后记

通过以上代码,我们可以在不用修改后端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。

感谢国人章亦春!写出功能这么强大的模块!

相关内容

    暂无相关文章