152Nginx 運(yùn)維基礎(chǔ)入門--Nginx模塊開(kāi)發(fā)實(shí)驗(yàn)

Nginx模塊開(kāi)發(fā)實(shí)驗(yàn)

實(shí)驗(yàn)原理

下面介紹有關(guān)實(shí)驗(yàn)的原理。
模塊的代碼通過(guò)如下命令下載:

wget -q https://labfile.oss.aliyuncs.com/courses/95/ngx_http_echo_module.tar.gz
tar xzf ngx_http_echo_module.tar.gz
image.png

建議完全沒(méi)有 Nginx 模塊編寫經(jīng)驗(yàn)的學(xué)習(xí)者對(duì)照代碼和文檔進(jìn)行學(xué)習(xí),在了解之后嘗試獨(dú)立撰寫模塊代碼。

# 進(jìn)入模塊代碼所在的文件夾
cd ngx_http_echo_module

# 使用 vim 編輯模塊代碼
vim ngx_http_echo_module.c

Nginx 模塊工作原理回顧

我們?cè)谏弦徽碌?nginx 模塊與進(jìn)程中講過(guò):當(dāng) Nginx 接到一個(gè) HTTP 請(qǐng)求時(shí),它僅僅是通過(guò)查找配置文件將此次請(qǐng)求映射到一個(gè) location block,而此 location 中所配置的各個(gè)指令則會(huì)啟動(dòng)不同的模塊去完成工作,因此模塊可以看做 Nginx 真正的勞動(dòng)工作者。
通常一個(gè) location 中的指令會(huì)涉及一個(gè) handler 模塊和多個(gè) filter 模塊(當(dāng)然,多個(gè) location 可以復(fù)用同一個(gè)模塊)。handler 模塊(這一章實(shí)驗(yàn)的重點(diǎn))負(fù)責(zé)處理請(qǐng)求,完成響應(yīng)內(nèi)容的生成,而 filter 模塊對(duì)響應(yīng)內(nèi)容進(jìn)行處理。因此 Nginx 模塊開(kāi)發(fā)分為 handler 開(kāi)發(fā)和 filter 開(kāi)發(fā)。

在這次實(shí)驗(yàn)中,我們需要參照 Nginx 的工作原理,開(kāi)發(fā)一個(gè)叫 echo 的 handler 模塊,這個(gè)模塊功能非常簡(jiǎn)單,它接收 “echo” 指令,指令可指定一個(gè)字符串參數(shù),模塊會(huì)輸出這個(gè)字符串作為 HTTP 響應(yīng),直觀來(lái)看,要實(shí)現(xiàn)這個(gè)功能需要三步:
1、讀入配置文件中 echo 指令及其參數(shù)
2、進(jìn)行 HTTP 包裝(添加 HTTP 頭等工作)
3、將結(jié)果返回給客戶端

模塊配置結(jié)構(gòu)

前面我們已經(jīng)使用 vim 編輯器打開(kāi) ngx_http_echo_module.c 了,為了后面實(shí)驗(yàn)講解的方便,我們還需要設(shè)置顯示行號(hào)。方式為:在剛進(jìn)入 vim 的界面按下 Shift + ;,這時(shí)光標(biāo)會(huì)移動(dòng)到 vim 編輯器的最末行(即進(jìn)入末行模式),輸入 set nu 并回車, 這時(shí) vim 就顯示行號(hào)了。
首先這里要說(shuō)一下,這里的模塊配置的命令根據(jù) Nginx 模塊開(kāi)發(fā)規(guī)則(也為了方便閱讀),這個(gè)結(jié)構(gòu)的命名規(guī)則為 ngx_http_[module-name]_[main|srv|loc]_conf_t,中間是模塊名稱,后面是表示模塊運(yùn)行在哪一層。

// 參考代碼從 5 行起
typedef struct {
    ngx_str_t  ed;
} ngx_http_echo_loc_conf_t;

第一個(gè)結(jié)構(gòu)體用于存儲(chǔ)從配置文件中讀進(jìn)來(lái)的相關(guān)指令參數(shù),即模塊配置信息結(jié)構(gòu)。其中字符串 ed 用于存儲(chǔ) echo 指令指定的需要輸出的字符串。

// Nginx 源碼的 src/core/ngx_string.h 中
typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

