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

2014-05-05 15:27 blogread IT技术博客 字号:T | T

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

我们来理一下思路,我们的需求是:

Nginx根据http包体的参数,来选择合适的路由

在这之前,我们先来考虑另一个问题:

在Nginx默认配置的支持下,能否实现服务器间的跳转呢?即类似于状态机,从一个服务器执行OK后,跳转到另一台服务器,按照规则依次传递下去。

答案是可以的,这也是我之前写bayonet之后,在nginx上特意尝试的功能。

一个示例的配置如下:

  1. server {
  2. listen       8080;
  3. server_name  localhost;
  4. location / {
  5. proxy_pass http://localhost:8888;
  6. error_page 433 = @433;
  7. error_page 434 = @434;
  8. }
  9. location @433 {
  10. proxy_pass http://localhost:6788;
  11. }
  12. location @434 {
  13. proxy_pass http://localhost:6789;
  14. }
  15. error_page   500 502 503 504  /50x.html;
  16. location = /50x.html {
  17. root   html;
  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模块开发指南

而我们这个模块一个最大的特点就是,需要等包体整个接收完才能进行处理,所以有如下代码:

  1. void ngx_http_foo_post_handler(ngx_http_request_t *r){
  2. // 请求全部读完后从这里入口, 可以产生响应
  3. ngx_http_request_body_t* rrb = r->request_body;
  4. char* body = NULL;
  5. int body_size = 0;
  6. if (rb && rb->buf)
  7. {
  8. body = (char*)rb->buf->pos;
  9. body_size = rb->buf->last - rb->buf->pos;
  10. }
  11. int result = get_route_id(r->connection->log,
  12. (int)r->method,
  13. (char*)r->uri.data,
  14. (char*)r->args.data,
  15. body,
  16. body_size
  17. );
  18. if (result < 0)
  19. {
  20. ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "get_route_id fail, result:%d", result);
  21. result = DFT_ROUTE_ID;
  22. }
  23. ngx_http_finalize_request(r, result);
  24. }
  25. static ngx_int_t ngx_http_req_route_handler(ngx_http_request_t *r)
  26. {
  27. ngx_http_read_client_request_body(r, ngx_http_foo_post_handler);
  28. return NGX_DONE; // 主handler结束
  29. }

我们注册了一个回调函数 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. int get_route_id(ngx_log_t *log, int method, char* uri, char* args, char* body, int body_size)
  2. {
  3. const char lua_funcname[] = "get_route_id";
  4. lua_State *L = luaL_newstate();
  5. luaL_openlibs(L);
  6. if (luaL_loadfile(L, LUA_FILENAME) || lua_pcall(L, 0, 0, 0))
  7. {
  8. ngx_log_error(NGX_LOG_ERR, log, 0, "cannot run configuration file: %s", lua_tostring(L, -1));
  9. lua_close(L);
  10. return -1;
  11. }
  12. lua_getglobal(L, lua_funcname); /* function to be called */
  13. lua_pushnumber(L, method);
  14. lua_pushstring(L, uri);
  15. lua_pushstring(L, args);
  16. lua_pushlstring(L, body, body_size);
  17. /* do the call (1 arguments, 1 result) */
  18. if (lua_pcall(L, 4, 1, 0) != 0)
  19. {
  20. ngx_log_error(NGX_LOG_ERR, log, 0, "error running function %s: %s", lua_funcname, lua_tostring(L, -1));
  21. lua_close(L);
  22. return -2;
  23. }
  24. /* retrieve result */
  25. if (!lua_isnumber(L, -1))
  26. {
  27. ngx_log_error(NGX_LOG_ERR, log, 0, "function %s must return a number", lua_funcname);
  28. lua_close(L);
  29. return -3;
  30. }
  31. int result = (int)lua_tonumber(L, -1);
  32. lua_pop(L, 1); /* pop returned value */
  33. lua_close(L);
  34. return result;
  35. }

比较郁闷的是,lua 5.2的很多函数都变了,比如lua_open废弃,变成luaL_newstate等,不过总体来说还算没浪费太多时间。

接下来是req_route.lua的内容,我只截取入口函数如下:

  1. function get_route_id(method, uri, args, body)
  2. loc, pf ,appid = get_need_vals(method, uri, args, body)
  3. if loc == nil or pf == nil or appid == nil then
  4. return OUT_CODE
  5. end
  6. --到这里位置,就把所有的数据都拿到了
  7. --print (loc, pf, appid)
  8. -- 找是否在对应的url, loc中
  9. if not is_match_pf_and_loc(pf, loc) then
  10. return OUT_CODE
  11. end
  12. -- 找是否在对应的appid中
  13. if not is_match_appid(appid) then
  14. return OUT_CODE
  15. end
  16. return IN_CODE
  17. end

OK,结合了lua解析器之后,无论多复杂的调整,我们都基本可以做到只修改lua脚本而不需要重新修改、编译nginx模块代码了。

接下来,就该是体验我们的成果了。

三、Nginx配置

  1. server {
  2. listen       8080;
  3. server_name  localhost;
  4. location /req_route {
  5. req_route;
  6. error_page 433 = @433;
  7. error_page 434 = @434;
  8. }
  9. location @433 {
  10. proxy_pass http://localhost:6788;
  11. }
  12. location @434 {
  13. proxy_pass http://localhost:6789;
  14. }
  15. error_page   500 502 503 504  /50x.html;
  16. location = /50x.html {
  17. root   html;
  18. }
  19. }

OK,enjoy it!

最后,放出代码如下:

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

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

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

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

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

  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. String API

    String它即是内置对象,也是 包装类型之一,所有的String API都无法修改原字符串,而是返回一个新的字符串.   1.大小写转换 str.toUpperCase(); str = str.t ...

  2. 原生ajax与封装的ajax使用方法

    当我们不会写后端接口来测试ajax时,我们可以使用node环境创建一个本地服务器. 1.创建一个本地服务器可参考http://www.cnblogs.com/heyujun-/p/6793900.ht ...

  3. 139.00.005 Git学习-分支管理

    @(139 - Environment Settings | 环境配置) 一.Why? 分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交, ...

  4. 常用APDU指令错误码

    状态码 性质 错误解释 9000 正常 成功执行 6200 警告 信息未提供 6281 警告 回送数据可能出错 6282 警告 文件长度小于Le 6283 警告 选中的文件无效 6284 警告 FCI ...

  5. 十、一行多个:使用float布局的经典方法 ---接(一)

    1.使用float必须要清除float:即在使用float元素的父级元素上清除float. 清除float的方法有三种,在父元素上加:1.width: 100% 或者固定宽度 +overflow:hi ...

  6. Javascript之全局变量和局部变量部分讲解

    以此文作为自己学习的一个总结. 关于全局变量和局部变量的一句简单的定义:在函数外声明的变量都为全局变量,在函数内声明的为局部变量. 一.局部变量和全局变量重名会覆盖全局变量 var a = 1; fu ...

  7. 修改 Linux VM 中单个用户最大进程数的限制

    在部署有并发任务执行的虚机上, 会遇到 SSH 无法访问的问题. 本文将帮助你找出其中一种比较特殊的原因, 并提供解决方案. Note 以下案例分析基于 CentOS 7, 对于其他版本的 Linux ...

  8. 针对通过 SSH 连接到 Azure Linux VM 时发生的失败、错误或被拒绝问题进行故障排除

    尝试连接到 Linux 虚拟机 (VM) 时,有多种原因可能会导致安全外壳 (SSH) 错误.SSH 连接失败或被拒绝. 本文帮助用户找出原因并更正问题. 可以使用 Azure 门户.Azure CL ...

  9. centos7 nginx访问目录403解决

    查看  /usr/sbin/sestatusSELinux status是不是enabled  然后 vi /etc/selinux/config修改:SELINUX=disabledreboot

  10. Java时间格式化时YYYY(大写)和yyyy(小写)的区别

    在Java中,我们大多数情况下格式日期都是用的SimpleDateFormat,比如说把一个日期格式成"yyyy-MM-dd"的形式. 我们要注意的是,对于年份来说,大写的Y和小写 ...