Nginx自定义模块编写:根据post参数路由到不同服务器
Nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器,然而当我们需要根据http包体来进行请求路由时,Nginx默认的配置规则就捉襟见肘了,但是没关系,Nginx提供了强大的自定义模块功能,我们只要进行需要的扩展就行了。
我们来理一下思路,我们的需求是:
Nginx根据http包体的参数,来选择合适的路由
在这之前,我们先来考虑另一个问题:
在Nginx默认配置的支持下,能否实现服务器间的跳转呢?即类似于状态机,从一个服务器执行OK后,跳转到另一台服务器,按照规则依次传递下去。
答案是可以的,这也是我之前写bayonet之后,在nginx上特意尝试的功能。
一个示例的配置如下:
- 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模块,所以也是参考了非常多文档,我一一列在这里,所以详细的入门就不说了,只说比较不太一样的地方。 参考链接:
而我们这个模块一个最大的特点就是,需要等包体整个接收完才能进行处理,所以有如下代码:
- void ngx_http_foo_post_handler(ngx_http_request_t *r){
 - // 请求全部读完后从这里入口, 可以产生响应
 - ngx_http_request_body_t* rrb = r->request_body;
 - char* body = NULL;
 - int body_size = 0;
 - if (rb && rb->buf)
 - {
 - body = (char*)rb->buf->pos;
 - body_size = rb->buf->last - rb->buf->pos;
 - }
 - int result = get_route_id(r->connection->log,
 - (int)r->method,
 - (char*)r->uri.data,
 - (char*)r->args.data,
 - body,
 - body_size
 - );
 - if (result < 0)
 - {
 - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "get_route_id fail, result:%d", result);
 - result = DFT_ROUTE_ID;
 - }
 - ngx_http_finalize_request(r, result);
 - }
 - static ngx_int_t ngx_http_req_route_handler(ngx_http_request_t *r)
 - {
 - ngx_http_read_client_request_body(r, ngx_http_foo_post_handler);
 - return NGX_DONE; // 主handler结束
 - }
 
我们注册了一个回调函数 ngx_http_foo_post_handler,当包体全部接受完成时就会调用。之后我们调用了get_route_id来获取返回码,然后通过 ngx_http_finalize_request(r, result); 来告诉nginx处理的结果。
这里有个小插曲,即get_route_id。我们来看一下它定义的原型:
- extern int get_route_id(ngx_log_t *log, int method, char* uri, char* args, char* body, int body_size)
 
第一个参数是 ngx_log_t *log,是为了方便在报错的时候打印日志。然而在最开始的时候,get_route_id 的原型是这样:
- extern int get_route_id(ngx_http_request_t *r, int method, char* uri, char* args, char* body, int body_size);
 
结果在 get_route_id 函数内部,调用:
- r->connection->log
 
的结果总是null,至今也不知道为什么。
OK,接下来我们只要在get_route_id中增加逻辑代码,读几行配置,判断一下就可以了~ 但是,我想要的远不止如此。
二、lua解析器的加入
老博友应该都看过我之前写的一篇博客: 代码即数据,数据即代码(1)-把难以变更的代码变成易于变更的数据,而这一次的需求也非常符合使用脚本的原则:
只需要告诉我返回nginx哪个返回码,具体怎么算出来的,再复杂,再多变,都放到脚本里面去。
所以接下来我又写了c调用lua的代码:
- int get_route_id(ngx_log_t *log, int method, char* uri, char* args, char* body, int body_size)
 - {
 - const char lua_funcname[] = "get_route_id";
 - lua_State *L = luaL_newstate();
 - luaL_openlibs(L);
 - if (luaL_loadfile(L, LUA_FILENAME) || lua_pcall(L, 0, 0, 0))
 - {
 - ngx_log_error(NGX_LOG_ERR, log, 0, "cannot run configuration file: %s", lua_tostring(L, -1));
 - lua_close(L);
 - return -1;
 - }
 - lua_getglobal(L, lua_funcname); /* function to be called */
 - lua_pushnumber(L, method);
 - lua_pushstring(L, uri);
 - lua_pushstring(L, args);
 - lua_pushlstring(L, body, body_size);
 - /* do the call (1 arguments, 1 result) */
 - if (lua_pcall(L, 4, 1, 0) != 0)
 - {
 - ngx_log_error(NGX_LOG_ERR, log, 0, "error running function %s: %s", lua_funcname, lua_tostring(L, -1));
 - lua_close(L);
 - return -2;
 - }
 - /* retrieve result */
 - if (!lua_isnumber(L, -1))
 - {
 - ngx_log_error(NGX_LOG_ERR, log, 0, "function %s must return a number", lua_funcname);
 - lua_close(L);
 - return -3;
 - }
 - int result = (int)lua_tonumber(L, -1);
 - lua_pop(L, 1); /* pop returned value */
 - lua_close(L);
 - return result;
 - }
 
