Nginx自定义模块编写:根据post参数路由到不同服务器

运维 系统运维
Nginx可以轻松实现根据不同的url 或者 get参数来转发到不同的服务器,然而当我们需要根据http包体来进行请求路由时,Nginx默认的配置规则就捉襟见肘了,但是没关系,Nginx提供了强大的自定义模块功能,我们只要进行需要的扩展就行了。

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; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

看明白了吧?我们使用了 433和434 这两个非标准http协议的返回码,所有请求进入时都默认进入 http://localhost:8888;,然后再根据返回码是 433 还是 434 来选择进入 http://localhost:6788 还是 http://localhost:6789。

OK,也许你已经猜到我将这个例子的用意了,是的,我们只要在我们的自定义模块中,根据http的包体返回不同的返回码,进而 proxy_pass 到不同的后端服务器即可。

好吧,接下来,我们正式进入nginx自定义模块的编写中来。

一. nginx 自定义模块编写 由于这也是我***次写nginx模块,所以也是参考了非常多文档,我一一列在这里,所以详细的入门就不说了,只说比较不太一样的地方。 参考链接:

  1. nginx的helloworld模块的helloworld
  2. nginx 一个例子模块,简单的将http请求的内容返输出
  3. nginx 自定义协议 扩展模块开发
  4. Emiller的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结束 

  • 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.

我们注册了一个回调函数 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) 
  • 1.

***个参数是 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); 
  • 1.

结果在 get_route_id 函数内部,调用:

r->connection->log 
  • 1.

的结果总是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; 

  • 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.

比较郁闷的是,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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

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; 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.

OK,enjoy it!

***,放出代码如下:

https://vimercode.googlecode.com/svn/trunk/nginx_req_route

责任编辑:黄丹 来源: IT技术博客
相关推荐

2011-03-21 09:16:52

2010-05-18 17:07:29

IIS服务器

2011-04-06 15:05:58

nagios监控Linux

2009-08-01 12:00:15

ASP.NET服务器自ASP.NET服务器ASP.NET

2011-05-19 10:16:27

ASP.NET

2012-09-24 14:31:55

C#网络协议C

2021-03-16 10:39:29

SpringBoot参数解析器

2022-07-11 10:37:41

MapPart集合

2009-06-25 14:53:35

自定义UI组件JSF框架

2021-05-28 08:58:41

Golang网卡metrics

2009-08-03 13:34:06

自定义C#控件

2018-11-21 09:53:08

服务服务器分类

2009-08-01 09:21:12

ASP.NET服务器自ASP.NET服务器控ASP.NET

2012-05-18 10:52:20

TitaniumAndroid模块自定义View模块

2020-11-19 10:50:43

ImportPython代码

2009-12-17 15:42:25

Rails自定义Hel

2010-04-02 11:08:30

惠普服务器选购

2021-02-04 09:18:20

服务器认证自定义

2015-06-10 10:54:24

自定义路PHP

2016-08-23 13:21:15

MVC路由视图
点赞
收藏

51CTO技术栈公众号