上面這兩個(gè)字段分別表示字符串的長(zhǎng)度和數(shù)據(jù)起始地址。注意在 Nginx 源代碼中對(duì)數(shù)據(jù)類型進(jìn)行了別稱定義,如 ngx_int_t 為 intptr_t 的別稱,為了保持一致,在開(kāi)發(fā) Nginx 模塊時(shí)也應(yīng)該使用這些 Nginx 源碼定義的類型而不要使用 C 原生類型。
除了 ngx_str_t 外,其它三個(gè)常用的 nginx type 分別為:

// 在 Nignx 源碼的 src/core/ngx_config.h
typedef intptr_t        ngx_int_t;
typedef uintptr_t       ngx_uint_t;
typedef intptr_t        ngx_flag_t;

這些就是最基礎(chǔ)的配置文件結(jié)構(gòu),總的來(lái)說(shuō):兩個(gè)結(jié)構(gòu)體,一個(gè)類型定義。

模塊配置指令

我們要清楚 echo 模塊需要接受指令“echo”。
Nginx 模塊使用一個(gè) ngx_command_s 數(shù)組表示模塊所能接受的所有指令,其中每一個(gè)元素表示一個(gè)條指令(這是在 nginx 中常用的模式)。ngx_command_t 是 ngx_command_s 的一個(gè)別稱(Nginx 習(xí)慣于使用“_s”后綴命名結(jié)構(gòu)體,然后 typedef 一個(gè)同名 _t 后綴名稱作為此結(jié)構(gòu)體的類型名)

// 在 Nignx 源碼的 src/core/ngx_conf_file.h
struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char            *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    ngx_uint_t            conf;
    ngx_uint_t            offset;
    void                 *post;
};

// 留意這一行,接下來(lái)要用
#define ngx_null_command  { ngx_null_string, 0, NULL, 0, 0, NULL }

name: 配置指令的名稱。
type: 該配置的類型,其實(shí)更準(zhǔn)確一點(diǎn)說(shuō),是該配置指令屬性的集合。nginx 提供了很多預(yù)定義的屬性值(一些宏定義),通過(guò)邏輯或運(yùn)算符可組合在一起,形成對(duì)這個(gè)配置指令的詳細(xì)的說(shuō)明。相關(guān)可用 type 定義在 Nginx 源碼包中的 src/core/ngx_config_file.h。
set: 是一個(gè)函數(shù)指針,用于指定一個(gè)參數(shù)轉(zhuǎn)化函數(shù),這個(gè)函數(shù)一般是將配置文件中相關(guān)指令的參數(shù)轉(zhuǎn)化成需要的格式并存入配置結(jié)構(gòu)體。Nginx 預(yù)定義了一些轉(zhuǎn)換函數(shù),可以方便我們調(diào)用,這些函數(shù)定義在 core/ngx_confile.h 中,一般以“_slot”結(jié)尾,例如 ngx_conf_set_flag_slot 將“on 或 off”轉(zhuǎn)換為“1 或 0”,再如 ngx_conf_set_str_slot 將裸字符串轉(zhuǎn)化為 ngx_str_t。
conf: 用于指定 Nginx 相應(yīng)配置文件內(nèi)存起始地址,一般可以通過(guò)內(nèi)置常量指定,如 NGX_HTTP_LOC_CONF_OFFSET,offset 指定此條指令的參數(shù)的偏移量。

下面是 echo 模塊的指令定義:

// 參考代碼第 8 行
// 這是參數(shù)轉(zhuǎn)化函數(shù)的函數(shù)原型,接下來(lái)會(huì)講解
static char *
ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

// 參考代碼從 12 行起
static ngx_command_t  ngx_http_echo_commands[] = {
    { ngx_string("echo"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_echo,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_echo_loc_conf_t, ed),
        NULL },
    ngx_null_command
};

