Nginx can easily forward requests to different servers based on different URLs or get parameters. However, when we need to route requests based on the http package body, the default configuration rules of nginx are too limited, but it doesn't matter. nginx provides powerful custom module functions. We only need to make the necessary extensions.
Let's take a look at our ideas. Our needs are:
Nginx selects an appropriate route based on the http packet body parameters
Before that, let's consider another problem:
With the support of nginx's default configuration, can I redirect between servers? It is similar to a state machine. After an OK command is executed from one server, the system jumps to another server and passes the command in sequence according to the rules.
The answer is yes. This is also a feature I tried on nginx after I wrote bayonet.
The configuration of an example is as follows:
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 x.html;
Location =/50x.html {
Root html;
}
}
See it, right? We use the 433 and 434 non-standard http return codes. When all requests are accessed, the default value is http: // localhost: 8888 ;, then, select http: // localhost: 433 or http: // localhost: 434 based on the returned code.
OK, maybe you have already guessed the intention of this example. Yes, we only need to return different return codes according to the http package body in our custom module, then proxy_pass to different backend servers.
Now, we will officially write the nginx custom module.
I. Compiling the nginx custom module
This is also the first time I wrote the nginx module, so I also referred to a lot of documents. I listed them here one by one, so I won't go into the details, just to say that they are not quite the same.
Reference link:
Helloworld of the helloworld module of nginx
Nginx is an example module that simply outputs the http request content
Development of the nginx custom protocol extension module
Emiller's Nginx module development guide
One of the biggest features of this module is that it can be processed only after the entire packet body is received. The following code is available:
Void ngx_http_foo_post_handler (ngx_http_request_t * r ){
// After all the requests are read, a response can be generated from here.
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; // The end of the main handler.
}
We have registered a callback function ngx_http_foo_post_handler, which is called when all the packages are accepted. Then we call get_route_id to get the return code, and then use ngx_http_finalize_request (r, result); to tell nginx the processing result.
Here is an episode, that is, get_route_id. Let's take a look at the prototype it defines:
Extern int get_route_id (ngx_log_t * log, int method, char * uri, char * args, char * body, int body_size );
The first parameter is ngx_log_t * log, which is used to print logs when an error is reported. However, at the beginning, the prototype of get_route_id was as follows:
Extern int get_route_id (ngx_http_request_t * r, int method, char * uri, char * args, char * body, int body_size );
The result is called within the get_route_id function.
R-> connection-> log
The result is always null, so I do not know why. (If you know the order of the lua header file and the ngx header file, put the ngx header file at the beginning)
OK. Next, we only need to add the logic code in get_route_id, read several lines of configuration, and judge it ~ However, I want more than that.
II. Add lua parser
Old Boyou should have read a blog I wrote earlier: code is data, data is code (1)-turning hard-to-change code into easy-to-change data, this requirement is also very consistent with the script principle:
You only need to tell me which nginx return code is returned, and how to calculate it, which is complicated and changeable, and put it in the script.
So next I wrote the code for c to call 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;
}
It is depressing that many functions of lua 5.2 have changed, for example, lua_open is discarded and changed to luaL_newstate. However, in general, it does not waste much time.
Next is the req_route.lua content. I only extract the entry function as follows:
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
-- Get all the data here
-- Print (loc, pf, appid)
-- Find whether it is in the corresponding url, loc
If not is_match_pf_and_loc (pf, loc) then
Return OUT_CODE
End
-- Find whether it is in the corresponding appid
If not is_match_appid (appid) then
Return OUT_CODE
End
Return IN_CODE
End
OK. After the lua parser is combined, no matter how complicated the adjustment is, we can basically modify only the lua script without re-modifying or compiling the nginx module code.
Next, we should experience our achievements.
III. nginx configuration
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 x.html;
Location =/50x.html {
Root html;
}
}
OK, enjoy it!
Finally, release the code as follows:
Https://vimercode.googlecode.com/svn/trunk/nginx_req_route
The perl or lua version is as follows:
Http://www.php-oa.com/2010/09/25/perl-perl-nginx.html
Https://github.com/chaoslawful/lua-nginx-module