Skip to content

Commit 256a8af

Browse files
committed
add 003-ngx_lua_rewrite_lua but not yet
1 parent 74bb269 commit 256a8af

4 files changed

+286
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.DS_Store
+285
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
> 以下代码均出自 lua-nginx-module v0.10.7 版本
2+
3+
> 这篇文章主要要介绍 `rewrite_by_lua` 这个指令 <br>
4+
> rewrite 是 Nginx HTTP 框架划分的 11 个阶段的其中之一,通常在这个阶段我们可以实现 uri 的修改(进行内部重定向)或者 301、302 的 HTTP 重定向
5+
>
6+
7+
8+
### rewrite_by_lua
9+
10+
`lua-nginx-module` 通过这个指令来介入到一个请求的 rewrite 阶段。先来看下其配置项结构
11+
12+
```c
13+
{ ngx_string("rewrite_by_lua"),
14+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
15+
ngx_http_lua_rewrite_by_lua,
16+
NGX_HTTP_LOC_CONF_OFFSET,
17+
0,
18+
(void *) ngx_http_lua_rewrite_handler_inline },
19+
```
20+
21+
从这里可以了解到
22+
23+
- 这个指令可以出现在 main 配置块下、server 配置块下、location 配置块下和 location 下的 if 块
24+
- 必须携带一个参数
25+
- 解析函数是 `ngx_http_lua_rewrite_by_lua`
26+
27+
同样地,我们来看下 `ngx_http_lua_rewrite_by_lua ` 这个函数
28+
29+
```c
30+
/* parse `rewrite_by_lua` */
31+
char *
32+
ngx_http_lua_rewrite_by_lua(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
33+
{
34+
u_char *p, *chunkname;
35+
ngx_str_t *value;
36+
ngx_http_lua_main_conf_t *lmcf;
37+
ngx_http_lua_loc_conf_t *llcf = conf;
38+
39+
ngx_http_compile_complex_value_t ccv;
40+
41+
dd("enter");
42+
43+
#if defined(nginx_version) && nginx_version >= 8042 && nginx_version <= 8053
44+
return "does not work with " NGINX_VER;
45+
#endif
46+
47+
/* must specify a content handler */
48+
if (cmd->post == NULL) {
49+
return NGX_CONF_ERROR;
50+
}
51+
52+
if (llcf->rewrite_handler) {
53+
return "is duplicate";
54+
}
55+
56+
value = cf->args->elts;
57+
58+
if (value[1].len == 0) {
59+
/* Oops...Invalid location conf */
60+
ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
61+
"invalid location config: no runnable Lua code");
62+
63+
return NGX_CONF_ERROR;
64+
}
65+
66+
if (cmd->post == ngx_http_lua_rewrite_handler_inline) {
67+
chunkname = ngx_http_lua_gen_chunk_name(cf, "rewrite_by_lua",
68+
sizeof("rewrite_by_lua") - 1);
69+
if (chunkname == NULL) {
70+
return NGX_CONF_ERROR;
71+
}
72+
73+
llcf->rewrite_chunkname = chunkname;
74+
75+
/* Don't eval nginx variables for inline lua code */
76+
77+
llcf->rewrite_src.value = value[1];
78+
79+
p = ngx_palloc(cf->pool, NGX_HTTP_LUA_INLINE_KEY_LEN + 1);
80+
if (p == NULL) {
81+
return NGX_CONF_ERROR;
82+
}
83+
84+
llcf->rewrite_src_key = p;
85+
86+
p = ngx_copy(p, NGX_HTTP_LUA_INLINE_TAG, NGX_HTTP_LUA_INLINE_TAG_LEN);
87+
p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len);
88+
*p = '\0';
89+
} else {
90+
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
91+
ccv.cf = cf;
92+
ccv.value = &value[1];
93+
ccv.complex_value = &llcf->rewrite_src;
94+
95+
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
96+
return NGX_CONF_ERROR;
97+
}
98+
99+
if (llcf->rewrite_src.lengths == NULL) {
100+
/* no variable found */
101+
p = ngx_palloc(cf->pool, NGX_HTTP_LUA_FILE_KEY_LEN + 1);
102+
if (p == NULL) {
103+
return NGX_CONF_ERROR;
104+
}
105+
106+
llcf->rewrite_src_key = p;
107+
108+
p = ngx_copy(p, NGX_HTTP_LUA_FILE_TAG, NGX_HTTP_LUA_FILE_TAG_LEN);
109+
p = ngx_http_lua_digest_hex(p, value[1].data, value[1].len);
110+
*p = '\0';
111+
}
112+
}
113+
114+
llcf->rewrite_handler = (ngx_http_handler_pt) cmd->post;
115+
116+
lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module);
117+
118+
lmcf->requires_rewrite = 1;
119+
lmcf->requires_capture_filter = 1;
120+
121+
return NGX_CONF_OK;
122+
}
123+
```
124+
125+
首先这个函数计算了个叫做 `chunkname` 的字符串,可以看到它是在函数 `ngx_http_lua_gen_chunk_name` 里得到的,通过 gdb 调试,我们可以看到,最终得到的 `chunkname``"rewrite_by_lua(marco.conf:148)"`,这个字符串的作用后面会介绍。
126+
127+
<img src="../images/003-ngx_http_lua_rewrite_by_lua-001.png" alt="chunkname">
128+
129+
接下来根据配置项配置的 post 指针指向的函数不同,记录下 Lua code 或者 Lua code path;最后为 Lua code cache 这个功能计算了一个 cache key,通过 gdb 我们来看下计算出来的 key 是什么样的(这边实例走的是 `rewrite_by_lua_file` 指令)
130+
131+
<img src="../images/003-ngx_http_lua_rewrite_by_lua-002.png" alt="rewrite_src_key">
132+
133+
后面就是用这个 key 来检索缓存 Lua code,当然前提是 `lua_code_cache``on`
134+
135+
恩,指令解析到这里就结束了,那么,`rewrite_by_lua` 究竟是如何让 Lua code 介入到 HTTP 请求里的呢?下面就来一探究竟
136+
137+
### rewrite_by_lua_handler
138+
139+
这是真正被插入到 rewrite 阶段的回调方法(见 [ngx_lua_init_by_lua](001-ngx_lua_init_by_lua.md) 关于`ngx_http_lua_init` 的介绍)
140+
141+
```c
142+
ngx_int_t
143+
ngx_http_lua_rewrite_handler_inline(ngx_http_request_t *r)
144+
{
145+
lua_State *L;
146+
ngx_int_t rc;
147+
ngx_http_lua_loc_conf_t *llcf;
148+
149+
dd("rewrite by lua inline");
150+
151+
llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module);
152+
L = ngx_http_lua_get_lua_vm(r, NULL);
153+
154+
/* load Lua inline script (w/ cache) sp = 1 */
155+
rc = ngx_http_lua_cache_loadbuffer(r->connection->log, L,
156+
llcf->rewrite_src.value.data,
157+
llcf->rewrite_src.value.len,
158+
llcf->rewrite_src_key,
159+
(const char *)
160+
llcf->rewrite_chunkname);
161+
if (rc != NGX_OK) {
162+
return NGX_HTTP_INTERNAL_SERVER_ERROR;
163+
}
164+
165+
return ngx_http_lua_rewrite_by_chunk(L, r);
166+
}
167+
```
168+
169+
这个函数通过函数 `ngx_http_lua_get_lua_vm` 获取到 Lua VM,然后进行 rewrite 阶段的 Lua code 的“载入”(从缓存里取或者调用 `lua_loadbuffer` 载入)
170+
最后调用 `ngx_http_lua_rewrite_by_chunk ` 运行 Lua chunk。下面就来分析下这些过程
171+
172+
### ngx_http_lua_get_lua_vm
173+
174+
首先,Lua VM 是怎么取到的呢?请看 `ngx_http_lua_get_lua_vm`
175+
176+
```c
177+
static ngx_inline lua_State *
178+
ngx_http_lua_get_lua_vm(ngx_http_request_t *r, ngx_http_lua_ctx_t *ctx)
179+
{
180+
ngx_http_lua_main_conf_t *lmcf;
181+
182+
if (ctx == NULL) {
183+
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
184+
}
185+
186+
if (ctx && ctx->vm_state) {
187+
return ctx->vm_state->vm;
188+
}
189+
190+
lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module);
191+
dd("lmcf->lua: %p", lmcf->lua);
192+
return lmcf->lua;
193+
}
194+
```
195+
196+
代码非常简单,首先是尝试从 `lua-nginx-module` 模块上下文里取 Lua VM,但是有可能此时 ctx 还没有被创建出来,所以在 ctx 没有被创建出来的情况下,就从 `lua-nginx-module` 的 main 配置结构体里取出 Lua VM
197+
198+
### ngx_http_lua_cache_loadbuffer
199+
200+
接下来我们看下 得到 Lua chunk 的过程,也就是 `ngx_http_lua_cache_loadbuffer`
201+
这个函数
202+
203+
```c
204+
ngx_int_t
205+
ngx_http_lua_cache_loadbuffer(ngx_log_t *log, lua_State *L,
206+
const u_char *src, size_t src_len, const u_char *cache_key,
207+
const char *name)
208+
{
209+
int n;
210+
ngx_int_t rc;
211+
const char *err = NULL;
212+
213+
n = lua_gettop(L);
214+
215+
dd("XXX cache key: [%s]", cache_key);
216+
217+
rc = ngx_http_lua_cache_load_code(log, L, (char *) cache_key);
218+
if (rc == NGX_OK) {
219+
/* code chunk loaded from cache, sp++ */
220+
dd("Code cache hit! cache key='%s', stack top=%d, script='%.*s'",
221+
cache_key, lua_gettop(L), (int) src_len, src);
222+
return NGX_OK;
223+
}
224+
225+
if (rc == NGX_ERROR) {
226+
return NGX_ERROR;
227+
}
228+
229+
/* rc == NGX_DECLINED */
230+
231+
dd("Code cache missed! cache key='%s', stack top=%d, script='%.*s'",
232+
cache_key, lua_gettop(L), (int) src_len, src);
233+
234+
/* load closure factory of inline script to the top of lua stack, sp++ */
235+
rc = ngx_http_lua_clfactory_loadbuffer(L, (char *) src, src_len, name);
236+
237+
if (rc != 0) {
238+
/* Oops! error occurred when loading Lua script */
239+
if (rc == LUA_ERRMEM) {
240+
err = "memory allocation error";
241+
242+
} else {
243+
if (lua_isstring(L, -1)) {
244+
err = lua_tostring(L, -1);
245+
246+
} else {
247+
err = "unknown error";
248+
}
249+
}
250+
251+
goto error;
252+
}
253+
254+
/* store closure factory and gen new closure at the top of lua stack to
255+
* code cache */
256+
rc = ngx_http_lua_cache_store_code(L, (char *) cache_key);
257+
if (rc != NGX_OK) {
258+
err = "fail to generate new closure from the closure factory";
259+
goto error;
260+
}
261+
262+
return NGX_OK;
263+
264+
error:
265+
266+
ngx_log_error(NGX_LOG_ERR, log, 0,
267+
"failed to load inlined Lua code: %s", err);
268+
lua_settop(L, n);
269+
return NGX_ERROR;
270+
}
271+
```
272+
273+
还记得之前所说的 `chunkname` 和 `rewrite_src_key`?没错,那两个字符串的用武之地就是在这里!我们来分析下这个函数,这里有个缓存状态机,步骤如下
274+
275+
1. 调用 `ngx_http_lua_cache_load_code`,判断当前的 Lua chunk 有没有缓存,得到返回码,如果返回码为 NGX_OK,跳到第二步;如果返回码是 `NGX_ERROR`,跳到第三步;否则跳到第四步
276+
2. 从缓存中拿到 Lua chunk 且被压入到栈,返回 NGX_OK
277+
3. 出错,返回 NGX_ERROR
278+
4. 缓存 Miss,从原生的 Lua 代码加载,然后压栈,如果出错,记录错误日志然后返回 `NGX_ERROR`;否则返回 `NGX_OK`
279+
280+
这里对 `ngx_http_lua_cache_load_code `,`ngx_http_lua_clfactory_loadbuffer ` 和 `ngx_http_lua_cache_store_code ` 这三个具体的函数不展开分析,有兴趣可以自行阅读
281+
282+
283+
### ngx_http_lua_rewrite_by_chunk
284+
285+
恩,现在 Lua chunk 拿到了而且已经在栈顶了,下面的任务就是把它跑起来了
113 KB
Loading
141 KB
Loading

0 commit comments

Comments
 (0)