1. 程式人生 > >ngx lua模組原始碼簡單解析

ngx lua模組原始碼簡單解析

對nginx lua模組的整個流程,原理簡單解析。由於nginx lua模組相關配置,指令,API非常多,所以本文件只以content_by_lua指令舉例說明。

讀本文件最好配合讀原始碼. 不適合對nginx和lua一點都不瞭解的人看。

1.相關配置

詳細配置見 https://github.com/openresty/lua-nginx-module#installation

2.原始碼解析

src/ngx_http_lua_module.c為模組主入口檔案
模組型別:NGX_HTTP_MODULE

解析配置階段(從上到下按執行順序)               
函式名 作用
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

2.1配置

由於配置太多。 使用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 },
。。。
}



content_by_lua
語法
:         content_by_lua <lua-script-str>
預設值:      無
上下文:       location, location if
說明:           行為類似與一個“content handler”,給每個請求執行定義於lua-script-str中的lua code。這lua code也許會呼叫API calls,並且在一個獨立的全域性環境(例如 沙盒)中作為一個新的spawned coroutine執行
不要將這個指令和其他content handler指令在相同的location中使用。例如這個指令和proxy_pass指令不能同時使用在相同的location中



2.1.1 Main configuration

結構體 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







2.1.2 Location configuration

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

2.2 配置解析

2.2.1 content_by_lua指令解析

ngx_http_lua_content_by_lua

合法性檢查
1.將lua code儲存在location配置中,key是“nhli_” + md5(src) + ‘\0’
2.註冊在content handler階段執行的函式ngx_http_lua_content_handler

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


2.2.2 Main部分配置預設值

ngx_http_lua_init_main_conf
如果以下配置沒被顯式指定,那麼將被設定成以下值
ngx_http_lua_main_conf_t
regex_cache_max_entries    1024
regex_match_limit                   0
max_pending_timers              1024
max_running_timers               256

2.2.3 Loc部分配置合併

如果在http 裡面和location裡面都有這個配置,那麼就以location配置為準

2.2.4 解析完配置後初始化操作

在函式ngx_http_lua_init中
功能:
1.建立lua_State
2.註冊nginx api for lua。 註冊lua code cache regex cache socket pool等全域性變數

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

詳見本文件4.2

2.3 請求處理

2.3.1 Content handler原始碼解析

1.編譯lua code成位元組碼,或者從code cache中得到位元組碼(code cache開關開啟)
2.如果code cache開啟,儲存位元組碼
3.新建coroutine
4.將全域性變數設為coroutine位元組碼的環境
5.將request結構體指標存入coroutine全域性變數
6.執行coroutine
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);
}


ngx_http_handler->ngx_http_core_run_phases->ngx_http_core_content_phase->ngx_http_lua_content_handler

3. 使用介面

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

 內容產生階段完成以後,生成的輸出會被傳遞到filter模組去進行處理。filter模組也是與location相關的。所有的fiter模組都被組織成一條鏈。輸出會依次穿越所有的filter,直到有一個filter模組的返回值表明已經處理完成。
比如gzip有關的。



4. C語言嵌入lua原理

C作為程式語言, lua作為擴充套件語言
C和lua之間通訊關鍵內容是一個虛擬的棧。幾乎所有的API呼叫都是對棧上的值進行操作。資料交換也通過棧。

4.1 協程(coroutine)

每個nginx work程序有一個lua_State結構,在Postconfiguration階段建立,見4.2.4
lua_State表示一個獨立的lua虛擬機器,會建立自己的棧。

所有的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_State *lua_newthread (lua_State *L)
建立一個coroutine. 將其壓入堆疊。返回維護這個coroutine的lua_State指標,
新的狀態機繼承共享原有狀態機中的所有物件(比如一些 table), 但是它有獨立的執行堆疊。



4.2 nginx與lua之間的資料互動

4.2.1 將request結構體指標存入lua全域性變數

程式碼位置:lua-nginx-module/src/ngx_http_lua_util.h:ngx_http_lua_set_req
這段程式碼只有三行,在每個請求過來後,lua code執行前,都會執行

  lua_pushliteral(L, "__ngx_req");//將字串"__ngx_req"壓棧
    lua_pushlightuserdata(L, r);// 將request指標壓棧r壓棧
  lua_rawset(L, LUA_GLOBALSINDEX);
  //將request指標存入全域性變數  global[“__ngx_req”] = r

    
lua_rawset類似於 lua_settable (lua_State *L, int index);,
作一個等價於 t[k] = v 的操作, 這裡 t 是一個給定有效索引 index 處的值, v 指棧頂的值, 而 k 是棧頂之下的那個值。

4.2.2 從lua全域性變數得到request結構體指標

程式碼位置:slua-nginx-module/src/ngx_http_lua_util.h:ngx_http_lua_get_req

為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;
}



