一、如何使用nginx的模塊
1.ngx_code.c:
#include "ngx_config.h"
#include "ngx_conf_file.h"
#include "nginx.h"
#include "ngx_core.h"
#include "ngx_string.h"
#include "ngx_palloc.h"
#include "ngx_array.h"
#include "ngx_hash.h"#include<stdio.h>
#include<stdlib.h>//nginx頭文件中又包含下面的兩個變量,所以我們必須包含#define unused(x) x=xvolatile ngx_cycle_t *ngx_cycle;void
ngx_log_error_core(ngx_uint_t level, ngx_log_t *log, ngx_err_t err,const char *fmt, ...) {unused(level);unused(log);unused(err);unused(fmt);}void print_pool(ngx_pool_t *pool) {printf("\nlast: %p, end: %p\n", pool->d.last, pool->d.end);}int main(){
#if 0ngx_str_t name =ngx_string("jerry");printf("name --> len:%ld, data: %s\n",name.len,name.data);
#else//內存池的使用ngx_pool_t *pool = ngx_create_pool(4096,NULL);print_pool(pool);void *p1 = ngx_palloc(pool,10);print_pool(pool);int *p2 = ngx_palloc(pool,sizeof(int));print_pool(pool);ngx_destroy_pool(pool);#endifreturn 0;
}
2. Makefile:
CXX = gcc
CXXFLAGS += -g -Wall -WextraNGX_ROOT = /home/jerry/snap/nginx/nginx-1.22.1
PCRE_ROOT = /home/jerry/snap/nginx/pcre-8.45TARGETS = ngx_code
TARGETS_C_FILE = $(TARGETS).cCLEANUP = rm -f $(TARGETS) *.oall: $(TARGETS)clean:$(CLEANUP)CORE_INCS = -I. \-I$(NGX_ROOT)/src/core \-I$(NGX_ROOT)/src/event \-I$(NGX_ROOT)/src/event/modules \-I$(NGX_ROOT)/src/os/unix \-I$(NGX_ROOT)/objs \-I$(PCRE_ROOT) \NGX_PALLOC = $(NGX_ROOT)/objs/src/core/ngx_palloc.o
NGX_STRING = $(NGX_ROOT)/objs/src/core/ngx_string.o
NGX_ALLOC = $(NGX_ROOT)/objs/src/os/unix/ngx_alloc.o
NGX_ARRAY = $(NGX_ROOT)/objs/src/core/ngx_array.o
NGX_HASH = $(NGX_ROOT)/objs/src/core/ngx_hash.o$(TARGETS): $(TARGETS_C_FILE)$(CXX) $(CXXFLAGS) $(CORE_INCS) $(NGX_PALLOC) $(NGX_STRING) $(NGX_ALLOC) $(NGX_ARRAY) $(NGX_HASH) $^ -o $@
3. 編譯運行結果如下:
?二、 過濾器模塊
????????Nginx 的過濾器(Filter)模塊是其 HTTP 處理流程中非常重要的組件,主要用于修改 HTTP 響應的頭部或正文內容。它就像一個 “中間處理層”,可以在響應回發給客戶端之前,對數據進行加工(如添加內容、壓縮、轉碼等)。具體源碼如下:
1. ngx_http_prefix_module.c:
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>//定義一個結構體 ngx_http_prefix_filter_conf_t,用于存儲模塊的配置信息。這里包含一個 ngx_flag_t 類型的 enable 標志,用于啟用或禁用模塊的功能。
typedef struct {ngx_flag_t enable;
} ngx_http_prefix_filter_conf_t;//定義一個上下文結構體 ngx_http_prefix_filter_ctx_t,用于存儲與請求相關的狀態信息。這里的 add_prefix 標志用于指示是否需要在響應體前添加前綴。
typedef struct {ngx_int_t add_prefix;
} ngx_http_prefix_filter_ctx_t;static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf);
static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in);//filter_prefix 是一個靜態字符串,定義了要添加到響應體前的 HTML 前綴。
static ngx_str_t filter_prefix = ngx_string("<h2>Author : King</h2><p><a href=\"http://www.0voice.com\">0voice</a></p>");/*
ngx_http_prefix_filter_create_conf 用于創建模塊的配置塊。
使用 ngx_pcalloc 分配內存并初始化為零。
enable 被設置為 NGX_CONF_UNSET,表示默認未設置
*/static void *ngx_http_prefix_filter_create_conf(ngx_conf_t *cf) {ngx_http_prefix_filter_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_prefix_filter_conf_t));if (conf == NULL) {return NULL;}conf->enable = NGX_CONF_UNSET;return conf;
}/*
ngx_http_prefix_filter_merge_conf 用于合并父配置和子配置。這里合并 enable 標志的值,
優先使用子配置的值,如果子配置未設置,則使用父配置的值。默認值為 0。
*/static char *ngx_http_prefix_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) {ngx_http_prefix_filter_conf_t *prev = (ngx_http_prefix_filter_conf_t*)parent;ngx_http_prefix_filter_conf_t *conf = (ngx_http_prefix_filter_conf_t*)child;ngx_conf_merge_value(conf->enable, prev->enable, 0);return NGX_CONF_OK;
}/*
ngx_http_prefix_filter_commands 定義了一個名為 add_prefix 的指令。
該指令可以放在 http、server、location 和 limit 塊中。
ngx_conf_set_flag_slot 是處理這個指令的回調函數,用于設置標志位 enable。
*/static ngx_command_t ngx_http_prefix_filter_commands[] = {{ngx_string("add_prefix"),NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_FLAG,ngx_conf_set_flag_slot,NGX_HTTP_LOC_CONF_OFFSET,offsetof(ngx_http_prefix_filter_conf_t, enable),NULL},ngx_null_command
};/*
ngx_http_prefix_filter_module_ctx 定義了模塊的上下文,
包括創建配置塊和合并配置的函數。
ngx_http_prefix_filter_init 用于初始化模塊。
*/
static ngx_http_module_t ngx_http_prefix_filter_module_ctx = {NULL,ngx_http_prefix_filter_init,NULL,NULL,NULL,NULL,ngx_http_prefix_filter_create_conf,ngx_http_prefix_filter_merge_conf
};//ngx_http_prefix_filter_module 是模塊的主要定義,包括模塊的版本、上下文、命令和類型等。
ngx_module_t ngx_http_prefix_filter_module = {NGX_MODULE_V1,&ngx_http_prefix_filter_module_ctx,ngx_http_prefix_filter_commands,NGX_HTTP_MODULE,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NGX_MODULE_V1_PADDING
}; static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt ngx_http_next_body_filter;/*
ngx_http_prefix_filter_init 是模塊初始化函數。它將自定義的頭部和主體過濾器插入到 Nginx 的過濾器鏈中。
ngx_http_prefix_filter_header_filter 和 ngx_http_prefix_filter_body_filter 將被設置為新的過濾器函數。
*/
//總的來說,這個函數就是截胡
static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf) {ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_prefix_filter_header_filter;ngx_http_next_body_filter = ngx_http_top_body_filter;ngx_http_top_body_filter = ngx_http_prefix_filter_body_filter;return NGX_OK;
}/*
ngx_http_prefix_filter_header_filter 函數用于處理 HTTP 響應頭。
如果響應狀態碼不是 200 OK,直接傳遞給下一個頭部過濾器。檢查模塊上下文 ctx,
如果存在則直接傳遞。如果配置 enable 為 0,則直接傳遞。
否則,根據 Content-Type 判斷是否是 text/html,
如果是,將前綴添加到響應頭的 content_length 中,
并將 add_prefix 設置為 1。
*/static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r) {ngx_http_prefix_filter_ctx_t *ctx;ngx_http_prefix_filter_conf_t *conf;if (r->headers_out.status != NGX_HTTP_OK) {return ngx_http_next_header_filter(r);}ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx) {return ngx_http_next_header_filter(r);}conf = ngx_http_get_module_loc_conf(r, ngx_http_prefix_filter_module);if (conf == NULL) {return ngx_http_next_header_filter(r);}if (conf->enable == 0) {return ngx_http_next_header_filter(r);}ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_prefix_filter_ctx_t));if (ctx == NULL) {return NGX_ERROR;}ctx->add_prefix = 0;ngx_http_set_ctx(r, ctx, ngx_http_prefix_filter_module);if (r->headers_out.content_type.len >= sizeof("text/html") - 1&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char*)"text/html", sizeof("text/html")-1) == 0) {ctx->add_prefix = 1;if (r->headers_out.content_length_n > 0) {r->headers_out.content_length_n += filter_prefix.len;}}//return ngx_http_prefix_filter_header_filter(r);return ngx_http_next_header_filter(r);
}/*
ngx_http_prefix_filter_body_filter 函數用于處理 HTTP 響應體。
首先檢查上下文 ctx 和 add_prefix 標志。
如果沒有上下文或標志不是 1,直接傳遞給下一個主體過濾器。
如果 add_prefix 為 1,則設置 add_prefix 為 2,創建一個臨時緩沖區 b,將前綴寫入緩沖區,并將緩沖區添加到過濾器鏈 cl 中。
最后,將 cl 鏈傳遞給下一個主體過濾器。
*/static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {ngx_http_prefix_filter_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx == NULL || ctx->add_prefix != 1) {return ngx_http_next_body_filter(r, in);}ctx->add_prefix = 2;ngx_buf_t *b = ngx_create_temp_buf(r->pool, filter_prefix.len);b->start = b->pos = filter_prefix.data;b->last = b->pos + filter_prefix.len;ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);cl->buf = b;cl->next = in;return ngx_http_next_body_filter(r, cl);
}
以下詳細解析一下上面的代碼:
一、頭文件與基礎定義
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h>
- 作用:包含 Nginx 核心庫和 HTTP 模塊開發所需的頭文件。
ngx_config.h
:Nginx 編譯相關的配置宏。ngx_core.h
:Nginx 核心數據結構(如?ngx_str_t
、ngx_pool_t
)和函數(如內存分配)。ngx_http.h
:HTTP 模塊開發的核心頭文件(如?ngx_http_request_t
、過濾器相關類型)。二、配置與上下文結構體
// 模塊配置結構體(存儲全局/局部配置) typedef struct {ngx_flag_t enable; // 0=禁用,1=啟用(通過配置指令控制) } ngx_http_prefix_filter_conf_t;// 請求上下文結構體(存儲單次請求的狀態) typedef struct {ngx_int_t add_prefix; // 0=不添加,1=待添加,2=已添加 } ngx_http_prefix_filter_ctx_t;
- 配置結構體:存儲模塊的全局或局部配置(如是否啟用),通過 Nginx 的配置系統(
ngx_command_t
)與配置文件中的指令綁定。- 上下文結構體:存儲單次 HTTP 請求的狀態(如是否需要添加前綴),避免不同請求之間的狀態污染(通過?
ngx_http_set_ctx
?與請求綁定)。三、函數聲明與前綴內容
static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf); static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in);// 要添加的 HTML 前綴內容 static ngx_str_t filter_prefix = ngx_string("<h2>Author : jerry</h2><p><a href=\"http://www.0voice.com\">0voice</a></p>");
- 函數聲明:模塊初始化函數、頭部過濾器、正文過濾器(均為靜態,僅在本模塊內可見)。
filter_prefix
:使用?ngx_string
?宏初始化一個?ngx_str_t
?結構體(包含字符串指針和長度),存儲要添加的 HTML 前綴內容。四、配置塊創建與合并
// 創建配置塊(初始化時調用) static void *ngx_http_prefix_filter_create_conf(ngx_conf_t *cf) {// 從內存池(cf->pool)中分配配置結構體內存ngx_http_prefix_filter_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_prefix_filter_conf_t));if (conf == NULL) {return NULL; // 內存分配失敗}conf->enable = NGX_CONF_UNSET; // 初始化為“未設置”狀態(由后續合并配置決定默認值)return conf; }// 合并父子配置(如 http 塊與 server 塊的配置沖突時) static char *ngx_http_prefix_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) {ngx_http_prefix_filter_conf_t *prev = (ngx_http_prefix_filter_conf_t*)parent; // 父配置(如 http 塊)ngx_http_prefix_filter_conf_t *conf = (ngx_http_prefix_filter_conf_t*)child; // 子配置(如 server/location 塊)// 合并 enable 標志:若子配置未設置(NGX_CONF_UNSET),則使用父配置的值;默認值為 0(禁用)ngx_conf_merge_value(conf->enable, prev->enable, 0);return NGX_CONF_OK; // 合并成功 }
ngx_pcalloc
:Nginx 內存池分配函數(自動清零內存),避免野指針。NGX_CONF_UNSET
:配置未顯式設置時的標記值,通過?ngx_conf_merge_value
?合并父子配置后確定最終值。五、配置指令定義
static ngx_command_t ngx_http_prefix_filter_commands[] = {{ngx_string("add_prefix"), // 配置指令名(在 nginx.conf 中使用)NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_FLAG, // 指令作用域(http/server/location/limit_except 塊)ngx_conf_set_flag_slot, // 指令處理函數(自動將“on/off”轉換為 1/0,賦值給 enable)NGX_HTTP_LOC_CONF_OFFSET, // 配置存儲位置(location 塊的配置)offsetof(ngx_http_prefix_filter_conf_t, enable), // enable 字段在配置結構體中的偏移量NULL // 額外參數(此處不需要)},ngx_null_command // 指令數組結束標記 };
- 指令作用域:
NGX_HTTP_MAIN_CONF
(http 塊)、NGX_HTTP_SRV_CONF
(server 塊)等,表示該指令可以在這些配置塊中使用。ngx_conf_set_flag_slot
:Nginx 內置的指令處理函數,用于將?on/off
?轉換為?ngx_flag_t
?類型的?1/0
,并賦值給配置結構體的?enable
?字段。六、模塊上下文與模塊定義
// 模塊上下文(定義模塊的初始化、配置相關函數) static ngx_http_module_t ngx_http_prefix_filter_module_ctx = {NULL, // preconfiguration(HTTP 框架初始化前調用)ngx_http_prefix_filter_init, // postconfiguration(HTTP 框架初始化后調用,用于注冊過濾器)NULL, // create_main_conf(創建 http 塊配置)NULL, // init_main_conf(初始化 http 塊配置)NULL, // create_srv_conf(創建 server 塊配置)NULL, // merge_srv_conf(合并 server 塊配置)ngx_http_prefix_filter_create_conf, // create_loc_conf(創建 location 塊配置)ngx_http_prefix_filter_merge_conf // merge_loc_conf(合并 location 塊配置) };// 模塊主結構體(向 Nginx 注冊模塊) ngx_module_t ngx_http_prefix_filter_module = {NGX_MODULE_V1, // 模塊版本(固定宏)&ngx_http_prefix_filter_module_ctx, // 模塊上下文ngx_http_prefix_filter_commands, // 配置指令數組NGX_HTTP_MODULE, // 模塊類型(HTTP 模塊)NULL, // init_master(Master 進程初始化)NULL, // init_module(模塊初始化,全局僅一次)NULL, // init_process(Worker 進程初始化)NULL, // exit_process(Worker 進程退出)NULL, // exit_module(模塊退出)NULL, // exit_master(Master 進程退出)NULL, // 模塊名(可選)NGX_MODULE_V1_PADDING // 填充字段(保持結構對齊) };
ngx_http_module_t
:HTTP 模塊的上下文結構體,定義模塊在不同生命周期的回調函數(如?postconfiguration
?用于注冊過濾器)。ngx_module_t
:Nginx 模塊的主結構體,用于向 Nginx 核心注冊模塊的類型、上下文、指令等信息。七、過濾器鏈初始化
// 保存原始的頭部和正文過濾器鏈(用于傳遞請求) static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter;// 模塊初始化函數(在 HTTP 框架初始化后調用) static ngx_int_t ngx_http_prefix_filter_init(ngx_conf_t *cf) {// 將自定義頭部過濾器插入到鏈頂,原頭部過濾器變為“下一個”ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_prefix_filter_header_filter;// 同理處理正文過濾器鏈ngx_http_next_body_filter = ngx_http_top_body_filter;ngx_http_top_body_filter = ngx_http_prefix_filter_body_filter;return NGX_OK; // 初始化成功 }
- 過濾器鏈機制:Nginx 的 HTTP 響應處理通過 “過濾器鏈” 實現,每個過濾器處理完后將請求傳遞給鏈中的下一個過濾器。
ngx_http_top_header_filter
:頭部過濾器鏈的頭部指針。通過將自定義過濾器設為新的鏈頭(ngx_http_top_header_filter
),并將原鏈頭保存到?ngx_http_next_header_filter
,確保自定義過濾器優先執行,處理完后通過?ngx_http_next_header_filter
?傳遞給后續過濾器。八、頭部過濾器:
ngx_http_prefix_filter_header_filter
static ngx_int_t ngx_http_prefix_filter_header_filter(ngx_http_request_t *r) {ngx_http_prefix_filter_ctx_t *ctx; // 請求上下文(存儲 add_prefix 標志)ngx_http_prefix_filter_conf_t *conf; // 模塊配置(enable 標志)// 1. 僅處理狀態碼為 200 OK 的響應if (r->headers_out.status != NGX_HTTP_OK) {return ngx_http_next_header_filter(r); // 傳遞給下一個頭部過濾器}// 2. 檢查是否已存在上下文(避免重復處理)ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx) {return ngx_http_next_header_filter(r);}// 3. 獲取 location 塊的配置(檢查是否啟用模塊)conf = ngx_http_get_module_loc_conf(r, ngx_http_prefix_filter_module);if (conf == NULL || conf->enable == 0) { // 配置不存在或未啟用return ngx_http_next_header_filter(r);}// 4. 創建上下文并綁定到請求ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_prefix_filter_ctx_t));if (ctx == NULL) {return NGX_ERROR; // 內存分配失敗,終止請求}ctx->add_prefix = 0; // 默認不添加前綴ngx_http_set_ctx(r, ctx, ngx_http_prefix_filter_module); // 綁定上下文到請求// 5. 檢查響應類型是否為 text/htmlif (r->headers_out.content_type.len >= sizeof("text/html") - 1 // 確保 content_type 長度足夠&& ngx_strncasecmp(r->headers_out.content_type.data, (u_char*)"text/html", sizeof("text/html")-1) == 0) {ctx->add_prefix = 1; // 標記需要添加前綴// 調整 Content-Length(若存在):原長度 + 前綴長度if (r->headers_out.content_length_n > 0) {r->headers_out.content_length_n += filter_prefix.len;}}// 6. 傳遞給下一個頭部過濾器(關鍵修正:原代碼錯誤遞歸調用自身,現已修正)return ngx_http_next_header_filter(r); }
- 核心邏輯:
- 僅處理?
200 OK
?響應,避免修改錯誤頁面(如?404 Not Found
)。- 通過?
ngx_http_get_module_ctx
?檢查是否已存在上下文(防止重復處理同一請求)。- 通過?
ngx_http_get_module_loc_conf
?獲取當前?location
?塊的配置,若未啟用則跳過。- 為請求創建上下文(
ngx_http_prefix_filter_ctx_t
),并通過?ngx_http_set_ctx
?綁定到請求(生命周期與請求一致)。- 檢查響應的?
Content-Type
?是否為?text/html
(忽略大小寫),若是則標記?add_prefix=1
,并調整?Content-Length
(避免客戶端接收數據時長度不符)。九、正文過濾器:
ngx_http_prefix_filter_body_filter
static ngx_int_t ngx_http_prefix_filter_body_filter(ngx_http_request_t *r, ngx_chain_t *in) {// 1. 獲取請求上下文,檢查是否需要添加前綴ngx_http_prefix_filter_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_prefix_filter_module);if (ctx == NULL || ctx->add_prefix != 1) { // 無上下文或未標記添加return ngx_http_next_body_filter(r, in); // 傳遞給下一個正文過濾器}// 2. 標記前綴已添加(避免重復處理)ctx->add_prefix = 2;// 3. 創建臨時緩沖區,存儲前綴內容ngx_buf_t *b = ngx_create_temp_buf(r->pool, filter_prefix.len); // 創建指定長度的緩沖區if (b == NULL) {return NGX_ERROR; // 內存分配失敗}b->start = b->pos = filter_prefix.data; // 緩沖區起始位置指向前綴內容b->last = b->pos + filter_prefix.len; // 緩沖區結束位置(前綴內容長度)b->temporary = 1; // 標記緩沖區為臨時(Nginx 會自動管理內存)// 4. 構造新的緩沖區鏈(前綴在前,原始內容在后)ngx_chain_t *cl = ngx_alloc_chain_link(r->pool); // 分配鏈節點if (cl == NULL) {return NGX_ERROR;}cl->buf = b; // 鏈節點指向前綴緩沖區cl->next = in; // 鏈的下一個節點指向原始響應體(in 是原始緩沖區鏈)// 5. 傳遞給下一個正文過濾器(處理后的鏈包含前綴+原始內容)return ngx_http_next_body_filter(r, cl); }
- 核心邏輯:
- 檢查上下文標志?
add_prefix=1
(僅當頭部過濾器標記需要添加時執行)。- 使用?
ngx_create_temp_buf
?創建臨時緩沖區,存儲前綴內容(filter_prefix
)。- 使用?
ngx_alloc_chain_link
?分配鏈節點,將前綴緩沖區與原始響應體鏈(in
)拼接,形成新的鏈(cl->next = in
)。- 將新鏈傳遞給下一個正文過濾器(如?
chunked
?編碼過濾器、gzip
?過濾器等),最終發送給客戶端。十、總結
這段代碼實現了一個?Nginx 過濾器模塊,核心功能是:在啟用?
add_prefix on
?的?location
?塊中,對?200 OK
?的?text/html
?響應,在正文前添加固定的 HTML 前綴(如作者信息)。關鍵流程:
- 配置加載:通過?
add_prefix
?指令控制模塊是否啟用(on/off
)。- 過濾器注冊:在?
ngx_http_prefix_filter_init
?中將自定義過濾器插入到過濾器鏈頂部。- 頭部過濾:檢查響應狀態碼、配置、
Content-Type
,調整?Content-Length
?并標記需要添加前綴。- 正文過濾:根據標記,將前綴內容插入到響應體前,傳遞給后續過濾器。
2.config:
ngx_addon_name=ngx_http_prefix_filter_module
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_prefix_filter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_prefix_filter_module.c"
3. 具體操作步驟
3.1 現在文件夾中創建config文件和.c文件
3.2 配置config文件,讓源碼再逼阿姨的過程中包含自己的Module
3.3 寫.c文件
3.4 在源碼下config編譯
./configure --prefix=/usr/local/nginx --with-http_realip_module --with-http_addition_module --with-http_ssl_module --with-http_gzip_static_module --with-http_secure_link_module --with-http_stub_status_module --with-stream --with-pcre=/home/jerry/snap/nginx/pcre-8.45 --with-zlib=/home/jerry/snap/nginx/zlib-1.2.13 --with-openssl=/home/jerry/snap/nginx/openssl-1.1.1s --add-module=/usr/local/nginx/ngx_code_test/ngx_http_prefix_filter_module
3.5 make + make install?
3.6 在我們自己的jerry.conf文件中添加.c文件里的特殊關鍵字: 這里是add_prefix
root@jerry-virtual-machine:/usr/local/nginx# vim conf/jerry.conf
3.7 在對應目錄添加html文件:
sudo mkdir -p /usr/local/nginx/html8001/html
vim index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>8001 服務頁面</title><style>body { font-family: Arial, sans-serif; text-align: center; background-color: #f0f0f0; }.container { margin: 100px auto; padding: 20px; background-color: white; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }h1 { color: #2c3e50; }p { color: #7f8c8d; font-size: 1.1em; }</style>
</head>
<body><div class="container"><h1>歡迎訪問 8001 端口服務!</h1><p>這是來自 nginx 8001 端口的靜態頁面。</p><p>當前時間:<span id="currentTime"></span></p></div><script>// 動態顯示當前時間function updateTime() {const timeElement = document.getElementById('currentTime');const now = new Date().toLocaleString();timeElement.textContent = now;}// 每秒鐘更新一次時間setInterval(updateTime, 1000);updateTime(); // 初始加載時立即顯示</script>
</body>
</html>
3.8?啟動nginx服務器,使用我們自己的配置文件
?3.9?打開瀏覽器,輸入網址加端口:
192.168.186.138:8000
?三、Handler模塊
????????在 Nginx 的 HTTP 處理框架中,Handler 模塊是處理具體請求的核心組件,負責生成最終的響應內容。當請求經過 Nginx 的路由匹配(如匹配到某個?location
?塊)后,Nginx 會調用對應的 Handler 來處理請求,例如讀取靜態文件、與后端程序(如 PHP-FPM、Java 應用)交互獲取動態內容等。具體代碼如下:
1.ngx_http_handler_count_module.c:
#include <ngx_http.h>
#include <ngx_core.h>#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>#define ENABLE_RBTREE 1/*
ngx_pv_table_t 結構體定義了一個包含 count 和 addr 的結構體。
count: 記錄來自特定 IP 地址的請求次數。
addr: 存儲 IP 地址。
*/typedef struct{int count;struct in_addr addr;
}ngx_pv_table_t;ngx_pv_table_t pv_table[256];#if ENABLE_RBTREEstatic ngx_rbtree_t ngx_pv_rbtree;static ngx_rbtree_node_t ngx_pv_sentinel;//插入新結點void ngx_rbtree_insert_count_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node,ngx_rbtree_node_t *sentinel)
{ ngx_rbtree_node_t **p;for ( ;; ) {/** Timer values* 1) are spread in small range, usually several minutes,* 2) and overflow each 49 days, if milliseconds are stored in 32 bits.* The comparison takes into account that overflow.*//* node->key < temp->key */
#if 0p = ((ngx_rbtree_key_int_t) (node->key - temp->key) < 0)? &temp->left : &temp->right;
#elseif (node->key < temp->key) {p = &temp->left;} else if (node->key > temp->key) {p = &temp->right;} else { // node->key == temp->keyreturn ;}#endifif (*p == sentinel) {break;}temp = *p;}*p = node;node->parent = temp;node->left = sentinel;node->right = sentinel;ngx_rbt_red(node);
}//
ngx_rbtree_node_t *ngx_rbtree_count_search(ngx_rbtree_t *rbtree, ngx_rbtree_key_t key) {ngx_rbtree_node_t *temp = rbtree->root;ngx_rbtree_node_t **p;for ( ;; ) {if (key < temp->key) {p = &temp->left;} else if (key > temp->key) {p = &temp->right;} else { // node->key == temp->keyreturn temp;}if (*p == &ngx_pv_sentinel) {return NULL;}temp = *p;}}void ngx_rbtree_count_traversal(ngx_rbtree_t *T, ngx_rbtree_node_t *node, char *html) { if (node != &ngx_pv_sentinel) { ngx_rbtree_count_traversal(T, node->left, html); //printf("key:%d, color:%d\n", node->key, node->color); char str[INET_ADDRSTRLEN] = {0};char buffer[128] = {0};snprintf(buffer, 128, "req from : %s, count : %d <br/>",inet_ntop(AF_INET, &node->key, str, sizeof(str)), node->data);strcat(html, buffer);ngx_rbtree_count_traversal(T, node->right, html); }
}#endifchar *ngx_http_handler_count_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_int_t ngx_http_handler_count_handler(ngx_http_request_t *r);
ngx_int_t ngx_http_handler_count_init(ngx_conf_t *cf);/*
ngx_http_handler_count_cmds 定義了一個 Nginx 配置指令 count。
ngx_string("count"): 配置指令的名稱。
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS: 指令作用于位置配置,并且不需要額外參數。
ngx_http_handler_count_set: 當指令被解析時調用的處理函數。
NGX_HTTP_LOC_CONF_OFFSET: 配置項的偏移量。
0: 沒有特別的標志。
NULL: 額外的配置項。
*/
static ngx_command_t ngx_http_handler_count_cmds[] = {{ngx_string("count"),NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,ngx_http_handler_count_set,NGX_HTTP_LOC_CONF_OFFSET,0,NULL},ngx_null_command
};/*ngx_http_handler_count_ctx 定義了模塊的上下文。
這些字段用于定義模塊的初始化、配置和處理函數。
ngx_http_handler_count_init 被注釋掉了,如果需要模塊初始化,請取消注釋。*/
// share mem
static ngx_http_module_t ngx_http_handler_count_ctx = {NULL,//ngx_http_handler_count_init, //NULL,NULL,NULL,NULL,NULL,NULL,NULL,
}; /*
ngx_http_handler_count_module 定義了模塊的主要結構。
NGX_MODULE_V1: 模塊的版本。
&ngx_http_handler_count_ctx: 模塊上下文。
ngx_http_handler_count_cmds: 模塊的配置指令。
NGX_HTTP_MODULE: 模塊類型(HTTP 模塊)。
NULL: 一些其他的字段(可選)設置為空。
*/ngx_module_t ngx_http_handler_count_module = {NGX_MODULE_V1,&ngx_http_handler_count_ctx,ngx_http_handler_count_cmds,NGX_HTTP_MODULE,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NGX_MODULE_V1_PADDING
};//當在conf文件中遇到count這個命令時,會調用這個回調函數
char *ngx_http_handler_count_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
#if ENABLE_RBTREEngx_rbtree_init(&ngx_pv_rbtree,&ngx_pv_sentinel,ngx_rbtree_insert_count_value);
#endif ngx_http_core_loc_conf_t *corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);corecf->handler = ngx_http_handler_count_handler;return NGX_CONF_OK;}//組織網頁
static int ngx_http_encode_page(char *html) {sprintf(html, "<h1>zvoice.jerry</h1>");strcat(html, "<h2>");#if ENABLE_RBTREEngx_rbtree_count_traversal(&ngx_pv_rbtree, ngx_pv_rbtree.root, html);#elseint i = 0;for (i = 0;i < 256;i ++) {if (pv_table[i].count != 0) {char str[INET_ADDRSTRLEN] = {0};char buffer[128] = {0};snprintf(buffer, 128, "req from : %s, count : %d <br/>",inet_ntop(AF_INET, &pv_table[i].addr, str, sizeof(str)), pv_table[i].count);strcat(html, buffer);}}
#endifstrcat(html, "<h2/>");return 0;
}/*
ngx_http_handler_count_handler 函數處理 HTTP 請求。
u_char html[1024] = {0};: 定義一個用于存儲 HTML 內容的緩沖區。
int len = sizeof(html);: 獲取緩沖區的大小。
struct sockaddr_in *client_addr = (struct sockaddr_in *)r->connection->sockaddr;: 獲取客戶端地址信息。
計算 idx 作為 IP 地址的索引,將計數增加 1,并更新 pv_table 中的 IP 地址。
使用 ngx_http_encode_page 函數將數據編碼成 HTML。
設置 HTTP 響應狀態為 200,并設置內容類型為 text/html。
通過 ngx_http_send_header 發送響應頭。
創建一個 ngx_buf_t 緩沖區,用于存儲 HTML 內容,并通過 ngx_http_output_filter 發送響應體
*///每次發起請求,就會走到這里
ngx_int_t ngx_http_handler_count_handler(ngx_http_request_t *r) {//可以獲取到對端的信息u_char html[1024] = {0};int len = sizeof(html);struct sockaddr_in *client_addr = (struct sockaddr_in *)r->connection->sockaddr; // ip#if ENABLE_RBTREE//查找,找到就把count+=ngx_rbtree_key_t key = (ngx_rbtree_key_t)client_addr->sin_addr.s_addr;ngx_rbtree_node_t *node = ngx_rbtree_count_search(&ngx_pv_rbtree, key);if (!node) {node = ngx_pcalloc(r->pool, sizeof(ngx_rbtree_node_t));node->key = key;node->data = 1;ngx_rbtree_insert(&ngx_pv_rbtree, node);} else {node->data ++;}#else//拿到ip后,取最后三位,即256個作為索引,存儲對應的ip的count值int idx = client_addr->sin_addr.s_addr >> 24;pv_table[idx].count ++;memcpy(&pv_table[idx].addr, &client_addr->sin_addr, sizeof(client_addr->sin_addr));
#endifngx_http_encode_page((char *)html);//返回狀態默認200r->headers_out.status = 200;ngx_str_set(&r->headers_out.content_type, "text/html");//先把http頭發出去ngx_http_send_header(r);//接著發body部分ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));ngx_chain_t out;out.buf = b;out.next = NULL;b->pos = html;b->last = html+len;b->memory = 1;b->last_buf = 1;return ngx_http_output_filter(r, &out);
}//
以下詳細講解一下這段代碼:
1. 頭文件與宏定義
#include <ngx_http.h> // Nginx HTTP模塊核心頭文件 #include <ngx_core.h> // Nginx核心功能頭文件(內存管理、字符串等) #include <arpa/inet.h> // 網絡地址轉換函數(如inet_ntop) #include <netinet/in.h> // 網絡地址結構(如struct in_addr) #include <stdio.h> // 標準輸入輸出(如snprintf)#define ENABLE_RBTREE 1 // 宏開關:1表示使用紅黑樹存儲統計數據,0表示使用數組
- 作用:引入 Nginx 核心庫、網絡相關庫,通過宏控制存儲方式(紅黑樹 / 數組)。
2. 統計數據結構體與存儲容器
/* 統計數據結構體:記錄IP地址和請求次數 */ typedef struct {int count; // 請求次數struct in_addr addr; // IP地址(IPv4,4字節) } ngx_pv_table_t;ngx_pv_table_t pv_table[256]; // 數組存儲(僅當ENABLE_RBTREE=0時使用)
- 說明:
ngx_pv_table_t
用于存儲單個 IP 的統計信息。pv_table
是長度為 256 的數組,通過 IP 地址的最后一個字節(0-255)作為索引(可能沖突)。3. 紅黑樹相關實現(當 ENABLE_RBTREE=1 時)
3.1 紅黑樹與哨兵節點
static ngx_rbtree_t ngx_pv_rbtree; // 紅黑樹實例 static ngx_rbtree_node_t ngx_pv_sentinel; // 哨兵節點(用于標記樹的邊界)
- 紅黑樹:Nginx 內置的平衡二叉樹結構,用于高效存儲和查找 IP 統計數據(插入、查找時間復雜度 O (logN))。
- 哨兵節點:所有葉子節點的子節點指向哨兵,避免 NULL 指針判斷,簡化代碼。
3.2 紅黑樹插入函數
void ngx_rbtree_insert_count_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) {ngx_rbtree_node_t **p;for (;;) {if (node->key < temp->key) { // 新節點鍵值更小,插入左子樹p = &temp->left;} else if (node->key > temp->key) { // 新節點鍵值更大,插入右子樹p = &temp->right;} else { // 鍵值已存在,直接返回(不重復插入)return;}if (*p == sentinel) { // 找到插入位置(當前子節點是哨兵)break;}temp = *p; // 繼續向下遍歷}*p = node; // 插入新節點node->parent = temp; // 設置父節點node->left = sentinel; // 左子節點指向哨兵node->right = sentinel; // 右子節點指向哨兵ngx_rbt_red(node); // 新節點默認紅色(紅黑樹規則) }
- 功能:將新節點插入紅黑樹,確保鍵(IP 地址)唯一。
- 關鍵邏輯:根據鍵值大小遍歷樹,找到插入位置后設置父子關系,新節點初始化為紅色(紅黑樹插入規則)。
3.3 紅黑樹搜索函數
ngx_rbtree_node_t *ngx_rbtree_count_search(ngx_rbtree_t *rbtree, ngx_rbtree_key_t key) {ngx_rbtree_node_t *temp = rbtree->root; // 從根節點開始搜索for (;;) {if (key < temp->key) { // 目標鍵更小,搜索左子樹temp = temp->left;} else if (key > temp->key) { // 目標鍵更大,搜索右子樹temp = temp->right;} else { // 找到匹配鍵,返回當前節點return temp;}if (temp == &ngx_pv_sentinel) { // 遇到哨兵,說明無匹配return NULL;}} }
- 功能:根據鍵(IP 地址)搜索紅黑樹,返回對應節點(若存在)。
- 關鍵邏輯:從根節點開始,根據鍵值大小遞歸搜索左右子樹,直到找到匹配或遇到哨兵。
3.4 紅黑樹遍歷函數
void ngx_rbtree_count_traversal(ngx_rbtree_t *T, ngx_rbtree_node_t *node, char *html) {if (node != &ngx_pv_sentinel) { // 非哨兵節點遞歸遍歷ngx_rbtree_count_traversal(T, node->left, html); // 左子樹// 將當前節點的IP和計數寫入HTMLchar str[INET_ADDRSTRLEN] = {0};char buffer[128] = {0};snprintf(buffer, 128, "req from : %s, count : %d <br/>",inet_ntop(AF_INET, &node->key, str, sizeof(str)), node->data);strcat(html, buffer);ngx_rbtree_count_traversal(T, node->right, html); // 右子樹} }
- 功能:中序遍歷紅黑樹(左 - 根 - 右),將每個節點的 IP 和計數追加到 HTML 字符串中。
- 關鍵邏輯:遞歸遍歷左右子樹,使用
inet_ntop
將 IP 地址(node->key
)轉換為點分十進制字符串,拼接統計信息到 HTML。4. Nginx 模塊核心結構
4.1 配置指令定義
static ngx_command_t ngx_http_handler_count_cmds[] = {{ngx_string("count"), // 配置指令名稱:`count`NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS, // 作用域:location塊;無參數ngx_http_handler_count_set, // 指令解析回調函數NGX_HTTP_LOC_CONF_OFFSET, // 配置存儲偏移量(無需自定義)0, // 無額外標志NULL // 無額外配置數據},ngx_null_command // 指令數組結束標記 };
- 作用:定義 Nginx 配置指令
count
,當在location
塊中使用該指令時,觸發ngx_http_handler_count_set
函數。4.2 模塊上下文
static ngx_http_module_t ngx_http_handler_count_ctx = {NULL, // preconfiguration(配置解析前回調)NULL, // postconfiguration(配置解析后回調)NULL, // create main configuration(創建主配置)NULL, // init main configuration(初始化主配置)NULL, // create server configuration(創建server配置)NULL, // merge server configuration(合并server配置)NULL, // create location configuration(創建location配置)NULL // merge location configuration(合并location配置) };
- 說明:Nginx 模塊上下文,用于注冊配置相關的回調函數。當前模塊未使用這些回調,故全部置空。
4.3 模塊主結構體
ngx_module_t ngx_http_handler_count_module = {NGX_MODULE_V1, // 模塊版本(固定)&ngx_http_handler_count_ctx, // 模塊上下文ngx_http_handler_count_cmds, // 配置指令數組NGX_HTTP_MODULE, // 模塊類型(HTTP模塊)NULL, NULL, NULL, NULL, // 其他生命周期回調(未使用)NULL, NULL, NULL, NULL, // 其他生命周期回調(未使用)NGX_MODULE_V1_PADDING // 填充字段(保持結構對齊) };
- 作用:向 Nginx 注冊模塊,聲明模塊類型、上下文和配置指令。
5. 配置指令處理函數
char *ngx_http_handler_count_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { #if ENABLE_RBTREEngx_rbtree_init(&ngx_pv_rbtree, &ngx_pv_sentinel, ngx_rbtree_insert_count_value); // 初始化紅黑樹 #endif// 獲取當前location的核心配置ngx_http_core_loc_conf_t *corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);corecf->handler = ngx_http_handler_count_handler; // 設置請求處理函數為ngx_http_handler_count_handlerreturn NGX_CONF_OK; // 配置解析成功 }
- 功能:當 Nginx 解析到
count
指令時,初始化紅黑樹(若啟用),并將當前location
的請求處理函數設置為ngx_http_handler_count_handler
。- 關鍵操作:
ngx_rbtree_init
初始化紅黑樹(指定插入函數和哨兵);corecf->handler
綁定請求處理入口。6. HTML 生成函數
static int ngx_http_encode_page(char *html) {sprintf(html, "<h1>zvoice.jerry</h1>"); // 標題strcat(html, "<h2>"); // 子標題開始#if ENABLE_RBTREE// 遍歷紅黑樹,拼接統計數據ngx_rbtree_count_traversal(&ngx_pv_rbtree, ngx_pv_rbtree.root, html); #else// 遍歷數組pv_table,拼接統計數據for (int i = 0; i < 256; i++) {if (pv_table[i].count != 0) { // 僅處理有計數的條目char str[INET_ADDRSTRLEN] = {0};char buffer[128] = {0};snprintf(buffer, 128, "req from : %s, count : %d <br/>",inet_ntop(AF_INET, &pv_table[i].addr, str, sizeof(str)), pv_table[i].count);strcat(html, buffer);}} #endifstrcat(html, "</h2>"); // 子標題結束return 0; }
- 功能:生成包含統計結果的 HTML 字符串。根據
ENABLE_RBTREE
選擇遍歷紅黑樹或數組。- 注意:
strcat
可能存在緩沖區溢出風險(HTML 緩沖區固定為 1024 字節)。7. 請求處理核心函數
ngx_int_t ngx_http_handler_count_handler(ngx_http_request_t *r) {u_char html[1024] = {0}; // HTML響應緩沖區(固定1024字節)int len = sizeof(html);// 獲取客戶端IP地址(IPv4)struct sockaddr_in *client_addr = (struct sockaddr_in *)r->connection->sockaddr;#if ENABLE_RBTREE// 紅黑樹模式:查找或插入IP統計ngx_rbtree_key_t key = (ngx_rbtree_key_t)client_addr->sin_addr.s_addr; // IP地址作為鍵(32位無符號整數)ngx_rbtree_node_t *node = ngx_rbtree_count_search(&ngx_pv_rbtree, key); // 搜索是否存在該IPif (!node) { // 未找到,插入新節點node = ngx_pcalloc(r->pool, sizeof(ngx_rbtree_node_t)); // 從Nginx內存池分配節點node->key = key;node->data = 1; // 初始計數為1ngx_rbtree_insert(&ngx_pv_rbtree, node); // 插入紅黑樹} else { // 已存在,計數+1node->data++;}#else// 數組模式:通過IP最后一個字節作為索引(可能沖突)int idx = client_addr->sin_addr.s_addr >> 24; // 右移24位,取最后一個字節(0-255)pv_table[idx].count++; // 計數+1memcpy(&pv_table[idx].addr, &client_addr->sin_addr, sizeof(client_addr->sin_addr)); // 存儲IP地址 #endifngx_http_encode_page((char *)html); // 生成HTML// 設置HTTP響應頭r->headers_out.status = NGX_HTTP_OK; // 狀態碼200ngx_str_set(&r->headers_out.content_type, "text/html"); // 內容類型為HTML// 發送響應頭if (ngx_http_send_header(r) == NGX_ERROR) {return NGX_ERROR;}// 構造響應體(使用Nginx緩沖區鏈)ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));ngx_chain_t out;out.buf = b;out.next = NULL; // 單緩沖區鏈b->pos = html; // 緩沖區起始位置b->last = html + len; // 緩沖區結束位置(注意:可能超出實際內容長度)b->memory = 1; // 標記為內存緩沖區(可安全讀取)b->last_buf = 1; // 標記為最后一個緩沖區// 發送響應體return ngx_http_output_filter(r, &out); }
- 功能:處理每個 HTTP 請求,統計客戶端 IP 的請求次數,生成并返回 HTML 響應。
- 關鍵邏輯:
- 獲取客戶端 IP:通過
r->connection->sockaddr
獲取客戶端地址(IPv4)。- 統計邏輯:
- 紅黑樹模式:以 IP 地址的 32 位整數作為鍵,搜索或插入節點,更新計數。
- 數組模式:取 IP 地址的最后一個字節(右移 24 位)作為索引,直接更新數組中的計數。
- 響應構造:生成 HTML 后,設置 HTTP 狀態碼、內容類型,通過 Nginx 的緩沖區鏈發送響應頭和體。
2. config:
ngx_addon_name=ngx_http_handler_count_module
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_handler_count_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_handler_count_module.c"
3. 具體操作步驟
##同前面
./configure --prefix=/usr/local/nginx --with-http_realip_module --with-http_addition_module --with-http_ssl_module --with-http_gzip_static_module --with-http_secure_link_module --with-http_stub_status_module --with-stream --with-pcre=/home/jerry/snap/nginx/pcre-8.45 --with-zlib=/home/jerry/snap/nginx/zlib-1.2.13 --with-openssl=/home/jerry/snap/nginx/openssl-1.1.1s --add-module=/usr/local/nginx/ngx_code_test/ngx_http_handler_count_module
- 使用的nginx.conf中,加上count選項:
? ? ? ? 2. 不同ip地址請求時的區別:
?0voice · GitHub?