今天有个需求,要求是在访问某地址,比如http://www.test.com/jump?a=x&location=http:www.baidu.com,这个时候要用nginx去匹配规则并跳转,要求设置cookie "a"的值为"x",并且301跳转到location指定的地址,nginx虽然也常用,但是也就是最基本的配置,所以还花了好几个小时,才算弄出来了。大体配置如下:
server {
listen 80;
server_name www.test.com;
root /data/vhosts/test.com/;
location / {
ssi on;
ssi_silent_errors on;
ssi_types text/shtml;
}
index index.php index.html;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:1026;
fastcgi_index index.php;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_param SCRIPT_FILENAME /data/vhosts/test.com/$fastcgi_script_name;
include fastcgi_params;
}
location /jump{#这里,如果url是jump的则进入这里进行判断
set $test_url '';
if ( $query_string ~* ^fref=([^&]*)&location=(.*)$ ){#因为nginx不能直接对查询参数进行正则匹配,所以需要通过查询字符串变量来实现
set $tset_cookie $1;#设置cookie的头信息
set $test_url $2;
}
if ( $test_url != '' ){#跳转地址不为空则跳转
add_header Set-Cookie "fref=$test_cookie; PATH=/; DOMAIN=www.test.com;";
rewrite /jump $test_url? permanent;#permanet表示301永久跳转
}
rewrite ^ http://www.test.com last;#如果参数不满足需求,则跳转到默认页
}
PS: 貌似没找到nginx的urldecode的语法,有些实现都是要安装模块的,这个要再看看
nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器,然而当我们需要根据http包体来进行请求路由时,nginx默认的配置规则就捉襟见肘了,但是没关系,nginx提供了强大的自定义模块功能,我们只要进行需要的扩展就行了。
我们来理一下思路,我们的需求是:
nginx根据http包体的参数,来选择合适的路由
在这之前,我们先来考虑另一个问题:
在nginx默认配置的支持下,能否实现服务器间的跳转呢?即类似于状态机,从一个服务器执行OK后,跳转到另一台服务器,按照规则依次传递下去。
答案是可以的,这也是我之前写bayonet之后,在nginx上特意尝试的功能。
一个示例的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | server { listen 8080; server_name localhost; location / { proxy_pass http://localhost:8888; error_page 433 = @433; error_page 434 = @434; } location @433 { proxy_pass http://localhost:6788; } location @434 { proxy_pass http://localhost:6789; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } |
看明白了吧?我们使用了 433和434 这两个非标准http协议的返回码,所有请求进入时都默认进入 http://localhost:8888;,然后再根据返回码是 433 还是 434 来选择进入 http://localhost:6788 还是 http://localhost:6789。
OK,也许你已经猜到我将这个例子的用意了,是的,我们只要在我们的自定义模块中,根据http的包体返回不同的返回码,进而 proxy_pass 到不同的后端服务器即可。
好吧,接下来,我们正式进入nginx自定义模块的编写中来。
一. nginx 自定义模块编写
由于这也是我第一次写nginx模块,所以也是参考了非常多文档,我一一列在这里,所以详细的入门就不说了,只说比较不太一样的地方。
参考链接:
而我们这个模块一个最大的特点就是,需要等包体整个接收完才能进行处理,所以有如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
我们注册了一个回调函数 ngx_http_foo_post_handler,当包体全部接受完成时就会调用。之后我们调用了get_route_id来获取返回码,然后通过 ngx_http_finalize_request(r, result); 来告诉nginx处理的结果。
这里有个小插曲,即get_route_id。我们来看一下它定义的原型:
1 | |
第一个参数是 ngx_log_t *log,是为了方便在报错的时候打印日志。然而在最开始的时候,get_route_id 的原型是这样:
1 | |
结果在 get_route_id 函数内部,调用
1 | |
的结果总是null,至今也不知道为什么。(知道了是lua头文件和ngx头文件顺序的问题,把ngx头文件放到最前面即可)
OK,接下来我们只要在get_route_id中增加逻辑代码,读几行配置,判断一下就可以了~ 但是,我想要的远不止如此。
二.lua解析器的加入
老博友应该都看过我之前写的一篇博客: 代码即数据,数据即代码(1)-把难以变更的代码变成易于变更的数据,而这一次的需求也非常符合使用脚本的原则:
只需要告诉我返回nginx哪个返回码,具体怎么算出来的,再复杂,再多变,都放到脚本里面去。
所以接下来我又写了c调用lua的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | |
比较郁闷的是,lua 5.2的很多函数都变了,比如lua_open废弃,变成luaL_newstate等,不过总体来说还算没浪费太多时间。
接下来是req_route.lua的内容,我只截取入口函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function get_route_id(method, uri, args, body) loc, pf ,appid = get_need_vals(method, uri, args, body) if loc == nil or pf == nil or appid == nil then return OUT_CODE end --到这里位置,就把所有的数据都拿到了 --print (loc, pf, appid) -- 找是否在对应的url, loc中 if not is_match_pf_and_loc(pf, loc) then return OUT_CODE end -- 找是否在对应的appid中 if not is_match_appid(appid) then return OUT_CODE end return IN_CODE end |
OK,结合了lua解析器之后,无论多复杂的调整,我们都基本可以做到只修改lua脚本而不需要重新修改、编译nginx模块代码了。
接下来,就该是体验我们的成果了。
三.nginx配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | server { listen 8080; server_name localhost; location /req_route { req_route; error_page 433 = @433; error_page 434 = @434; } location @433 { proxy_pass http://localhost:6788; } location @434 { proxy_pass http://localhost:6789; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } |
OK,enjoy it!
最后,放出代码如下:
https://vimercode.googlecode.com/svn/trunk/nginx_req_route
用perl or lua的版本如下
http://www.php-oa.com/2010/09/25/perl-perl-nginx.html
https://github.com/chaoslawful/lua-nginx-module