昔我往矣

Nginx中的url参数获取和header传递方法

2016年05月17日

本文分为如下三个部分

  • Nginx中获取url的参数
  • Nginx向后端服务器发送自定义header
  • Nginx返回自定义header给客户端
  • 使用Lua填Nginx的坑

获取url中的某个参数

关于获取和修改参数,强烈推荐春哥的文章Nginx变量漫谈,比如请求的域名是http://test.xnow.me/test?search=123,如果要获取search的参数值,可以使用nginx变量$arg_search,例如:

  server {
    server_name test.xnow.me;
    listen 80;
    
    location /test {
      rewrite ^(.*)$ https://www.google.com/?q=$arg_search; 
    } 
  }

使用上面的配置,在你请求http://test.xnow.me/test?abc=something的时候,浏览器会自动rewrite到https://www.google.com/?q=something。具体演示就忽略了。

在向后端服务器的请求中新增header

还是上面的例子,如果用户请求的是http://test.xnow.me/test?data=123,我们有一个奇怪的需求(譬如由于老版本的后端不支持处理这种参数形式的请求,%>_<%),要把请求中参数转为header交给后端处理,在ngx_http_proxy_module中有proxy_set_header指令,可以设置往后端服务器传递的header值,通常我们用它来设置X-Real-IP和Host等头部,其实还可以是其它自定义的头部,譬如我们把url中的参数data=123设置到头部里面。Nginx的配置如下:

  server {
    server_name test.xnow.me;
    listen 80;
    
    location /test2 {
      proxy_set_header data $arg_data;
      proxy_pass http://127.0.0.1:5000;
    }
 } 

为了验证这段配置,在nginx上的机器上编写一段flask代码,专门给客户端返回收到的header中的data数据,flask代码如下:

#!/usr/bin/python

import flask

app = flask.Flask(__name__)

@app.route("/test2")
def retheader():
    return flask.request.headers.get('data')

if __name__ == '__main__':
    app.run()

上面的flask.request.headers.get('data')就是专门打印header中的data字段内容的,重启Nginx和flask,然后使用curl测试

$ curl http://test.xnow.me/test2?data=haha
haha
$ curl http://test.xnow.me/test2?data2=123
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request.  Either the server is overloaded or there is an error in the application.</p>

如果请求中没有data参数,就返回了500错误,悲剧啊,异常数据要好好处理,反正我只是测试。

Nginx返回自定义header给客户端

上面的例子是把请求的参数数据发送给后端服务器,但是,我遇到的真实需求是要把请求的参数数据作为cookie种到用户的浏览器中,真变态。但是配合add_header指令,Nginx也能做,Nginx配置如下:

 server {
    server_name test.xnow.me;
    listen 80;
    
    location /test3 {
      if ( $arg_iamcookie != "" ) {
        add_header "Set-Cookie" "IamCookie=$arg_iamcookie; Domain=.xnow.me; Path=/; max-age=2592000";
      }
      proxy_pass http://127.0.0.1:5000;
    }
 }

为flask脚本增加一个/test3的路径(不提供样例了)以便我们测试,然后重启Nginx和Flask,再次发起请求,如下:

$ curl -I  http://test.xnow.me/test3?iamcookie=noone
HTTP/1.1 200 OK
Server: nginx/1.2.9
Date: Fri, 20 May 2016 15:37:25 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 8
Connection: keep-alive
Set-Cookie: IamCookie=noone; Domain=.xnow.me; Path=/; max-age=2592000

使用curl -I查看返回的header部分,注意观察header中的最后一个字段

Set-Cookie: IamCookie=noone; Domain=.xnow.me; Path=/; max-age=2592000

这一段Set-Cookie的意思是网浏览器中种下一个Cookie,Cookie的名字是IamCookie,内容是noone,对xnow.me有效,有效期为一个月。God Bless You。

使用Lua填Nginx的坑

读到这里大家可能有疑问,这一路下来不都是挺顺利的嘛,有啥坑需要填的呢?主要原因有二:

  1. 网上有一篇名为If Is Evil的文章,细数了Nginx中if不按照套路工作的情况。
  2. add_header的上下文环境为http,server,location和location中的if。如果要把上面实例中的if搬到server或者http上下文中,Nginx就会报add_header的错。如果前面的add_header是要对nginx上所有的站点都生效,岂不是要在所有的location下都增加上面的配置。

从灵活性和成本,以及以上两个理由看起来,我应该寻找更合适的解决方案。于是祭出Lua神器。Lua代码如下

http {
...
  header_filter_by_lua_block {
    if ngx.var.arg_iamcookie then
      ngx.header["Set-Cookie"] = "Iamcookie=" .. ngx.var.arg_iamcookie .. ";Domain=.xnow.me; Path=/; max-age=2592000"
    end
  }
...
}

以上这段代码,在Nginx返回的时候,给header里安插了Set-Cookie字段,十分简洁高效,用ab做了简单的压力测试,性能与Nginx的add_header不相上下。

最神奇的是,这段代码可以放在Nginx的任意上下文,我就把它放在了http里面。

End

做了这么多测试,其实并没有一个主题,但是又包含了很多知识点,解决问题的过程就是不断深入学习的过程。Openresty是个很有用的东西,值得好好学习。

当前暂无评论 »

添加新评论 »