NGX_HTTP_LOC_CONF: 可以出現(xiàn)在 http server 塊里面的 location 配置指令里。
NGX_CONF_TAKE1:配置指令接受 1 個(gè)參數(shù)。
offset: 指定該配置項(xiàng)值的精確存放位置,一般指定為某一個(gè)結(jié)構(gòu)體變量的字段偏移。因?yàn)閷?duì)于配置信息的存儲(chǔ),一般我們都是定義一個(gè)結(jié)構(gòu)體來(lái)存儲(chǔ)的。比如我們定義了一個(gè)結(jié)構(gòu)體 A,該項(xiàng)配置的值需要存儲(chǔ)到該結(jié)構(gòu)體的 b 字段,那么在這里就可以填寫為 offsetof(A, b)。對(duì)于有些配置項(xiàng),它的值不需要保存或者是需要保存到更為復(fù)雜的結(jié)構(gòu)中時(shí),這里可以設(shè)置為 0。
ngx_http_hello_commands: 這個(gè)數(shù)組每 5 個(gè)元素為一組,用來(lái)描述一個(gè)配置項(xiàng)的所有情況。那么如果有多個(gè)配置項(xiàng),只要按照需要再增加 5 個(gè)對(duì)應(yīng)的元素對(duì)新的配置項(xiàng)進(jìn)行說(shuō)明。
需要注意的是,ngxhttp[module-name]_commands 這個(gè)數(shù)組定義的最后,都要加一個(gè) ngx_null_command 作為結(jié)尾。

參數(shù)轉(zhuǎn)化函數(shù)的代碼為:

// 參考代碼從 91 行起
static char *
ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_echo_handler;
    ngx_conf_set_str_slot(cf,cmd,conf);
    return NGX_CONF_OK;
}

這個(gè)函數(shù)除了調(diào)用 ngx_conf_set_str_slot 轉(zhuǎn)化 echo 指令的參數(shù)外,還修改了核心模塊配置(也就是這個(gè) location 的配置),將其 handler 替換為我們編寫的 handler:ngx_http_echo_handler。
如果不替換的話,他會(huì)自動(dòng)調(diào)用默認(rèn)的 handler 模塊,現(xiàn)在就可以使用 ngx_http_echo_handler 產(chǎn)生 HTTP 響應(yīng)。

定義模塊 Context

下一步是定義模塊 Context。
這里首先需要定義一個(gè) ngx_http_module_t 類型的結(jié)構(gòu)體變量(命名規(guī)則為 ngx_http_[module-name]_module_ctx),這個(gè)結(jié)構(gòu)主要用于定義各個(gè) Hook 函數(shù)。下面是 echo 模塊的 context 結(jié)構(gòu):

// 參考代碼從 22 行起
static ngx_http_module_t  ngx_http_echo_module_ctx = {
    NULL,                 /* preconfiguration */
    NULL,                  /* postconfiguration */
    NULL,               /* create main configuration */
    NULL,                /* init main configuration */
    NULL,             /* create server configuration */
    NULL,              /* merge server configuration */
ngx_http_echo_create_loc_conf,/* create location configration */
ngx_http_echo_merge_loc_conf/* merge location configration */
};

可以看到一共有 8 個(gè) Hook 注入點(diǎn),分別會(huì)在不同時(shí)刻被 Nginx 調(diào)用,由于我們的模塊僅僅用于 location 域,這里將不需要的注入點(diǎn)設(shè)為 NULL 即可。
其中 create_loc_conf 用于初始化一個(gè)配置結(jié)構(gòu)體,如為配置結(jié)構(gòu)體分配內(nèi)存等工作;merge_loc_conf 用于將其父 block 的配置信息合并到此結(jié)構(gòu)體中,也就是實(shí)現(xiàn)配置的繼承。這兩個(gè)函數(shù)會(huì)被 Nginx 自動(dòng)調(diào)用。
這里是 echo 模塊的兩個(gè)函數(shù):

// 參考代碼從 100 行起
static void *
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_echo_loc_conf_t  *conf;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->ed.len = 0;
    conf->ed.data = NULL;
    return conf;
}
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_echo_loc_conf_t *prev = parent;
    ngx_http_echo_loc_conf_t *conf = child;
    ngx_conf_merge_str_value(conf->ed, prev->ed, "");
    return NGX_CONF_OK;
}

其中 ngx_pcalloc 用于在 Nginx 內(nèi)存池中分配一塊空間,是 pcalloc 的一個(gè)包裝。使用 ngx_pcalloc 分配的內(nèi)存空間不必手工 free,Nginx 會(huì)自行管理,在適當(dāng)時(shí)釋放。
create_loc_conf 新建一個(gè) ngx_http_echo_loc_conf_t,分配內(nèi)存,并初始化其中的數(shù)據(jù),然后返回這個(gè)結(jié)構(gòu)的指針;
而 merge_loc_conf 將父 block 域的配置信息合并到 create_loc_conf 新建的配置結(jié)構(gòu)體中。

