ngx_lua中的協程調度(一)

命令行中執行lua

在命令行中調用lua執行一條輸出語句, 以下所示。html

$ luajit -e "print('Hello, Lua')"
Hello, Lua

 

C程序中內嵌Lua運行環境

在C語言中建立Lua運行環境,執行一樣的Lua語句也至關簡單。nginx

#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

const char script[] = "print('Hello, Lua!')";

int main(void) {
    /* 建立一個全局的global_State結構和表明一個協程的lua_State結構,lua_State做爲主協程返回 */
    lua_State   *L = luaL_newstate();
    if (!L) return -1;

    /*  將print, math,string,table等Lua內置的函數庫註冊到協程中 */
    luaL_openlibs(L);

    /*  加載一段Lua代碼,將其編譯成Lua虛擬機的字節碼 */
    int ret = luaL_loadstring(L, script);
    if (ret != 0) {
        return -1;
    }

    /*  在Lua虛擬機中執行前面加載的Lua代碼 */
    //ret = lua_pcall(L, 0, LUA_MULTRET, 0);
    ret = lua_resume(L, 0);
    if (ret != 0) {
        return -1;
    }

    lua_close(L);

    return 0;
}

 

Nginx C模塊嵌入Lua腳本

主要工做

編寫一個簡單的Nginx模塊,在ACCESS階段執行配置中指定的Lua腳本, 主要工做有瀏覽器

  1. 解析配置時建立Lua運行環境
  2. 在ACCESS階段掛在回調函數,執行配置中設置的Lua腳本
  3. 在Nginx配置中的location中增長 access_by_lua "print('Hello, Lua!')"

模塊名爲ngx_http_test_module,源文件爲ngx_http_test_module.c, 文件內容以下。bash

ngx_http_test_module.c文件內容

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>


typedef struct {
    lua_State   *vm;
    ngx_str_t   script;
} ngx_http_test_loc_conf_t;


static ngx_int_t
ngx_http_test_handler(ngx_http_request_t *r);
static ngx_int_t
ngx_http_test_init(ngx_conf_t *cf);
static void *
ngx_http_test_create_loc_conf(ngx_conf_t *cf);


static ngx_command_t ngx_http_test_commands[] = {
    {
        ngx_string("access_by_lua"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_test_loc_conf_t, script),
        NULL },
    ngx_null_command
};


static ngx_http_module_t ngx_http_test_module_ctx = {
    NULL,
    ngx_http_test_init,
    NULL,
    NULL,
    NULL,
    NULL,
    ngx_http_test_create_loc_conf,
    NULL
};


ngx_module_t ngx_http_test_module = {
    NGX_MODULE_V1,
    &ngx_http_test_module_ctx,
    ngx_http_test_commands,
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};


static ngx_int_t
ngx_http_test_handler(ngx_http_request_t *r)
{
    ngx_http_test_loc_conf_t *tlcf = ngx_http_get_module_loc_conf(r, ngx_http_test_module);
    if (tlcf->script.len == 0) {
        return NGX_DECLINED;
    }

    /*  加載一段Lua代碼,將其編譯成Lua虛擬機的字節碼 */
    int ret = luaL_loadstring(tlcf->vm, (const char *)tlcf->script.data);
    if (ret != 0) {
        return -1;
    }

    /*  調用前面加載的Lua代碼 */
    ret = lua_resume(tlcf->vm, 0);
    if (ret != 0) {
        return -1;
    }

    return NGX_DECLINED;
}


static void *
ngx_http_test_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_test_loc_conf_t *conf = NULL;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_test_loc_conf_t));
    if (conf == NULL) return NULL;

    ngx_str_null(&conf->script);

    /* 初始化Lua環境 */
    /* 建立一個全局的global_State結構和表明一個協程的lua_State結構,lua_State做爲主協程返回 */
    lua_State   *L = luaL_newstate();
    if (!L) return NULL;

    /*  將print, math,string,table等Lua內置的函數庫註冊到協程中 */
    luaL_openlibs(L);
    conf->vm = L;

    return conf;
}


static ngx_int_t
ngx_http_test_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt *h;
    ngx_http_core_main_conf_t *cmcf;

    /* 在ACCESS階段掛在回調函數 */
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
    *h = ngx_http_test_handler;

    return NGX_OK;
}

模塊config文件

