续上一篇,加载完LUA代码之后,再来看看怎么执行LUA。

先来看一个简单的例子, nginx.conf配置一个location:

location /say { 
    content_by_lua '
        ngx.say("hello world")
    ';  
}   

用curl访问这个url,得到输出:

# curl -v localhost/say

< HTTP/1.1 200 OK
< Server: ngx_openresty/1.0.11.19
< Date: Wed, 27 Jun 2012 03:52:47 GMT
< Content-Type: application/octet-stream
< Transfer-Encoding: chunked
< Connection: keep-alive
< 
hello world

上面的response header是nginx默认输出的内容,可以通过ngx.header.xxxx内置变量进行修改。来分析一下代码,看这个“hello world”是如何输出的。

由于是content_by_lua指令,所以对应的处理函数是ngx_http_lua_content_handler_file()ngx_http_lua_content_handler_inline()

/*  load Lua script file (w/ cache)        sp = 1 */
rc = ngx_http_lua_cache_loadfile(L, script_path, llcf->content_src_key,
        &err, llcf->enable_code_cache ? 1 : 0);

if (rc != NGX_OK) {
    if (err == NULL) {
        err = "unknown error";
    }

    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                  "failed to load Lua inlined code: %s", err);

    return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

/*  make sure we have a valid code chunk */
assert(lua_isfunction(L, -1)); //load之后,lua代码实际是一个闭包函数,这里判断是否函数

rc = ngx_http_lua_content_by_chunk(L, r); //进入执行

ngx_http_lua_content_by_chunk()里调用ngx_http_lua_new_thread()生成一个协程(coroutine),再调用ngx_http_lua_run_thread()执行这个coroutine;代码注释都比较清晰,不再赘述。这里还把request变量push进协程的全局表中,索引为ngx_http_lua_request_key

ngx_http_lua_new_thread()生成一个新协程并返回。处理流程如下:

top = lua_gettop(L);

lua_pushlightuserdata(L, &ngx_http_lua_coroutines_key);
lua_rawget(L, LUA_REGISTRYINDEX); //相当于将registry_table[ngx_http_lua_coroutines_key]这个table放到栈顶

cr = lua_newthread(L); //这时栈顶为cr,registry table变为-2

if (cr) {
    /*  { { { inherit coroutine's globals to main thread's globals table
     *  for print() function will try to find tostring() in current
     *  globals table.
     */
    /*  new globals table for coroutine */
    ngx_http_lua_create_new_global_table(cr, 0, 0);

    lua_createtable(cr, 0, 1);
    lua_pushvalue(cr, LUA_GLOBALSINDEX);
    lua_setfield(cr, -2, "__index");
    lua_setmetatable(cr, -2);

    lua_replace(cr, LUA_GLOBALSINDEX);
    /*  } } } */

    *ref = luaL_ref(L, -2); //将cr放入-2位置的registry table并返回引用

    if (*ref == LUA_NOREF) {
        lua_settop(L, top);  /* restore main thread stack */
        return NULL;
    }
}

/*  pop coroutine reference on main thread's stack after anchoring it
 *  in registry */
lua_pop(L, 1); //这里pop之后,gc会回收栈顶的cr,所以需要将cr引用保存起来。也就是上面的luaL_ref()所做的事情。

接下来ngx_http_lua_run_thread()执行协程代码,即执行ngx.say("hello world")。对于lua来讲,ngx.say是一个全局表中注册的c API,所以又会调用之前在加载代码阶段注册的函数ngx_http_lua_ngx_say()。最后调用的是ngx_http_lua_ngx_echo(),主要工作就是从LUA_GLOBALSINDEX中获取nginx request变量,然后将需要输出的内容写入ctx->out,然后发送响应头和body。

发送完数据之后ngx.say就执行完毕,后续没有lua语句所以请求结束。更复杂一点就是类似访问数据库这样的处理,发送完数据之后需要等待返回,这时候就需要从协程之中yield,将控制权交回给nginx主循环,nginx主循环处理IO事件和其他请求,直到这个数据库的响应返回之后才又再次进入这个请求的lua代码。这个也就是proactor模式,下一篇我们来看ngx_luacosocket是如何工作的。


Simon Lee

My blog