準備工作
nginx模塊需要用C(或者C++)語言來編碼實現,每個模塊都要有自己的名字。我們這裏把編寫的HTTP模塊命名爲ngx_http_mytest_module,源代碼文件命名爲ngx_http_mytest_module.c。
定義自己的HTTP模塊
定義HTTP模塊的方式很簡單,例如:
ngx_module_t ngx_http_mytest_module;
ngx_module_t是一個nginx模塊的數據結構,如下所示:
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
// 分類的模塊計數器
// nginx模塊可以分爲四種:core、event、http和mail
// 每個模塊都會各自計數,ctx_index就是每個模塊在其所屬類組的計數
ngx_uint_t ctx_index;
// 模塊計數器,按照每個模塊在ngx_modules[]數組中的聲明順序
// 從0開始依次給每個模塊賦值
ngx_uint_t index;
// 保留變量,暫未使用
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
ngx_uint_t version; // nginx模塊版本
// 模塊的上下文,不同種類的模塊有不同的上下文,因此實現了四種結構體
void *ctx;
ngx_command_t *commands; // 處理模塊中的配置項
ngx_uint_t type; // 模塊類型,用於區分core,event,http和mail
ngx_int_t (*init_master)(ngx_log_t *log); // 初始化master時執行
ngx_int_t (*init_module)(ngx_cycle_t *cycle); // 初始化module時執行
ngx_int_t (*init_process)(ngx_cycle_t *cycle); // 初始化process時執行
ngx_int_t (*init_thread)(ngx_cycle_t *cycle); // 初始化thread時執行
void (*exit_thread)(ngx_cycle_t *cycle); // 退出thread時執行
void (*exit_process)(ngx_cycle_t *cycle); // 退出process時執行
void (*exit_master)(ngx_cycle_t *cycle); // 退出master時執行
// 以下變量是保留字段,暫未使用
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;
};
該結構體在初始化時,需要用到兩個宏定義:
#define NGX_MODULE_V1 0, 0, 0, 0, 0, 0, 1 // 初始化前七個成員
#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0 // 初始化後八個成員
定義HTTP模塊時,最重要的是要設置ctx和commands這兩個成員:
(1)我們首先來定義mytest配置項的處理,並設置在出現mytest配置後的解析方法ngx_http_mytest,如下所示:
static ngx_command_t ngx_http_mytest_commands[] = {
{
ngx_string("mytest"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
// 找到mytest配置項所屬的配置塊
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http-core_module);
// ngx_http_mytest_handler方法處理請求
clcf->handler = ngx_http_mytest_handler;
return NGX_CONF_OK;
}
(2)對於HTTP類型的模塊來說,ngx_module_t中的ctx指針必須指向ngx_http_module_t接口。我們當前只是開發一個簡單的模塊,如果沒有什麼工作是必須在HTTP框架初始化時完成的,那就不必實現ngx_http_module_t中的8個回調方法,可以像下面這樣定義ngx_http_module_t接口:
static ngx_http_module_t ngx_http_mytest_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
NULL, /* create location configration */
NULL, /* merge location configration */
};
最後,我們來定義mytest模塊:
ngx_module_t ngx_http_mytest_module = {
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx, /* module context */
ngx_http_mytest_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
};
這樣,mytest模塊在編譯時就會被加入到ngx_modules全局數組中。
完整的ngx_http_mytest_handler處理方法
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
// 必須是GET或HEAD方法,否則返回405 not allowed
if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
// 丟棄請求中的包體(如果不想處理就丟棄掉)
// 它的意義在於:有些客戶端可能會一直試圖發送包體,而如果HTTP模塊
// 不接收發來的TCP流,有可能造成客戶端發送超時
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
// 設置返回的Content-Type
ngx_str_t type = ngx_string("text/plain");
// 返回的包體內容
ngx_str_t response = ngx_string("Hello World");
// 設置返回狀態碼
r->headers_out.status = NGX_HTTP_OK;
// 響應包是有包體內容的,需要設置Content-Length長度
r->headers_out.content_length_n = response.len;
// 設置Content-Type
r->headers_out.content_type = type;
// 發送HTTP頭部
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
// 構造ngx_buf_t結構體準備發送包體
ngx_buf_t *b;
b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 將包體內容即Hello World複製到ngx_buf_t所指向的內存
ngx_memcpy(b->pos, response.data, response.len);
// 一定要設置好last指針
b->last = b->pos + response.len;
// 聲明這是最後一塊緩衝區
b->last_buf = 1;
// 構造發送時的ngx_chain_t結構體
ngx_chain_t out;
// 賦值ngx_buf_t
out.buf = b;
// 設置next爲NULL
out.next = NULL;
// 最後一步爲發送包體
// 發送結束後HTTP框架會調用ngx_http_finalize_request方法結束請求
return ngx_http_output_filter(r, &out);
}
將編寫的HTTP模塊編譯進nginx
爲了讓HTTP模塊正常工作,首先需要把它編譯進nginx。
nginx提供的一種簡單的編譯方式是:首先把源代碼文件全部放到一個目錄下,同時在該目錄中編寫一個文件用於通知nginx如何編譯本模塊,這個文件名必須爲config。
config文件的寫法:
如果只想開發一個HTTP模塊,那麼config文件中需要定義以下三個變量:
(1)ngx_addon_name:僅在configure執行時使用,一般設置爲模塊名稱;
(2)HTTP_MODULES:保存所有的HTTP模塊名稱,每個模塊間由空格相連;
(3)NGX_ADDON_SRCS:用於指定新模塊的源代碼,多個待編譯的源代碼之間可以用空格相連。注意,在設置這個變量時可以使用$ngx_addon_dir變量,它等價於configure執行時--add-module=PATH的PATH參數。
對於我們的mytest模塊,可以這樣編寫config文件:
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
這樣,只要在configure腳本執行時加入參數–add-module=PATH(PATH就是我們的源代碼和config文件的保存目錄),就可以在執行正常編譯安裝流程時完成nginx編譯工作。
上述方法是最方便的,但有時我們需要更靈活的方式,比如重新決定各個模塊的順序或者在編譯源代碼時加入一些獨特的編譯選項,我們可以在執行configure後,對生成的objs/ngx_modules.c和objs/Makefile文件直接進行修改。(此種方法需慎用,不正確的修改可能導致nginx運作不正常,此處不詳述)
完整代碼
config文件:
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
源代碼 ngx_http_mytest_module.c
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);
static char *
ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static ngx_command_t ngx_http_mytest_commands[] = {
{
ngx_string("mytest"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_mytest_module_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
ngx_module_t ngx_http_mytest_module = {
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx,
ngx_http_mytest_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
static char *
ngx_http_mytest(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_mytest_handler;
return NGX_CONF_OK;
}
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
}
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
ngx_str_t type = ngx_string("text/plain");
ngx_str_t response = ngx_string("Hello World");
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = response.len;
r->headers_out.content_type = type;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
ngx_buf_t *b;
b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos + response.len;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
編譯及測試結果
編譯:
./configure --prefix=/usr/local/nginx(指定安裝部署後的根目錄) --add-module=/home/test/testmodule(新模塊存放目錄)
make
make install
修改配置文件:
配置文件位於/usr/local/nginx/conf/nginx.conf,做如下修改:
location /test {
mytest;
}
測試結果:
參考資料:
陶輝.深入理解Nginx 模塊開發與架構解析.北京:機械工業出版社,2013