ngx_addon_name=ngx_http_test_module
HTTP_MODULES="$HTTP_MODULES ngx_http_test_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_test_module.c"

編譯腳本內容

主要是鏈接Lua庫和頭文件,這裏用的是LuaJIT-2.1.0, 若是用的是其餘版本或者lua5.1須要根據須要更改。curl

export LUAJIT_INC=/usr/local/include/luajit-2.1
export LUAJIT_LIB=/usr/local/lib


./configure --with-debug \
    --with-cc-opt='-O0 -I /usr/local/include/luajit-2.1'  \
    --with-ld-opt='-Wl,-rpath,/usr/local/lib -lluajit-5.1' \
    --add-module=$HOME/ngx_http_test_module

nginx.conf

   在location中增長access_by_lua指定執行的Lua代碼。函數

 

daemon off;

events {
    worker_connections  1024;
}

http {
    server {
        listen       80;
        server_name  localhost;
        location / {
	        access_by_lua "print('Hello, Lua!')";
            root   html;
            index  index.html index.htm;
        }

    }
}

運行

編譯成功後啓動Nginx,用curl或瀏覽器訪問, Nginx會在終端輸出lua

$ ./sbin/nginx
Hello, Lua!

若是以daemon方式運行Nginx,可能沒法輸出內容。url

 

Nginx與Lua交互

Nginx的ACCESS階段用來控制是否容許訪問,這裏爲Lua增長一個功能,返回403禁止訪問。spa

增長模塊上下文結構

爲模塊增長一個ngx_http_test_ctx_t結構,保存執行過程當中須要的一些信息。命令行

typedef struct {
    int         status;
} ngx_http_test_ctx_t;

有一個statu的成員,執行Lua腳本後檢查status的值, 若是是403的話就返回NGX_HTTP_FORBIDDEN結束請求。

增長ngx.exit API

這裏仿照lua-nginx-module的作法,增長一個方法ngx.exit, 在Lua中調用ngx.exit(403)時將status值設置爲403。

那麼如何在Lua中修改status的值呢?Nginx中的ngx_http_request_t結構體保存了請求的全部信息,包括各個模塊的上下文,將這個結構體的指針以lightuserdata的方式保存到lua_State的全局變量中獲取便可。

ngx_http_test_handler中保存ngx_http_request_t指針

/* 將r保存到全局變量中,key爲ngx_http_test_req_key */
    lua_pushlightuserdata(tlcf->vm, r);
    lua_setglobal(tlcf->vm, ngx_http_test_req_key);

ngx.exit API的實現

static int
ngx_http_test_ngx_exit(lua_State *L)
{
    int status;
    status = luaL_checkint(L, 1);

    ngx_http_request_t *r;
    lua_getglobal(L, ngx_http_test_req_key);
    r = lua_touserdata(L, -1);

    ngx_http_test_ctx_t *ctx;
    ctx = ngx_http_get_module_ctx(r, ngx_http_test_module);
    ctx->status = status;

    lua_pushboolean(L, 1);
    return 1;
}

 

在函數ngx_http_test_create_loc_conf中,建立全局變量ngx, 類型爲table,將ngx["exit"]值設置爲函數ngx_http_test_ngx_exit。完成ngx.exit 的註冊,在Lua腳本中就能夠經過ngx.exit()的方式調用。

static void *
ngx_http_test_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_test_loc_conf_t *conf = NULL;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_test_loc_conf_t));
    if (conf == NULL) return NULL;

    ngx_str_null(&conf->script);

    /* 初始化Lua環境 */
    /* 建立一個全局的global_State結構和表明一個協程的lua_State結構,lua_State做爲主協程返回 */
    lua_State   *L = luaL_newstate();
    if (!L) return NULL;

    /*  將print, math,string,table等Lua內置的函數庫註冊到協程中 */
    luaL_openlibs(L);

    /* 註冊ngx API */
    lua_createtable(L, 0, 0);
    lua_pushcfunction(L, ngx_http_test_ngx_exit);
    lua_setfield(L, -2, "exit");

    lua_setglobal(L, "ngx");

    conf->vm = L;

    return conf;
}

 

最終的ngx_http_test_module.c

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>


typedef struct {
    lua_State   *vm;
    ngx_str_t   script;
} ngx_http_test_loc_conf_t;


typedef struct {
    int         status;
} ngx_http_test_ctx_t;