handler 模塊

handler 模塊處理的結(jié)果通常有三種情況:處理成功,處理失敗(處理的時(shí)候發(fā)生了錯(cuò)誤)或者是拒絕處理。在拒絕處理的情況下,這個(gè) location 的處理就會(huì)由默認(rèn)的 handler 模塊來(lái)進(jìn)行處理(例如,當(dāng)請(qǐng)求一個(gè)靜態(tài)文件的時(shí)候,如果關(guān)聯(lián)到這個(gè) location 上的一個(gè) handler 模塊拒絕處理,就會(huì)由默認(rèn)的 ngx_http_static_module 模塊進(jìn)行處理,該模塊是一個(gè)典型的 handler 模塊。)。
這個(gè)模塊是核心,前面的都是鋪墊,讓我們稍微整理一下思路,回顧一下實(shí)現(xiàn)一個(gè) handler 的步驟: 讀入模塊配置,處理功能業(yè)務(wù),產(chǎn)生 HTTP header,產(chǎn)生 HTTP body。

// 參考代碼從 47 行起
static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_http_echo_loc_conf_t *elcf;
    elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
    {
        return NGX_HTTP_NOT_ALLOWED;
    }
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = elcf->ed.len;
    if(r->method == NGX_HTTP_HEAD)
    {
        rc = ngx_http_send_header(r);
        if(rc != NGX_OK)
        {
            return rc;
        }
    }
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if(b == NULL)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf = b;
    out.next = NULL;
    b->pos = elcf->ed.data;
    b->last = elcf->ed.data + (elcf->ed.len);
    b->memory = 1;
    b->last_buf = 1;
    rc = ngx_http_send_header(r);
    if(rc != NGX_OK)
    {
        return rc;
    }
    return ngx_http_output_filter(r, &out);
}

獲取模塊配置信息:這一塊只要簡(jiǎn)單使用 ngx_http_get_module_loc_conf 就可以了。
功能邏輯:因?yàn)?echo 模塊非常簡(jiǎn)單,只是簡(jiǎn)單輸出一個(gè)字符串,所以這里沒(méi)有功能邏輯代碼。
設(shè)置 response header:Header 內(nèi)容可以通過(guò)填充 headers_out 實(shí)現(xiàn),我們這里只設(shè)置了 Content-type 和 Content-length 等基本內(nèi)容,ngx_http_headers_out_t 定義了所有可以設(shè)置的 HTTP Response Header 信息。
輸出 Response body:首先了解下 Nginx 的 I/O 機(jī)制,Nginx 允許 handler 一次產(chǎn)生一組輸出,可以產(chǎn)生多次,Nginx 將輸出組織成一個(gè)單鏈表結(jié)構(gòu),鏈表中的每個(gè)節(jié)點(diǎn)是一個(gè) chain_t,定義在 core/ngx_buf.h 。
到了這里 我們要做的 就只是將這些小模塊集合到一起。

// 參考代碼從 32 行起
ngx_module_t  ngx_http_echo_module = {
    NGX_MODULE_V1,
    &ngx_http_echo_module_ctx,     /* module context */
    ngx_http_echo_commands,     /* module directives */
    NGX_HTTP_MODULE,                  /* module type */
    NULL,                             /* init master */
    NULL,                             /* init module */
    NULL,                            /* init process */
    NULL,                             /* init thread */
    NULL,                            /* exit thread */
    NULL,                            /* exit process */
    NULL,                            /* exit master */
    NGX_MODULE_V1_PADDING
};

模塊可以提供一些回調(diào)函數(shù)給 nginx,當(dāng) nginx 在創(chuàng)建進(jìn)程線程或者結(jié)束進(jìn)程線程時(shí)進(jìn)行調(diào)用。但大多數(shù)模塊在這些時(shí)刻并不需要做什么,所以都簡(jiǎn)單賦值為 NULL。這里主要需要填入的信息從上到下依次為:context、指令數(shù)組、模塊類型以及若干特定事件的回調(diào)處理函數(shù)(不需要可以置為 NULL)。
注意我們的 echo 是一個(gè) HTTP 模塊,所以這里類型是 NGX_HTTP_MODULE,其它可用類型還有 NGX_EVENT_MODULE(事件處理模塊)和 NGX_MAIL_MODULE(郵件模塊)。
下面是 ngx_module_t 的定義,可與 echo 模塊進(jìn)行對(duì)應(yīng):

