Nginx模組開發之最簡單的Hello模組
nginx模組開發並不是那麼容易, 從行數上來講, 淘寶給出的tengine給出的那個所謂hello模組的長度也到了245行, 要想真正獨立寫出這麼多程式碼, 對於我來說是非常難的.
245行, 如果是nodejs, 已經可以寫一個比較完善的檔案伺服器了. 要想完全理解這個hello模組, 有c基礎的也怕是要花不少時間, 像我這樣沒有c經驗的, 更是難上加難.
我決定寫一個真正的hello模組,也就是最最簡單的那種,能自己寫出來,也算是至少nginx模組開發入門了.
這個hello模組作用就是當訪問/test
的時候, 返回一段固定的html程式碼(一個字串).
此模組一共不到60行, 理解此模組比理解淘寶教程的模組要簡單幾倍.
先上全程式碼
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
static char *set(ngx_conf_t *, ngx_command_t *, void *);
static ngx_int_t handler(ngx_http_request_t *);
static ngx_command_t test_commands[] = {
{
ngx_string("test"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS ,
set,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
static ngx_http_module_t test_ctx = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
ngx_module_t ngx_http_test_module = {
NGX_MODULE_V1,
&test_ctx,
test_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL , NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
static char *set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_core_loc_conf_t *corecf;
corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
corecf->handler = handler;
return NGX_CONF_OK;
};
static ngx_int_t handler(ngx_http_request_t *req) {
u_char html[1024] = "<h1>This is Test Page!</h1>";
req->headers_out.status = 200;
int len = sizeof(html) - 1;
req->headers_out.content_length_n = len;
ngx_str_set(&req->headers_out.content_type, "text/html");
ngx_http_send_header(req);
ngx_buf_t *b;
b = ngx_pcalloc(req->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(req, &out);
}
這個檔案的路徑是src/http/module/ngx_http_test_module.c
.
光是放這個是nginx的makefile是不知道的,它不會去編譯新增的模組, 還需要在auto/modules
這個檔案中加入
if [ $HTTP_ACCESS = YES ]; then
HTTP_MODULES="$HTTP_MODULES $HTTP_ACCESS_MODULE"
HTTP_SRCS="$HTTP_SRCS $HTTP_ACCESS_SRCS"
fi
# 上面是原有的, 這裡才是加上的
HTTP_MODULES="$HTTP_MODULES ngx_http_test_module"
HTTP_SRCS="$HTTP_SRCS src/http/modules/ngx_http_test_module.c"
auto是用來生成Makefile的很多shell指令碼,Nginx沒有用那些構建工具來製作自己的Makefile, 而是自己寫了大量的shell指令碼, 學習這些指令碼對於自己的shell程式設計也是很有幫助的. nginx的編譯的生成檔案都在objs
中, 清晰明瞭, 因此make
clean
也只是呼叫rm -rf objs
即可, 非常簡潔.
總之加上上面兩句話, nginx就知道你要新增這個模組了, 順序應該不是很要緊(其實我是沒試過).
這樣我們的模組依然不起作用, 還需要修改配置檔案, nginx啟動完全依靠那個conf/nginx.conf
的配置檔案!
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /test {
test;
}
我們在http中的server中加上location /test
來插入我們的模組.
執行之, 在瀏覽器中訪問你的域名/test
就能看到This
is Test Page
幾個大字, 因為是<h1>
的嘛!
解釋下這段程式碼吧.
#include <ngx_core.h>
#include <ngx_http.h>
#include <ngx_config.h>
包含三個關鍵標頭檔案, 這沒什麼異議, 注意, 我們寫的模組基本都是http的.
static char *set(ngx_conf_t *, ngx_command_t *, void *);
static ngx_int_t handler(ngx_http_request_t *);
宣告兩個函式, 這是兩個非常重要的函式, 後面主要講.
command註冊
static ngx_command_t test_commands[] = {
{
ngx_string("test"),
NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
set,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
這是定義一個配置命令資訊陣列(為何是陣列暫時還真不知道), 陣列最後一個元素都是ngxnullcommand.
結構體第一個引數尤為重要, 這裡是test, 指的是我們在配置檔案中輸入test(不是路徑的/test
).這樣指定後, nginx在讀取配置的時候讀到test命令, 才會把接管權給我們.也就是把請求轉給我們去處理.
第二個引數代表我們的模組註冊的是http location的命令, 並且接受0個引數.http location當然就是指這個命令觸發是跟路徑有關的.
事實上大量的模組觸發都跟路徑相關, 比如php, php就是接管所有後綴是
.php
的location.php的nginx模組配置如下所示
location ~ \.php$ {
root /home/www;
fastcgi_pass 127.0.0.1:3344;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
include fastcgi_params;
}
第三個引數是set, 這是一個函式指標,也就是我們一開始宣告的兩個關鍵函式中的一個,讀到我們註冊的test
這個命令的時候觸發的, 我們一般在set中寫上託管http請求的handler函式.
第四個引數是offset型別, 用來結構體的偏移的, 不再hello模組的討論範圍, loc conf型別就直接是NGX_HTTP_LOC_CONF_OFFSET
就行.
第五個引數就是具體offset的值, 我們這裡只有一個命令, 沒有引數, 輸入0即可.
第六個引數NULL即可, 作用未知.
回撥函式
寫完命令註冊, 我們還需要一個context,至於為何叫上下文我也不知道, 但這種把函式作為引數的方法在nodejs中一般叫回調函式,核心程式設計師喜歡叫它hook, 鉤子函式, 我覺得也很形象.但是在hello模組中,我們可以完全不用這些回撥機會.
static ngx_http_module_t test_ctx = {
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
nginx一共提供8個回撥機會, 具體是什麼時候用不在hello模組討論範圍, 這裡都設定為NULL即可.
模組結構體
最後寫上真正暴露給外面的模組結構體
ngx_module_t ngx_http_test_module = {
NGX_MODULE_V1,
&test_ctx,
test_commands,
NGX_HTTP_MODULE,
NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NGX_MODULE_V1_PADDING
};
其中NGX_MODULE_V1
和NGX_MODULE_V1_PADDING
都是巨集定義,
不必去管
只需知道第二個放上回調函式陣列, 第三個是註冊命令, 第四個是模組型別即可.後面7個NULL.
set函式
前面說到, set函式就是給配置資訊掛上託管http請求的handler函式的.
static char *set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_core_loc_conf_t *corecf;
corecf = ngx_http_conf_get_module_loc_conf(cf->pool, ngx_http_core_module);
corecf->handler = handler;
return NGX_CONF_OK;
}
返回型別是char *
,它有兩種返回值, 一個是NGX_CONF_OK
也就是NULL,
另一個是NGX_CONF_ERROR
也就是(void
*) -1
.
裡面最重要的就是corecf->handler = handler
了, 這句話把handler函式掛到corecf的handler屬性上.
handler函式
這是最重要的函式, nodejs中寫http伺服器, 完全只要寫個handler就行了.nginx卻要寫前面一堆廢話.
static ngx_int_t handler(ngx_http_request_t *req) {
char html[1024] = "<h1>This is Test Page</h1>";
int len = sizeof(html) - 1;
req->headers_out.status = 200;
req->headers_out.content_length_n = len;
ngx_str_set(&req->headers_out.content_type, "text/html");
ngx_http_send_header(req);
ngx_buf_t *b;
b = ngx_pcalloc(req->pool, sizeof(ngx_buf_t));
b->pos = html;
b->last = html + len;
b->memory = 1;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(req, &out);
};
其實主菜才是最直觀的, 前面是設定http的返回頭部, 後面是設定http body.簡單至極.每每寫到handler部分都神清氣爽, 感覺自己也會用c了..