static ngx_int_t
ngx_http_test_handler(ngx_http_request_t *r);
static ngx_int_t
ngx_http_test_init(ngx_conf_t *cf);
static void *
ngx_http_test_create_loc_conf(ngx_conf_t *cf);


static ngx_command_t ngx_http_test_commands[] = {
    {
        ngx_string("access_by_lua"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_test_loc_conf_t, script),
        NULL },
    ngx_null_command
};


static ngx_http_module_t ngx_http_test_module_ctx = {
    NULL,
    ngx_http_test_init,
    NULL,
    NULL,
    NULL,
    NULL,
    ngx_http_test_create_loc_conf,
    NULL
};


ngx_module_t ngx_http_test_module = {
    NGX_MODULE_V1,
    &ngx_http_test_module_ctx,
    ngx_http_test_commands,
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};

#define ngx_http_test_req_key    "__ngx_req"

static ngx_int_t
ngx_http_test_handler(ngx_http_request_t *r)
{
    ngx_http_test_loc_conf_t *tlcf = ngx_http_get_module_loc_conf(r, ngx_http_test_module);
    if (tlcf->script.len == 0) {
        return NGX_DECLINED;
    }

    ngx_http_test_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_test_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(*ctx));
        ngx_http_set_ctx(r, ctx, ngx_http_test_module);
    }

    /* 將r保存到全局變量中,key爲ngx_http_test_req_key */
    lua_pushlightuserdata(tlcf->vm, r);
    lua_setglobal(tlcf->vm, ngx_http_test_req_key);

    /*  加載一段Lua代碼,將其編譯成Lua虛擬機的字節碼 */
    int ret = luaL_loadstring(tlcf->vm, (const char *)tlcf->script.data);
    if (ret != 0) {
        return NGX_ERROR;
    }

    /*  調用前面加載的Lua代碼 */
    ret = lua_resume(tlcf->vm, 0);
    if (ret != 0) {
        return NGX_ERROR;
    }

    if (ctx->status == 403) {
        return NGX_HTTP_FORBIDDEN;
    }

    return NGX_DECLINED;
}


static int
ngx_http_test_ngx_exit(lua_State *L)
{
    int status;
    status = luaL_checkint(L, 1);

    ngx_http_request_t *r;
    lua_getglobal(L, ngx_http_test_req_key);
    r = lua_touserdata(L, -1);

    ngx_http_test_ctx_t *ctx;
    ctx = ngx_http_get_module_ctx(r, ngx_http_test_module);
    ctx->status = status;

    lua_pushboolean(L, 1);
    return 1;
}


static void *
ngx_http_test_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_test_loc_conf_t *conf = NULL;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_test_loc_conf_t));
    if (conf == NULL) return NULL;

    ngx_str_null(&conf->script);

    /* 初始化Lua環境 */
    /* 建立一個全局的global_State結構和表明一個協程的lua_State結構,lua_State做爲主協程返回 */
    lua_State   *L = luaL_newstate();
    if (!L) return NULL;

    /*  將print, math,string,table等Lua內置的函數庫註冊到協程中 */
    luaL_openlibs(L);

    /* 註冊ngx API */
    lua_createtable(L, 0, 0);
    lua_pushcfunction(L, ngx_http_test_ngx_exit);
    lua_setfield(L, -2, "exit");

    lua_setglobal(L, "ngx");

    conf->vm = L;

    return conf;
}


static ngx_int_t
ngx_http_test_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt *h;
    ngx_http_core_main_conf_t *cmcf;

    /* 在ACCESS階段掛在回調函數 */
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
    *h = ngx_http_test_handler;

    return NGX_OK;
}

更改nginx.conf配置

location / {
	    access_by_lua "ngx.exit(403)";
            root   html;
            index  index.html index.htm;
        }

編譯後啓動Nginx

訪問時直接返回403 Forbidden.

$ curl 127.0.0.1
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.10.1</center>
</body>
</html>

一樣的方法也能夠用於獲取請求的URL, 頭部等信息。如獲取請求方法的實現

static int
ngx_http_test_req_get_method(lua_State *L)
{
    int status;
    status = luaL_checkint(L, 1);

    ngx_http_request_t *r;
    lua_getglobal(L, ngx_http_test_req_key);
    r = lua_touserdata(L, -1);

    lua_pushlstring(L, (char *) r->method_name.data, r->method_name.len);

    return 1;
}
相關文章
相關標籤/搜索