typedef struct ngx_module_s      ngx_module_t;
struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
    ngx_uint_t            abi_compatibility;
    ngx_uint_t            major_version;
    ngx_uint_t            minor_version;
    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;
    ngx_int_t           (*init_master)(ngx_log_t *log);
    ngx_int_t       (*init_module)(ngx_cycle_t *cycle);
    ngx_int_t      (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t       (*init_thread)(ngx_cycle_t *cycle);
    void            (*exit_thread)(ngx_cycle_t *cycle);
    void           (*exit_process)(ngx_cycle_t *cycle);
    void            (*exit_master)(ngx_cycle_t *cycle);
    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

實(shí)驗(yàn)步驟

下面正式進(jìn)入到我們實(shí)際操作環(huán)節(jié)。

模塊編譯安裝

完成總體模塊的編寫以后,要想模塊能工作,就是安裝編寫好的模塊。因?yàn)?Nginx 不支持動(dòng)態(tài)鏈接模塊,所以安裝模塊需要將模塊代碼與 Nginx 源代碼進(jìn)行重新編譯。

cd
sudo wget https://labfile.oss.aliyuncs.com/nginx-1.7.9.tar.gz
tar xvf nginx-1.7.9.tar.gz

image.png

下載以后再解壓,然后先放在那里吧(由于這個(gè)源在國(guó)外,下載速度很慢,這里我們把文件放在了自己的服務(wù)器上,以加快速度)
config 文件的編寫
把 ngx_http_echo_module 里的 ngx_http_echo_module.c 放在我們新建的 ng 文件夾里,然后在 ng 文件夾里新建一個(gè) config 文件。這個(gè) config 文件的內(nèi)容就是告訴 nginx 的編譯腳本,該如何進(jìn)行編譯。

mkdir -p /home/shiyanlou/Desktop/ng
mv /home/shiyanlou/ngx_http_echo_module/ngx_http_echo_module.c /home/shiyanlou/Desktop/ng
cd /home/shiyanlou/Desktop/ng
sudo vim config

我們來(lái)看一下 hello handler module 的 config 文件的內(nèi)容,然后再做解釋。

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

image.png

編譯源碼
下面我們就開(kāi)始編譯吧。編譯命令之中 后面兩個(gè)參數(shù)是 nginx 安裝路徑和即將添加的模塊路徑。如果你不規(guī)定 nginx 安裝路徑,那你只需要加后面一個(gè)參數(shù)如圖所示(ng 文件夾在桌面上):

cd /home/shiyanlou/nginx-1.7.9
./configure --add-module=/home/shiyanlou/Desktop/ng
image.png

如果出現(xiàn)

./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module

我們可以像它說(shuō)的,去掉 rewrite 模塊進(jìn)行編譯

./configure --without-http_rewrite_module --add-module=/home/shiyanlou/Desktop/ng

然后使用命令:

make && sudo make install

安裝完成以后你可以開(kāi)瀏覽器訪問(wèn)本地測(cè)試下,首先停止 apt 安裝的 nginx。

sudo service nginx stop

然后到源碼文件夾下的 objs 文件夾下執(zhí)行。

cd objs
sudo ./nginx

image.png

然后你再訪問(wèn)下 localhost,可以看到 nginx 已經(jīng)正常運(yùn)行。
修改 http 模塊配置文件
要想達(dá)到訪問(wèn)本地地址回顯你想要的字符串,你就得在 nginx 對(duì)應(yīng)的 http 模塊 server 中加入一個(gè) location 來(lái)執(zhí)行(注意格式),如下圖
image.png

補(bǔ)充:如果你不知道要改的配置文件在哪里,你可以回頭看看,剛在編譯之后的信息:
image.png

你還記得嗎,在修改完配置文件以后,我們還要做什么,第二章中,我們修改好配置的文件,都是采用 reload,但是現(xiàn)在,我們只需要重啟 nginx 就行,他會(huì)自動(dòng)重新加載配置文件。先殺了他,再啟一下就好。

sudo killall nginx

切入 nginx 源碼包下的 objs 文件夾,重新開(kāi)啟 nginx:

sudo ./nginx

結(jié)果展示

現(xiàn)在我們可以訪問(wèn) localhost/echo


image.png

