IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

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

Vimer 2012-03-12 23:54:03 累计浏览 7,351 次
本机暂存

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. nginx的helloworld模块的helloworld
  2. nginx 一个例子模块,简单的将http请求的内容返输出
  3. nginx 自定义协议 扩展模块开发
  4. Emiller的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
void ngx_http_foo_post_handler(ngx_http_request_t *r){
    // 请求全部读完后从这里入口, 可以产生响应
    ngx_http_request_body_t* rb = 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。我们来看一下它定义的原型:

1
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 的原型是这样:

1
extern int get_route_id(ngx_http_request_t *r, int method, char* uri, char* args, char* body, int body_size);

结果在 get_route_id 函数内部,调用

1
r->connection->log

的结果总是null,至今也不知道为什么。

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
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的内容,我只截取入口函数如下:

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

同分类推荐文章

  1. 等了十年的 Go 链式管道,终于来了:seq 让你像写 Scala 一样写 Go (2026-06-25 18:38:18)
  2. Go 实验特性详解 (2026-06-21 10:05:27)
  3. amd64 微架构级别对 Go 程序性能提升多少? (2026-06-21 09:38:49)

查看更多 后端 文章 →

建议继续学习

  1. 配置Nginx+uwsgi更方便地部署python应用 (累计阅读 107,164)
  2. 搜狐闪电邮箱的 Nginx/Postfix 使用模式 (累计阅读 33,895)
  3. 记录一个软中断问题 (累计阅读 16,953)
  4. 解析nginx负载均衡 (累计阅读 16,622)
  5. server日志的路径分析 (累计阅读 11,239)
  6. Nginx模块开发入门 (累计阅读 11,168)
  7. 检查nginx配置,重载配置以及重启的方法 (累计阅读 10,895)
  8. Cacti 添加 Nginx 监控 (累计阅读 10,641)
  9. fsockopen 异步处理 (累计阅读 10,342)
  10. 使用Squid缓存视频 (累计阅读 10,337)