比较郁闷的是,lua 5.2的很多函数都变了,比如lua_open废弃,变成luaL_newstate等,不过总体来说还算没浪费太多时间。
接下来是req_route.lua的内容,我只截取入口函数如下:
- 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配置
- 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!
最后,放出代码如下:
Nginx自定义模块编写:根据post参数路由到不同服务器的更多相关文章
- nginx自定义模块编写-根据post参数路由到不同服务器
		
nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器,然而当我们需要根据http包体来进行请求路由时,nginx默认的配置规则就捉襟见肘了,但是没关系,nginx提供了强大的自 ...
 - nginx自定义模块编写-实时统计模块--转载
		
原文:http://www.vimer.cn/2012/05/nginx%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E5%9D%97%E7%BC%96%E5%86%99- ...
 - nginx自定义模块记录上游服务器特定响应头
		
功能,服务器通过扩展自定义命令,记录上游的服务器返回的特定响应头内容,记录到本地文件中 代码如下: /* * Copyright (C) Ciaos */ #include <ngx_confi ...
 - Nginx模块开发1_明白自定义模块的编译流程
		
自定义模块的编译流程 --add-module参数 configure使用--add-module参数指定添加模块目录. config脚本 由--add-module指定的目录保存为$ngx-addo ...
 - 解决nginx上传模块nginx_upload_module传递GET参数
		
解决nginx上传模块nginx_upload_module传递GET参数的方法总结 最近用户反映我们的系统只能上传50M大小的文件, 希望能够支持上传更大的文件. 很显然PHP无法轻易实现大文件上传 ...
 - nginx的proxy模块详解以及参数
		
文章来源 运维公会:nginx的proxy模块详解以及参数 使用nginx配置代理的时候,肯定是要用到http_proxy模块.这个模块也是在安装nginx的时候默认安装.它的作用就是将请求转发到相应 ...
 - Angular 自定义模块以及配置路由实现模块懒加载
		
项目目录 创建模块 ng g module module/user --routing ng g module module/article --routing ng g module module/ ...
 - 实现Nginx Upload 模块 功能上传文件。
		
分析(也许我表达的让人难以理解,但是我想说一句,直接实践是最好的.....): 一.Ningx 上传( 1.安装Nginx 的模块文件(upload):https://www.nginx.com/re ...
 - nginx -- handler模块(100%)
		
handler模块简介 相信大家在看了前一章的模块概述以后,都对nginx的模块有了一个基本的认识.基本上作为第三方开发者最可能开发的就是三种类型的模块,即handler,filter和load-ba ...
 
随机推荐
- 五、spring之DI循环依赖
			
什么是循环依赖 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映 ...
 - tmux使用技巧
			
1.tmux 进入tmux 2.在tmux中 按ctrl+b 表示要进行tmux操作了. 3. c -> create a session 4. "," -> ren ...
 - PAT 1048. Find Coins
			
two sum题目,算是贪婪吧 #include <cstdio> #include <cstdlib> #include <vector> #include &l ...
 - 数据库字段值为null利用setInc方法无法直接写入
			
1.数据库字段值为null利用setInc方法无法直接写入,先判断是否为空,再写入. if($points->add($dataList)){ $user=M('cuser'); $null=$ ...
 - 如何去除vue项目中的 # — vue路由的History模式
			
前言 在创建的 router 对象中,如果不配置 mode,就会使用默认的 hash 模式,该模式下会将路径格式化为 #! 开头. 添加 mode: 'history' 之后将使用 HTML5 his ...
 - JNLP文件具体说明编辑
			
JNLP(Java Network Launching Protocol )是java提供的一种可以通过浏览器直接执行java应用程序的途径,它使你可以直接通过一个网页上的url连接打开一个java应 ...
 - QT 编译遇到重定义;不同的基类型&在QT中使用C++ lib库
			
最近在使用osg和qt开发,在集成osg时候因为我使用的qt版本为非opengl的版本,导致qt自己封了一遍opengl的一些基类变量如double 这时候就会跟osg中声明的opengl的类型冲突, ...
 - 深度解析pos机,养卡人必看!
			
好多人对POS 好像都比较迷茫,这个说这个POS 好,那个说那个POS 好.下面就我对POS 的认知给兄弟们说下.对与不对的各位见谅. 第一.一清机 一清机是指在结算日结算后直接通过支付公司账号转 ...
 - 微软智能云Azure – 中国首家官方支持CoreOS的公有云
			
北京2016年6月24日, 在由中国开源软件推进联盟(COPU)主办, 开源社协办,微软赞助的“第十一届开源中国开源世界高峰论坛”上,微软亚太研发集团云计算高级总监梁戈碧女士正式对外宣布一个令人振奋的 ...
 - LeetCode-Maximal Rectangle [学以致用] ZZ
			
http://www.cnblogs.com/lichen782/p/leetcode_maximal_rectangle.html 题目: Given a 2D binary matrix fill ...