4.2.3 註冊C函式,處理request指標

所有的nginx api for lua註冊在
lua-nginx-module/src/ngx_http_lua_util.c:ngx_http_lua_inject_ngx_api中
與request有關的註冊在
lua-nginx-module/src/ngx_http_lua_util.c:ngx_http_lua_inject_req_api中

舉例說,註冊處理method的函式ngx.req.get_method:
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;
}



4.2.4 在lua指令碼中呼叫C函式,處理request

在4.3.3中,我們註冊了ngx.req.get_method方法。我們可以在nginx的配置檔案中呼叫。
語法: method_name = ngx.req.get_method()
上下文: set_by_lua, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua**
獲取當前請求的請求方法名。得到像“GET”和”POST”這樣的字串
如果當前的請求是nginx 子請求,那麼子請求的方法名將返回

配置舉例

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);
        ';
    }
}


結果:

[email protected]:~/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




C如何從lua中得到資料

在4.2.4中content_by_lua指令指定的lua語句中,呼叫了ngx.say() 函式,
ngx.say函式定義在lua-nginx-module/src/ngx_http_lua_output.c:ngx_http_lua_ngx_say
實際上呼叫的是ngx_http_lua_ngx_echo函式。

這個函式呼叫了lua_tolstring lua_toboolean lua_touserdata等函式,將lua棧中的資料彈出棧,轉化成C裡面的資料,然後nginx做相應的處理,

int lua_toboolean (lua_State *L, int index)                                                                              把指定的索引處的的 Lua 值轉換為一個 C 中的 boolean 值( 0 或是 1 )。
lua_tocfunction                                                                                                                              把給定索引處的 Lua 值轉換為一個 C 函式。 這個值必須是一個 C 函式;如果不是就返回 NULL 。
lua_tointeger                                                                                                                                 把給定索引處的 Lua 值轉換為 lua_Integer 這樣一個有符號整數型別。 這個 Lua 值必須是一個數字或是一個可以轉換為數字的字串 (參見 §2.2.1); 否則,lua_tointeger 返回 0
lua_tolstring                                                                                                                                   把給定索引處的 Lua 值轉換為一個 C 字串。 如果 len 不為 NULL , 它還把字串長度設到 *len 中。 這個 Lua 值必須是一個字串或是一個數字; 否則返回返回 NULL
lua_tonumber                                                                                                                                把給定索引處的 Lua 值轉換為 lua_Number 這樣一個 C 型別。就是double型別
lua_tothread                                                                                                                                   把給定索引處的值轉換為一個 Lua 執行緒
lua_touserdata                                                                                                                              如果給定索引處的值是一個完整的 userdata ,函式返回記憶體塊的地址。 如果值是一個 light userdata ,那麼就返回它表示的指標。 否則,返回 NULL 。
Lua_type                                                                                                                                          LUA_TNIL , LUA_TNUMBER , LUA_TBOOLEAN , LUA_TSTRING , LUA_TTABLE , LUA_TFUNCTION , LUA_TUSERDATA , LUA_TTHREAD , LUA_TLIGHTUSERDATA

ngx_http_lua_ngx_echo函式呼叫了

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);
          }
      }
  。。。
}


相關推薦

ngx lua模組原始碼簡單解析

對nginx lua模組的整個流程,原理簡單解析。由於nginx lua模組相關配置,指令,API非常多,所以本文件只以content_by_lua指令舉例說明。 讀本文件最好配合讀原始碼. 不適合對nginx和lua一點都不瞭解的人看。 1.相關配置 詳細配置見 http

caffe原始碼簡單解析——Blob(1)

使用caffe也有一段時間了,但更多是使用Python的介面,使用現有的ImageNet訓練好的模型進行圖片分類。為了更好的瞭解caffe這個框架,也為了提高自己的水平,在對卷積神經網路有了一些研究之後,終於開始研讀caffe的原始碼了,今天看了Blob類的一些內容,做個總

Android網路框架:OKHttp原始碼簡單解析(一)

這是第一次解析原始碼並把它寫出來,在之前,我一直以為只要會用別人的輪子就好,做出實際的效果就行,對看原始碼對自己的能力提升不以為然。後來偶然聽到一句話:看別人的DEMO,你就可以會用輪子,但是要想用好輪子,還是得看原始碼。我覺得看原始碼有兩個方面的好處: 1

ThreadLocal原始碼簡單解析

ThreadLocal    ThreadLocal我一開始接觸的時候,以為是“本地執行緒”搞的我雲裡霧裡的,看了內部實現後,這個Local應該稱為“區域性”。    在《多執行緒併發程式設計實戰》提到:維持執行緒封閉性的一種規範方法,這個類為每個使用該變數的執行緒都存有一份

C#軟體授權、註冊、加密、解密模組原始碼解析並製作註冊機生成license

