天天看點

Nginx 子產品開發

作者:Hu先生Linux背景開發

一、在了解Nginx子產品開發前,首先得知道在Nginx中http初始化流程、11個狀态機、http請求具體流程。

(1)conf檔案加載

  對conf檔案内容進行初始化,在指令行執行nginx -c ./conf/nginx.conf的之後,開始解析conf檔案,啟動http子產品(入口:ngx_http_block)。

(2)狀态機初始化

  ngx_http_init_phase_handlers,儲存狀态機每個階段需要處理的handler。

(3)tcp server啟動

  ngx_http_optimize_servers裡面ngx_http_init_listening監聽多個端口,這裡是在master程序監聽。

(4)http處理流程

  業務處理邏輯是在worker程序處理,每一個worker都有一個ngx_worker_process_cycle()函數無限循環的處理,這個函數處理流程:

  (a)accpet_cb接收和處理事件

  (b)處理 request 的 header 和 body

  (c)産生響應,并發送回用戶端

  一個HTTP Request 的處理過程涉及到以下幾個階段:

    (a)初始化 HTTP Request(讀取來自用戶端的資料,生成 HTTP Request 對象);

    (b)處理請求頭;

    (c)處理請求體;

    (d)調用與此請求(location)關聯的handler;

    (e)依次調用各phase handler進行處理。

      每一個phase就是一個階段/狀态,每個狀态包含若幹個handler,依次調用該狀态的handler對HTTP Request進行處理,包括擷取location配置、産生适當的響應、發送response header、發送response body

二、Nginx子產品包括:event、handler、filter、upstream、load-balancer等,本文主要講handler、filter、upstream等子產品的開發;對于這3種子產品的開發,子產品配置指令結構、子產品上下文寫法類似。

  (1)handler子產品

Nginx 子產品開發

  接收用戶端請求并産生輸出的子產品,每個子產品就需要定義三個不同的資料結構去進行存儲。

    (a)子產品配置資訊存儲結構

//以hello子產品為例
typedef struct
{
ngx_str_t hello_string;
ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;           

    (b)子產品配置指令結構

struct ngx_command_s {
ngx_str_t name;//配置指令的名稱
ngx_uint_t type;//該配置的類型,配置接收參數個數
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);//處理指令handler
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};           

    一個子產品的配置指令是定義在一個靜态數組中,包括了指令的名稱、指令類型或接收的參數、處理指令的handler(函數指針,當 nginx 在解析配置的時候,如果遇到這個配置指令,将會把讀取到的值傳遞給這個函數進行分解處理)

    (c)子產品上下文結構

//子產品上下文結構體,存放回調函數指針,根據需求,添加不同狀态相應的處理,否則填NULL
static ngx_http_module_t ctx = {
ngx_int_t (*preconfiguration)(ngx_conf_t *cf),//在建立和讀取該子產品的配置資訊之前被調用
ngx_int_t (*postconfiguration)(ngx_conf_t *cf),//在建立和讀取該子產品的配置資訊之後被調用
void *(*create_main_conf)(ngx_conf_t *cf),//調用該函數建立本子產品位于 http block 的配置資訊存儲結構
char *(*init_main_conf)(ngx_conf_t *cf, void *conf),//調用該函數初始化本子產品位于 http block 的配置資訊存儲結構
void *(*create_srv_conf)(ngx_conf_t *cf),//調用該函數建立本子產品位于 http server block 的配置資訊存儲結構,每個 server block 會建立一個
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf),//有些配置指令既可以出現在 http block,也可以出現在 http server block 中。那麼遇到這種情況,每個 server都會有自己存儲結構來存儲該 server 的配置,但是在這種情況下 http block 中的配置與 server block 中的配置資訊發生沖突的時候,就需要調用此函數進行合并
void *(*create_loc_conf)(ngx_conf_t *cf),//調用該函數建立本子產品位于 location block 的配置資訊存儲結構
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf)//與 merge_srv_conf 類似,這個也是進行配置值合并的地方
};           

通過上面8個回調函數,知道http狀态資訊來源conf檔案解析,conf檔案中http、server、location對應着以上相應的回調函數。

【文章福利】另外小編還整理了一些C/C++背景開發教學視訊,相關面試題,背景學習路線圖免費分享,需要的可以自行添加:點選 正在跳轉 加入~群檔案共享

小編強力推薦C++背景開發免費學習位址:C/C++Linux伺服器開發/背景架構師【零聲教育】-學習視訊教程-騰訊課堂

Nginx 子產品開發

子產品的定義:

//子產品定義
ngx_module_t ngx_http_pagecount_module = {
    NGX_MODULE_V1,//版本,宏定義
    &count_ctx,//子產品上下文
    count_commands,//子產品配置指令
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING//宏定義
};           

  任何子產品都可以從兩個角度分析,一個是抓住執行nginx -c conf/nginx.conf,子產品配置指令的入口handler,另一個就接收用戶端請求時,子產品執行的handler。

  挂載handler,一種是按處理階段挂載;另外一種挂載方式就是按需挂載。

  (a)按處理階段挂載,就是根據需求,可挂載在11個狀态機的某一個階段(在上下文結構體中)

  (b)按需挂載,也被稱為 content handler,某個子產品對某個 location 進行了處理以後,發現符合自己處理的邏輯,而且也沒有必要再調用 NGX_HTTP_CONTENT_PHASE 階段的其它 handler 進行處理的時候,就動态挂載上這個 handler。

(2)Filter子產品

