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,至今也不知道为什么。(知道了是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
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


用perl or lua的版本如下
http://www.php-oa.com/2010/09/25/perl-perl-nginx.html
https://github.com/chaoslawful/lua-nginx-module

nginx自定义模块编写-根据post参数路由到不同服务器的更多相关文章

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

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

    Nginx自定义模块编写:根据post参数路由到不同服务器 2014-05-05 15:27 blogread IT技术博客 字号:T | T Nginx可以轻松实现根据不同的url 或者 get参数 ...

  3. nginx自定义模块记录上游服务器特定响应头

    功能,服务器通过扩展自定义命令,记录上游的服务器返回的特定响应头内容,记录到本地文件中 代码如下: /* * Copyright (C) Ciaos */ #include <ngx_confi ...

  4. Nginx模块开发1_明白自定义模块的编译流程

    自定义模块的编译流程 --add-module参数 configure使用--add-module参数指定添加模块目录. config脚本 由--add-module指定的目录保存为$ngx-addo ...

  5. 解决nginx上传模块nginx_upload_module传递GET参数

    解决nginx上传模块nginx_upload_module传递GET参数的方法总结 最近用户反映我们的系统只能上传50M大小的文件, 希望能够支持上传更大的文件. 很显然PHP无法轻易实现大文件上传 ...

  6. nginx的proxy模块详解以及参数

    文章来源 运维公会:nginx的proxy模块详解以及参数 使用nginx配置代理的时候,肯定是要用到http_proxy模块.这个模块也是在安装nginx的时候默认安装.它的作用就是将请求转发到相应 ...

  7. Angular 自定义模块以及配置路由实现模块懒加载

    项目目录 创建模块 ng g module module/user --routing ng g module module/article --routing ng g module module/ ...

  8. 实现Nginx Upload 模块 功能上传文件。

    分析(也许我表达的让人难以理解,但是我想说一句,直接实践是最好的.....): 一.Ningx 上传( 1.安装Nginx 的模块文件(upload):https://www.nginx.com/re ...

  9. nginx -- handler模块(100%)

    handler模块简介 相信大家在看了前一章的模块概述以后,都对nginx的模块有了一个基本的认识.基本上作为第三方开发者最可能开发的就是三种类型的模块,即handler,filter和load-ba ...

随机推荐

  1. 几个STL算法:includes,set_difference、set_intersection、set_symmetric_difference、set_union, pre_permutation, next_permutation

    includes: 测试有序序列中是否包含另一个序列的全部元素. template<class inputIterator1, class inputIterator2> bool inc ...

  2. Stripies(POJ 1862 贪心)

    Stripies Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 14151   Accepted: 6628 Descrip ...

  3. Taglib、EL、OGNL

    Taglib.EL.OGNL 阅读目录 1. Taglib(tag library) 标签库 2. EL(Expression Language) 表达式 3. OGNL(Object-Graph N ...

  4. 【Xamarin挖墙脚系列:代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧(转)】

    正愁如何选择构建项目中的视图呢,现在官方推荐画板 Storybord...但是好像 xib貌似更胜一筹.以前的老棒子总喜欢装吊,用代码写....用代码堆一个HTML页面不知道你们尝试过没有.等页面做出 ...

  5. css案例学习之双斜角横线菜单

    效果 代码 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w ...

  6. bzoj1623 [Usaco2008 Open]Cow Cars 奶牛飞车

    Description   编号为1到N的N只奶牛正各自驾着车打算在牛德比亚的高速公路上飞驰.高速公路有M(1≤M≤N)条车道.奶牛i有一个自己的车速上限Si(l≤Si≤1,000,000).     ...

  7. UESTC_Frozen Rose-Heads CDOJ 791

    The winter is coming and all the experts are warning that it will be the coldest one in the last hun ...

  8. Redis 3.0集群 Window搭建方案

    Redis 3.0集群 Window搭建方案 1.集群安装前准备 安装Ruby环境,安装:rubyinstaller-2.3.0-x64.exe http://dl.bintray.com/onecl ...

  9. Unsupported Media Type 415问题解决办法(Ajax)

    场景:Ajax传一个json对象到服务器,让参数自动封装至与json对象匹配的java对象中. 错误类型 错误类型1: "status":415 "error" ...

  10. Virtual Friends(并查集+map)

    Virtual Friends Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)T ...