實(shí)驗(yàn)總結(jié)

請(qǐng)大家 完成練習(xí) 后 思考:
configure 這個(gè)過(guò)程干了些什么

./configure --add-module=/home/shiyanlou/Desktop/ng

它指明了我們需要加載哪些模塊,需要什么配置,不需要哪些配置都在這里

編譯這個(gè)過(guò)程干了些什么
編譯也就是通過(guò) make 管理,在我們輸入 make 之后,在 nginx 項(xiàng)目中開(kāi)始將 .c 文件編譯成 .o 然后再將這些鏈接起來(lái),形成我們最后可以運(yùn)行的程序

make install 這個(gè)過(guò)程干了些什么
這其實(shí)就是安裝的一個(gè)過(guò)程,將一些配置文件移動(dòng)要它應(yīng)該呆的地方,大家可以在終端看出它的行為

挑戰(zhàn):寫日志

資源
nginx 源碼

sudo wget https://labfile.oss.aliyuncs.com/nginx-1.7.9.tar.gz

ngx_http_echo_module 源碼

wget -q https://labfile.oss.aliyuncs.com/courses/95/ngx_http_echo_module.tar.gz
tar xzf ngx_http_echo_module.tar.gz

目標(biāo)
每次訪問(wèn)網(wǎng)頁(yè) localhost/echo 的時(shí)候 nginx 都會(huì)把這句話 ngx_http_echo_handler is called! 寫入 error.log。

參考代碼

(1)編輯代碼
參考代碼(其中有中文注釋的地方與上一節(jié)代碼有不同,其他的均相同):

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

/* Module config */
typedef struct {
    ngx_str_t  ed;
} ngx_http_echo_loc_conf_t;

static ngx_log_t *echo_log;              /* 定義日志變量 */

static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);

static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);

/* Directives */
static ngx_command_t ngx_http_echo_commands[] = {
    {
        ngx_string("echo"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_http_echo,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_echo_loc_conf_t, ed),
        NULL
    },
    ngx_null_command
};

/* Http context of the module */
static ngx_http_module_t  ngx_http_echo_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */
    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */
    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */
    ngx_http_echo_create_loc_conf,         /* create location configration */
    ngx_http_echo_merge_loc_conf           /* merge location configration */
};

/* Module */
ngx_module_t  ngx_http_echo_module = {
    NGX_MODULE_V1,
    &ngx_http_echo_module_ctx,             /* module context */
    ngx_http_echo_commands,                /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

/* Handler function */
static ngx_int_t ngx_http_echo_handler(ngx_http_request_t *r)
{
    ngx_int_t rc;
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_http_echo_loc_conf_t *elcf;
    elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
    if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
    {
            return NGX_HTTP_NOT_ALLOWED;
        }
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = elcf->ed.len;
    if(r->method == NGX_HTTP_HEAD)
    {
        rc = ngx_http_send_header(r);
        if(rc != NGX_OK)
        {
            return rc;
        }
    }
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if(b == NULL)
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf = b;
    out.next = NULL;
    b->pos = elcf->ed.data;
    b->last = elcf->ed.data + (elcf->ed.len);
    b->memory = 1;
    b->last_buf = 1;
    rc = ngx_http_send_header(r);
    if(rc != NGX_OK)
    {
        return rc;
    }
    /* 輸出日志 */
    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_echo_handler is called!");
    return ngx_http_output_filter(r, &out);
}

static char* ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_echo_handler;
    ngx_conf_set_str_slot(cf,cmd,conf);
    return NGX_CONF_OK;
}

static void* ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_echo_loc_conf_t  *conf;
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }
    conf->ed.len = 0;
    conf->ed.data = NULL;
    return conf;
}

static char* ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_echo_loc_conf_t *prev = parent;
    ngx_http_echo_loc_conf_t *conf = child;
    ngx_conf_merge_str_value(conf->ed, prev->ed, "");
    echo_log = &cf->cycle->new_log;      /* 初始化日志變量 */
    return NGX_CONF_OK;
}

(2)編譯時(shí)還需開(kāi)啟日志調(diào)試:./configure --with-debug ...
(3)修改配置文件 /usr/local/nginx/conf/nginx.conf
去掉 error_log logs/error.log 前面的 #
(4)查看日志

cat /usr/local/nginx/logs/error.log | grep "ngx_http_echo_handler"
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容