Nginx 子產品開發

  過濾子產品是過濾響應頭和内容的子產品,可以對回複的頭和内容進行處理。過濾子產品的調用是有順序的,它的順序在編譯時就已經确定,可以打開ngx_module.c檔案,按着ngx_modules數組逆序執行。在過濾子產品中,所有輸出的内容都是通過一條單連結清單所組成,Nginx都是讀到一部分的内容就放到連結清單,然後輸出出去(ngx_http_write_filter_module)。

Nginx 子產品開發

  ngx_http_top_header_filter 是一個全局變量。當編譯進一個 filter 子產品的時候,就被指派為目前 filter 子產品的處理函數。而 ngx_http_next_header_filter 是一個局部全局變量,它儲存了編譯前上一個 filter 子產品的處理函數。是以整體看來,就像用全局變量組成的一條單向連結清單。每個子產品想執行下一個過濾函數,隻要調用一下 ngx_http_next_header_filter 這個局部變量。而整個過濾子產品鍊的入口,需要調用 ngx_http_top_header_filter 這個全局變量。ngx_http_top_body_filter 的行為與 header fitler 類似在初始化時,設定過濾塊鍊的入口。

//解析完conf後,設定運作時的回調函數
//使用頭插法,将header_filter和body_filter插入filter隊列頭部
static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_myfilter_header_filter;
 
    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_myfilter_body_filter;
    return NGX_OK;
}           

  響應頭過濾主要的用處是處理http響應頭,可以根據實際情況對響應頭進行修改、添加或删除,而且隻調用一次,是以一般可作過濾子產品的初始化工作,入口函數如下:

static ngx_int_t ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
        ..........
        return ngx_http_top_header_filter(r);
}           

  Nginx把 HTTP響應頭的存儲方式想象成一個 hash 表,在 Nginx 内部可以很友善地查找和修改各個響應頭部,ngx_http_header_filter_module 過濾子產品把所有的 HTTP 頭組合成一個完整的 buffer,最終 ngx_http_write_filter_module 過濾子產品把 buffer 輸出。

  響應體過濾函數,ngx_http_top_body_filter (指向ngx_http_myfilter_body_filter)這個函數每個請求可能會被執行多次,響應體過濾函數的格式類似這樣:

static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ..........
    return ngx_http_next_body_filter(r,c1);
}           

(3)Upstream子產品

Nginx 子產品開發

  upstream子產品實作反向代理的功能,将真正的請求轉發到後端伺服器上,并從後端伺服器上讀取響應,發回用戶端;upstream子產品是一種特殊的handler,隻不過響應内容不是由自己産生,而是從後端伺服器上讀取的。

  upstream一開始的配置指令、子產品上下文等都與handler類似,在 memcached 的例子中,可以觀察 ngx_http_memcached_handler 的代碼,可以發現,這個固定的操作流程是

  a.建立upstream資料結構

if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}           

 b.設定子產品的tag和schema

u = r->upstream;
ngx_str_set(&u->schema, "memcached://");
u->output.tag = (ngx_buf_tag_t) &ngx_http_memcached_module;           

  c.設定upstream的後端伺服器清單資料結構

mlcf = ngx_http_get_module_loc_conf(r,
ngx_http_memcached_module);
u->conf = &mlcf->upstream;           

  d.設定upstream回調函數

  upstream的回調函數清單如下:

Nginx 子產品開發

  此時,回調函數設定如下:

u->create_request = ngx_http_memcached_create_request;
u->reinit_request = ngx_http_memcached_reinit_request;
u->process_header = ngx_http_memcached_process_header;
u->abort_request = ngx_http_memcached_abort_request;
u->finalize_request = ngx_http_memcached_finalize_request;
u->input_filter_init = ngx_http_memcached_filter_init;
u->input_filter = ngx_http_memcached_filter;           

  e.建立并設定upstream環境資料結構

ctx = ngx_palloc(r->pool, sizeof(ngx_http_memcached_ctx_t));
if (ctx == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ctx->rest = NGX_HTTP_MEMCACHED_END;
ctx->request = r;
ngx_http_set_ctx(r, ctx, ngx_http_memcached_module);
u->input_filter_ctx = ctx;           

  f.完成upstream初始化并進行收尾工作

r->main->count++;
ngx_http_upstream_init(r);
return NGX_DONE;           

  其中第 b、d兩步很容易了解,不同的子產品設定的标志和使用的回調函數肯定不同。第 e步也不難了解,隻有第 c步是最為晦澀的,不同的子產品在取得後端伺服器清單時,政策的差異非常大,有如 memcached 這樣簡單明了的,也有如 proxy 那樣邏輯複雜的。這個問題先記下來,等把 memcached 剖析清楚了,再單獨讨論。

  upstream 子產品是從 handler 子產品發展而來,指令系統和子產品生效方式與 handler 子產品無異。不同之處在于,upstream 子產品在 handler 函數中設定衆多回調函數。實際工作都是由這些回調函數完成的。每個回調函數都是在 upstream的某個固定階段執行,各司其職,大部分回調函數一般不會真正用到。 upstream 最重要的回調函數是 create_request 、process_header 和 input_filter,他們共同實作了與後端伺服器的協定的解析部分。

參考資料

Nginx 子產品開發

推薦一個零聲教育C/C++背景開發的免費公開課程,個人覺得老師講得不錯,分享給大家:C/C++背景開發進階架構師,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等技術内容,C/C++Linux伺服器開發/背景架構師【零聲教育】-學習視訊教程-騰訊課堂 立即學習

原文連結:Nginx 子產品開發 - MrJuJu - 部落格園

繼續閱讀