1. 程式人生 > >Nginx模組開發之最簡單的Hello模組

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_V1NGX_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了..