<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    nginx自定義模塊編寫-根據post參數路由到不同服務器

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    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

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全