最近做了一個綠色免安裝軟體,領導臨時要求加個註冊機制,不能讓現場工程師隨意複製。事出突然,只能在現場開發(離開現場軟體就不受我們控了)。花了不到兩個小時實現了簡單的註冊機制,稍作整理。        基本原理:1.軟體一執行就把計算機的CPU、主機板、BIOS、MAC地

Spring原始碼深度解析-1、Spring核心類簡單介紹

在更新JAVA基礎原始碼學習的同時,也有必要把Spring抓一抓,以前對於spring的程度僅在於使用,以及一點IOC/AOP的概念,具體深層的瞭解不是很深入,每次看了一點原始碼就看不下去,然後一轉眼都忘記看了啥。 所以這次專門買了書,來細細品味下Spring。 希望能從這一波學習中加強自己

django---admin模組原始碼解析

django有一套強大的admin後臺資料庫管理工具,通過url(r'^admin/', admin.site.urls)完成對已註冊model的增刪改成,註冊方法是admin.site.register(Publish) 我們建立一個app,然後建立一個mo

原始碼安裝nginx並配置Lua模組

今天需要在測試機佈置nginx測試環境,測試機不能聯網。所以只能用scp上傳nginx原始碼安裝。期間遇到不少問題 1.下載nginx原始碼 $wget 'http://sysoev.ru/nginx/nginx-1.2.6.tar.gz' 2.下載lua-nginx-

python原始碼PyObject簡單解析

1. python一切皆物件, 物件定義object.h /* Define pointers to support a doubly-linked list of all live heap objects. */ #define _PyObject_HEAD_EXTR

nginx原始碼閱讀(十五).事件模組小結(從解析配置到事件的處理)

前言 本小節主要是整理一下前幾節分析的nginx的核心模組的ngx_events_module以及事件模組,關於事件模組什麼時候初始化以及事件的處理等,因此不會涉及到太多具體的程式碼,主要是把握事件模組的整體。 配置項結構體的建立及賦值 在使

(一) Mybatis原始碼分析-解析模組

Mybatis原始碼分析-解析器模組 原創-轉載請說明出處 1. 解析器模組的作用 對XPath進行封裝,為mybatis-config.xml配置檔案以及對映檔案提供支援 為處理動態 SQL 語句中的佔位符提供支援 2. 解析器模組parsing包 3. 解析器模組parsing包 Gene

「從零單排canal 05」 server模組原始碼解析

基於1.1.5-alpha版本,具體原始碼筆記可以參考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_reading/canal 本文將對canal的server模組進行分析,跟之前一樣,我們帶著幾個問題來看原始碼

「從零單排canal 06」 instance模組原始碼解析

基於1.1.5-alpha版本,具體原始碼筆記可以參考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_reading/canal instance模組比較簡單,我們重點了解以下幾個問題 instance配置模式有

「從零單排canal 07」 parser模組原始碼解析

基於1.1.5-alpha版本,具體原始碼筆記可以參考我的github:https://github.com/saigu/JavaKnowledgeGraph/tree/master/code_reading/canal 本文將對canal的binlog訂閱模組parser進行分析。 parser模組(綠

使用lua-nginx模組實現請求解析與排程

**系統版本及需求**: ``OS``:CentOS 7.7.1908 ``OpenResty``:1.15.8.2 [TOC] # 描述 lua-nginx-module模組是什麼: > It is a core component of OpenResty. If you are usin

MySQL配置文件簡單解析

spa art ini update rep 模式 buffer lda reads 1 [mysqld] 2 basedir = /data/mysql 3 datadir = /data/mysqldata 4 tmpd

http協議簡單解析

orm 列表 partial 關閉連接 connect 時移 通過 ont sat HTTP協議(轉載自牛客網不知名大神) 1.簡介   HTTP協議(Hyper Text Transfer Protocol,超文本傳輸協議),是用於從萬維網(WWW:World Wide

lua中是 ffi 解析 【是如何處理數據包的/pkt是如何傳進去的】 fsfsfs

and 賦值 true dst cati multipl 又是 -- light lua中的ffi是如何解析的呢? 拿bcc中對proto的解析說起; metatype是有大學問的: ffi.metatype(ffi.typeof(‘struct ip_t‘), {

[ 轉載 ] Java基礎10--關於Object類下所有方法的簡單解析

zed final關鍵字 pro target 解釋 temp cat turn syn 關於Object類下所有方法的簡單解析 類Object是類層次結構的根類,是每一個類的父類,所有的對象包括數組,String,Integer等包裝類,所以了解Object是很有必要

Java之dom4j的簡單解析和生成xml的應用

util 讀寫 pro artifact gettext depend bject sta rgs   一、dom4j是一個Java的XML API,是jdom的升級品,用來讀寫XML文件的。dom4j是一個十分優秀的JavaXML API,具有性能優異、功能強大和極其易使