本篇文章給大家分享的是有關(guān)Nginx自定義模塊中根據(jù)post參數(shù)路由到不同服務(wù)器的示例分析,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
創(chuàng)新互聯(lián)專注于海豐企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站開發(fā),商城網(wǎng)站開發(fā)。海豐網(wǎng)站建設(shè)公司,為海豐等地區(qū)提供建站服務(wù)。全流程按需求定制設(shè)計(jì),專業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務(wù)
Nginx可以輕松實(shí)現(xiàn)根據(jù)不同的url 或者 get參數(shù)來轉(zhuǎn)發(fā)到不同的服務(wù)器,然而當(dāng)我們需要根據(jù)http包體來進(jìn)行請(qǐng)求路由時(shí),Nginx默認(rèn)的配置規(guī)則就捉襟見肘了,但是沒關(guān)系,Nginx提供了強(qiáng)大的自定義模塊功能,我們只要進(jìn)行需要的擴(kuò)展就行了。
我們來理一下思路,我們的需求是:
Nginx根據(jù)http包體的參數(shù),來選擇合適的路由
在這之前,我們先來考慮另一個(gè)問題:
在Nginx默認(rèn)配置的支持下,能否實(shí)現(xiàn)服務(wù)器間的跳轉(zhuǎn)呢?即類似于狀態(tài)機(jī),從一個(gè)服務(wù)器執(zhí)行OK后,跳轉(zhuǎn)到另一臺(tái)服務(wù)器,按照規(guī)則依次傳遞下去。
答案是可以的,這也是我之前寫bayonet之后,在nginx上特意嘗試的功能。
一個(gè)示例的配置如下:
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 這兩個(gè)非標(biāo)準(zhǔn)http協(xié)議的返回碼,所有請(qǐng)求進(jìn)入時(shí)都默認(rèn)進(jìn)入 http://localhost:8888;,然后再根據(jù)返回碼是 433 還是 434 來選擇進(jìn)入 http://localhost:6788 還是 http://localhost:6789。
OK,也許你已經(jīng)猜到我將這個(gè)例子的用意了,是的,我們只要在我們的自定義模塊中,根據(jù)http的包體返回不同的返回碼,進(jìn)而 proxy_pass 到不同的后端服務(wù)器即可。
好吧,接下來,我們正式進(jìn)入nginx自定義模塊的編寫中來。
一. nginx 自定義模塊編寫由于這也是我***次寫nginx模塊,所以也是參考了非常多文檔,我一一列在這里,所以詳細(xì)的入門就不說了,只說比較不太一樣的地方。 參考鏈接:
nginx的helloworld模塊的helloworld
nginx 一個(gè)例子模塊,簡(jiǎn)單的將http請(qǐng)求的內(nèi)容返輸出
nginx 自定義協(xié)議 擴(kuò)展模塊開發(fā)
Emiller的Nginx模塊開發(fā)指南
而我們這個(gè)模塊一個(gè)***的特點(diǎn)就是,需要等包體整個(gè)接收完才能進(jìn)行處理,所以有如下代碼:
void ngx_http_foo_post_handler(ngx_http_request_t *r){ // 請(qǐng)求全部讀完后從這里入口, 可以產(chǎn)生響應(yīng) ngx_http_request_body_t* rrb = 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結(jié)束 }
我們注冊(cè)了一個(gè)回調(diào)函數(shù) ngx_http_foo_post_handler,當(dāng)包體全部接受完成時(shí)就會(huì)調(diào)用。之后我們調(diào)用了get_route_id來獲取返回碼,然后通過 ngx_http_finalize_request(r, result); 來告訴nginx處理的結(jié)果。
這里有個(gè)小插曲,即get_route_id。我們來看一下它定義的原型:
extern int get_route_id(ngx_log_t *log, int method, char* uri, char* args, char* body, int body_size)
***個(gè)參數(shù)是 ngx_log_t *log,是為了方便在報(bào)錯(cuò)的時(shí)候打印日志。然而在最開始的時(shí)候,get_route_id 的原型是這樣:
extern int get_route_id(ngx_http_request_t *r, int method, char* uri, char* args, char* body, int body_size);
結(jié)果在 get_route_id 函數(shù)內(nèi)部,調(diào)用:
r->connection->log
的結(jié)果總是null,至今也不知道為什么。
OK,接下來我們只要在get_route_id中增加邏輯代碼,讀幾行配置,判斷一下就可以了~ 但是,我想要的遠(yuǎn)不止如此。
二、lua解析器的加入
老博友應(yīng)該都看過我之前寫的一篇博客: 代碼即數(shù)據(jù),數(shù)據(jù)即代碼(1)-把難以變更的代碼變成易于變更的數(shù)據(jù),而這一次的需求也非常符合使用腳本的原則:
只需要告訴我返回nginx哪個(gè)返回碼,具體怎么算出來的,再復(fù)雜,再多變,都放到腳本里面去。
所以接下來我又寫了c調(diào)用lua的代碼:
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的很多函數(shù)都變了,比如lua_open廢棄,變成luaL_newstate等,不過總體來說還算沒浪費(fèi)太多時(shí)間。
接下來是req_route.lua的內(nèi)容,我只截取入口函數(shù)如下:
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 --到這里位置,就把所有的數(shù)據(jù)都拿到了 --print (loc, pf, appid) -- 找是否在對(duì)應(yīng)的url, loc中 if not is_match_pf_and_loc(pf, loc) then return OUT_CODE end -- 找是否在對(duì)應(yīng)的appid中 if not is_match_appid(appid) then return OUT_CODE end return IN_CODE end
OK,結(jié)合了lua解析器之后,無論多復(fù)雜的調(diào)整,我們都基本可以做到只修改lua腳本而不需要重新修改、編譯nginx模塊代碼了。
接下來,就該是體驗(yàn)我們的成果了。
三、Nginx配置
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!
以上就是Nginx自定義模塊中根據(jù)post參數(shù)路由到不同服務(wù)器的示例分析,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。