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. window7下搭建本机版git并在本机上进行代码管理

    1.安装软件:Git-1.9.2-preview20140411.exe与TortoiseGit-1.8.8.0-64bit.msi 2.在本机任一目录下执行如下操作: 3.创建一个test.java ...

  2. sftp java 上传

    1. 注意问题 uri的格式: sftp://zhangsan:123456@10.10.10.10:22 dir问题 : 判断有没有 没有创建 然后进入 类推 config问题: StrictHos ...

  3. 洛谷P1155 双栈排序(贪心)

    题意 题目链接 Sol 首先不难想到一种贪心策略:能弹则弹,优先放A 然后xjb写了写发现只有\(40\),原因是存在需要决策的情况 比如 \(A = {10}\) \(B = {8}\) 现在进来一 ...

  4. Activiti 配置Oracle不能自动创建表解决方法

    使用配置文件创建工作流表 <bean id="processEngineConfiguration" class="org.activiti.engine.impl ...

  5. Python爬虫教程-30-Scrapy 爬虫框架介绍

    从本篇开始学习 Scrapy 爬虫框架 Python爬虫教程-30-Scrapy 爬虫框架介绍 框架:框架就是对于相同的相似的部分,代码做到不出错,而我们就可以将注意力放到我们自己的部分了 常见爬虫框 ...

  6. 139.00.006 Git学习-标签管理Tag

    @(139 - Environment Settings | 环境配置) 一.Why 发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本.将来无论什么时候,取 ...

  7. OLED中的Demura

    OLED作为一种电流型发光器件已越来越多地被应用于高性能显示中.由于它自发光的特性,与LCD相比,AMOLED具有高对比度.超轻薄.可弯曲等诸多优点.但是,亮度均匀性和残像仍然是它目前面临的两个主要难 ...

  8. 查看源代码HTML

    HTML 提示 - 如何查看源代码 如果您想找到其中的奥秘,只需要单击右键,然后选择“查看源文件”(IE)或“查看页面源代码”(Firefox),其他浏览器的做法也是类似的.这么做会打开一个包含页面 ...

  9. Hyper-V复制

    Hyper-V复制: 默认HV01上的所有虚机都被复制到HV02的 Hyper-V Replica 目录下.虚机启用复制后,当需要启用虚机副本时,要先在HV01上把原虚机关机,然后在HV02上选择故障 ...

  10. PPTP vs. OpenVPN vs. L2TP/IPsec vs. SSTP

    Which is the Best VPN Protocol? PPTP vs. OpenVPN vs. L2TP/IPsec vs. SSTP Want to use a VPN? If you’r ...