对nginx lua模块的整个流程,原理简单解析。由于nginx lua模块相关配置,指令,API非常多,所以本文档只以content_by_lua指令举例说明。
读本文档最好配合读源码. 不适合对nginx和lua一点都不了解的人看。
解析配置阶段(从上到下按执行顺序) | |
函数名 | 作用 |
ngx_http_lua_create_main_conf | create main configuration 创建main级别配置ngx_http_lua_main_conf_t 见本文档2.1.1 |
ngx_http_lua_create_loc_conf | create location configuration 创建location级别配置ngx_http_lua_loc_conf_t 见本文档2.1.2 |
ngx_http_lua_content_by_lua | 配置设置解析 见本文档2.2.1 |
ngx_http_lua_init_main_conf | init main configuration 解析完配置后,如果某项配置没配,给予默认值 见本文档2.2.2 |
ngx_http_lua_merge_loc_conf | merge location configuration 合并location配置 见本文档2.2.3 |
ngx_http_lua_init | Postconfiguration 配置解析完后,进行的配置检查操作 见本文档2.2.4 |
处理请求阶段 | |
ngx_http_lua_content_handler(ngx_http_request_t *r) | 处理请求体,创建执行lua code的上下文环境 |
ngx_http_lua_content_handler_inline(ngx_http_request_t *r) | 这个函数被ngx_http_lua_content_handler调用。本函数实际调用lua code来处理请求 见本文档2.3 |
由于配置太多。 使用content_by_lua指令举例
static ngx_command_t ngx_http_lua_cmds[] = { /* content_by_lua <inline script> */ { ngx_string("content_by_lua"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, ngx_http_lua_content_by_lua, NGX_HTTP_LOC_CONF_OFFSET, 0, (void *) ngx_http_lua_content_handler_inline }, 。。。 }
结构体 ngx_http_lua_main_conf_t 部分成员 配置项
ngx_http_lua_main_conf_t | |
lua_State *lua | Postconfiguration阶段ngx_http_lua_init中被创建。Lua的状态机 |
ngx_str_t lua_path; | Lua_package_path指定,指定的脚本所使用的Lua模块搜索路径 |
ngx_str_t lua_cpath; | Lua_package_cpath指定,设置Lua c模块搜索路径 |
ngx_http_lua_conf_handler_pt init_handler | Value为ngx_http_lua_init_by_inline init_by_lua中指定 Value为ngx_http_lua_init_by_file init_by_lua_file中指定 在Postconfiguration阶段ngx_http_lua_init中执行 |
Ngx_str_t init_src | Init_by_lua[_file] 指定lua源码,在Postconfiguration阶段ngx_http_lua_init中执行 |
unsigned requires_capture_filter:1 | ngx_http_lua_content_by_lua中赋值为1 |
ngx_http_lua_loc_conf_t部分成员,配置项
ngx_http_lua_loc_conf_t | |
ngx_flag_t force_read_body | 是否强制读取request body配置lua_need_request_body |
ngx_flag_t enable_code_cache | 是否使能code cache 默认是1 配置lua_code_cache |
ngx_http_handler_pt content_handler | Content_by_lua 指令 Cmd->post ngx_http_lua_content_handler_inline |
ngx_http_complex_value_t content_src | Content_by_lua 的lua code字符串 |
u_char *content_src_key | Cached key for content_src 指令content_by_lua lua code的key。内容为 “nhli_” + md5 + ‘\0’值 |
Ngx_flag_t check_client_abort | 检查client是否关闭连接 配置lua_check_client_abort |
llcf->content_src.value = value[1]; llcf->content_src_key = p; p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN); p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len); *p = '\0'; llcf->content_handler = (ngx_http_handler_pt) cmd->post; lmcf->requires_capture_filter = 1; // main conf clcf->handler = ngx_http_lua_content_handler; // register location content handler
3. 执行init_by_lua指令中lua code
if (multi_http_blocks || lmcf->requires_capture_filter) {// require_capture_filter 为1 rc = ngx_http_lua_capture_filter_init(cf);// if (rc != NGX_OK) { return rc; } } lmcf->postponed_to_rewrite_phase_end = 0; if (lmcf->lua == NULL) { ngx_http_lua_init_vm(cf, lmcf); // init lua vm lmcf->init_handler(cf->log, lmcf, lmcf->lua); // init_by_lua中的内容 }
注册nginx api for lua
ngx_http_lua_content_handler_inline { /* load Lua inline script (w/ cache) sp = 1 */ rc = ngx_http_lua_cache_loadbuffer(L, llcf->content_src.value.data, llcf->content_src.value.len, llcf->content_src_key, "content_by_lua", llcf->enable_code_cache ? 1 : 0); return ngx_http_lua_content_by_chunk(L, r); }
Nginx解析完请求后,找到了对应虚拟主机的配置,通常情况下会经过以下几个阶段的处理:
NGX_HTTP_POST_READ_PHASE: 读取请求内容阶段 |
|
NGX_HTTP_SERVER_REWRITE_PHASE: Server请求地址重写阶段 |
|
NGX_HTTP_FIND_CONFIG_PHASE: 配置查找阶段 |
|
NGX_HTTP_REWRITE_PHASE: Location请求地址重写阶段 |
Rewrite_by_lua rewrite_by_lua_file set_by_lua |
NGX_HTTP_POST_REWRITE_PHASE: 请求地址重写提交阶段 |
|
NGX_HTTP_PREACCESS_PHASE: 访问权限检查准备阶段 |
|
NGX_HTTP_ACCESS_PHASE: 访问权限检查阶段 |
Access_by_lua access_by_lua_file |
NGX_HTTP_POST_ACCESS_PHASE: 访问权限检查提交阶段 |
|
NGX_HTTP_TRY_FILES_PHASE: 配置项try_files处理阶段 |
|
NGX_HTTP_CONTENT_PHASE: 内容产生阶段 |
Content_by_lua content_by_lua_file |
NGX_HTTP_LOG_PHASE: 日志模块处理阶段 |
Log_by_lua log_by_lua_file |
所有的nginx api for lua保存在lua_State的全局变量中。
LUA_GLOBALSINDEX 全局变量,保存所有API 公共变量 | |
__ngx_req | ngx_http_request_t 结构体指针 |
ngx.arg | 所有参数 |
ngx.req | 请求相关的函数 |
ngx.log | 日志相关的函数 |
ngx.OK ngx.DONE ngx.AGAIN | 定义的一些返回值 |
。。。。。。 | |
LUA_REGISTRYINDEX 保存一些公共数据 Lua 提供了一个注册表,这是一个预定义出来的表,可以用来保存任何 C 代码想保存的 Lua 值。 这个表可以用伪索引 LUA_REGISTRYINDEX 来定位 |
|
ngx_http_lua_coroutines_key | 保存所有协程的table |
ngx_http_lua_ctx_tables_key | Lua request ctx data table |
ngx_http_lua_socket_pool_key | Lua socket connection pool table 连接池 |
ngx_http_lua_regex_cache_key | Lua precompiled regex object cache 正则表达式对象池 |
ngx_http_lua_code_cache_key | register table to cache user code |
lua_pushliteral(L, "__ngx_req");//将字符串"__ngx_req"压栈 lua_pushlightuserdata(L, r);// 将request指针压栈r压栈 lua_rawset(L, LUA_GLOBALSINDEX); //将request指针存入全局变量 global[“__ngx_req”] = r
为ngx_http_lua_set_req的反操作
{ ngx_http_request_t *r; lua_pushliteral(L, "__ngx_req");//将字符串"__ngx_req"压栈 lua_rawget(L, LUA_GLOBALSINDEX);//将全局变量global[“__ngx_req”]压栈 r = lua_touserdata(L, -1);//global[“__ngx_req”]即为light user data类型,将这个指针转为ngx_http_request_t *类型 lua_pop(L, 1);//从栈顶弹出一个元素 return r; }
lua_createtable(L, 0 /* narr */, 23 /* nrec */); /* .req */ { //将处理request结构体的c函数压栈 lua_pushcfunction(L, ngx_http_lua_ngx_req_get_method); //给.req table赋值,相当于.req[“get_method”]=ngx_http_lua_ngx_req_get_method,并将栈首元素出栈 lua_setfield(L, -2, "get_method"); } //等价于ngx[“req”] = req table lua_setfield(L, -2, "req");
static int ngx_http_lua_ngx_req_get_method(lua_State *L) { int n; ngx_http_request_t *r; n = lua_gettop(L); if (n != 0) { return luaL_error(L, "only one argument expected but got %d", n); } r = ngx_http_lua_get_req(L);//从lua全局变量得到request结构体指针,见4.2.2 if (r == NULL) { return luaL_error(L, "request object not found"); } ngx_http_lua_check_fake_request(L, r);//检查r合法性 lua_pushlstring(L, (char *) r->method_name.data, r->method_name.len);//将method压栈 return 1; }
配置举例
server { listen 80 default_server; listen 443; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; location / { content_by_lua ' method = ngx.req.get_method(); ngx.say(method); '; } }
结果:
root@root:~/nginx1.4.3/logs$ curl -i http://127.0.0.1 HTTP/1.1 200 OK Server: NexQloud/2.0.0 Date: Mon, 30 Jun 2014 11:21:33 GMT Content-Type: application/octet-stream Transfer-Encoding: chunked Connection: keep-alive GET
这个函数调用了lua_tolstring lua_toboolean lua_touserdata等函数,将lua栈中的数据弹出栈,转化成C里面的数据,然后nginx做相应的处理,
ngx_http_lua_ngx_echo
{ 。。。 for (i = 1; i <= nargs; i++) { type = lua_type(L, i); switch (type) { case LUA_TNUMBER: case LUA_TSTRING: lua_tolstring(L, i, &len); size += len; break; case LUA_TTABLE: size += ngx_http_lua_calc_strlen_in_table(L, i, i, 0 /* strict */); break; case LUA_TLIGHTUSERDATA: dd("userdata: %p", lua_touserdata(L, i)); if (lua_touserdata(L, i) == NULL) { size += sizeof("null") - 1; break; } continue; default: msg = lua_pushfstring(L, "string, number, boolean, nil, " "ngx.null, or array table expected, " "but got %s", lua_typename(L, type)); return luaL_argerror(L, i, msg); } } 。。。 }