NGINX模塊開發 之 驗證URL參數


1 需求

    要求在瀏覽器地址欄中輸入"localhost/login?user=qifeng&passwd=123456",並在瀏覽器上顯示驗證結果(成功 或 失敗)。以下是在NGINX中添加一個LOGIN模塊的整個處理過程。


2 修改配置

  根據需求修改配置文件nginx.conf,在http{...}的server{...}中增加location配置信息:


圖1 修改配置

(注意:將passwd的值"abcd"改爲“123456”)


3 編寫代碼

3.1 創建源碼目錄

    在NGINX源碼目錄src下新建ext文件夾,src/ext用於存放所有擴展模塊代碼,src/ext/login則用於存放LOGIN模塊的代碼.

    #mkdir -p src/ext/login

    #cd src/ext/login

    #vim ngx_http_login_module.c


3.2 定義配置結構

  LOGIN模塊主要實現的是對用戶(user)和密碼(password)的驗證,因此,配置信息結構中需要包含user字段和password字段,故其結構定義如下:(命名規則:ngx_http_模塊名_(main|srv|loc)conf_t)

/* 配置項結構體:用於存放配置項和對應值 */
typedef struct
{
    ngx_str_t user;
    ngx_str_t passwd;
}ngx_http_login_loc_conf_t;

代碼1 定義配置結構

3.3 設置解析數組

  從圖1中可知:配置項有user、passwd和check.解析這些配置項的配置項的解析是通過配置ngx_command_t數組,配置項解析數組如下:(命名規則:ngx_http_模塊名_commands)

/* 配置項解析數組:配置指令是通過此數組依次解析的. */
static ngx_command_t ngx_http_login_commands[] =
{
    {
        ngx_string("user"),                            /* 配置項名 */
        NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,            /* 所屬模塊和配置值個數 */
        ngx_conf_set_str_slot,                         /* 解析該配置項的回調 */
        NGX_HTTP_LOC_CONF_OFFSET,                      /* 配置存儲位置 */
        offsetof(ngx_http_login_loc_conf_t, name),     /* 配置值賦給的變量 */
        NULL
    },
    {
        ngx_string("passwd"),
        NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
        ngx_conf_set_str_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_login_loc_conf_t, passwd),
        NULL
    },
    {
        ngx_string("check"),
        NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
        ngx_http_login_check,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL
    },
    ngx_null_command
};

代碼2 配置項解析數組


3.4 設置配置回調

    定義模塊配置解析過程中各階段的處理回調:(命名規則:ngx_http_模塊名_module_ctx)

static ngx_http_module_t ngx_http_login_module_ctx =                            
{                                                                                  
    NULL,                               /* 讀入配置前的回調 */
    NULL,                               /* 讀入配置後的回調 */
    NULL,                               /* 創建main配置時的回調 */
    NULL,                               /* 初始化main配置時的回調 */
    NULL,                               /* 創建srv配置時的回調 */
    NULL,                               /* 合併srv配置時的回調 */
    ngx_http_login_create_loc_conf,     /* 創建loc配置時的回調 - LOGIN模塊的配置在location中,因此需要註冊此函數 */
    NULL                                /* 合併loc配置時的回調 */
};
代碼3 配置解析各階段回調

  絕大多數handler只需要設置最後面回調函數,即:設置ngx_http_xxx_create_loc_conf和ngx_http_xxx_merge_loc_conf.前者用於特定location的內存分配,而後者用來設置默認值以及合併繼承過來的配置值,同時往往還負責配置值的合法性驗證,如果不合法,則退出後續處理。

3.5 設置模塊屬性

  NGINX中包含了很多模塊,所有模塊都是通過ngx_module_t類型,但每個模塊擁有不同的屬性。通過設置各模塊不同的屬性來控制各模塊的行爲。LOGIN模塊的模塊屬性設置如下:

ngx_module_t ngx_http_login_module =                                            
{                                                                               
    NGX_MODULE_V1,                      /* 含多個字段:一般使用此宏賦值 */
    &ngx_http_login_module_ctx,         /* 當前模塊上下文回調 */
    ngx_http_login_commands,            /* 配置指令解析數組 */
    NGX_HTTP_MODULE,                    /* 模塊類型 */
    NULL,                               /* 初始化master時的回調 */
    NULL,                               /* 初始化module時的回調 */
    NULL,                               /* 初始化工作進程時的回調 */
    NULL,                               /* 初始化線程時的回調 */
    NULL,                               /* 退出線程時的回調 */
    NULL,                               /* 退出工作進程時的回調 */
    NULL,                               /* 退出master時的回調 */
    NGX_MODULE_V1_PADDING               /* 含多個字段:一般使用此宏賦值 */
};

代碼4 設置模塊屬性

3.6 編寫函數代碼

  在代碼3中的定義配置解析各階段的回調設置了創建loc配置時的回調ngx_http_login_create_loc_conf(),其主要功能是爲location配置分配內存空間,實現代碼如下:

/******************************************************************************
 **函數名稱: ngx_http_login_create_loc_conf
 **功    能: 爲LOGIN的配置結構分配內存空間
 **輸入參數:
 **     cf: 配置信息對象
 **輸出參數: NONE
 **返    回: 存儲LOGIN配置的內存地址
 **實現描述:
 **注意事項:
 **作    者: # Qifeng.zou # 2014.05.26 #
 ******************************************************************************/
static void *ngx_http_login_create_loc_conf(ngx_conf_t *cf)                  
{
    ngx_http_login_loc_conf_t *llcf = NULL;

    llcf = ngx_palloc(cf->pool, sizeof(ngx_http_login_loc_conf_t));
    if(NULL == llcf)
    {
        return NGX_CONF_ERROR;
    }

    memset(llcf, 0, sizeof(ngx_http_login_loc_conf_t));

    return lcf;
}

代碼5 創建loc配置時的回調

  在代碼1中的定義配置項解析數組中設置了配置項check的回調ngx_http_login_check(),其主要功能解析配置項check,並設置解析後的處理函數,實現代碼如下:

/******************************************************************************
 **函數名稱: ngx_http_login_check
 **功    能: 配置項check的解析處理回調
 **輸入參數:
 **     cf: 配置信息對象
 **     cmd: 當前正在解析的配置項解析數組
 **     conf: 自定義配置結構體ngx_http_login_loc_conf_t的地址
 **輸出參數: NONE
 **返    回: NGX_CONF_OK:Success Other:Failed
 **實現描述:
 **注意事項:
 **作    者: # Qifeng.zou # 2014.05.26 #
 ******************************************************************************/
static int ngx_http_login_check(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t *clcf = NULL;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_login_check_handler;
    ngx_conf_set_str_slot(cf, cmd, conf);

    return NGX_CONF_OK;
}
代碼6 CHECK配置項解析處理

  在ngx_http_login_check()中對配置項check進行了解析處理,同時設置瞭解析後的處理函數:ngx_http_login_check_handler(),其主要功能是檢測URL中的參數user和passwd的合法性,並返回最終的驗證結果。實現代碼如下:

/******************************************************************************
 **函數名稱: ngx_http_login_check_handler
 **功    能: 驗證user和passwd的合法性
 **輸入參數:
 **     r: HTTP請求.
 **輸出參數: NONE
 **返    回: 0:Success !0:Failed
 **實現描述:
 **    1.必須是GET或HEAD請求
 **    2.獲取LOGIN配置信息
 **    3.提取URL參數
 **    4.驗證URL參數合法性
 **    5.發送應答數據
 **注意事項:
 **作    者: # Qifeng.zou # 2014.05.26 #
 ******************************************************************************/
static int ngx_http_login_check_handler(ngx_http_request_t *r)
{
    ngx_int_t ret = 0;
    ngx_str_t user, passwd, repmsg;
    ngx_http_login_loc_conf_t *llcf = NULL;
 
    /* 1. 必須是GET或HEAD請求 */
    if(!(r->method &(NGX_HTTP_GET | NGX_HTTP_HEAD)))
    {
        return NGX_HTTP_NOT_ALLOWED;
    }

    ret = ngx_http_discard_request_body(r); /* 丟棄請求報文體 */
    if(NGX_OK != ret)
    {
        return ret;
    }
                                                                                
    /* 2. 獲取LOGIN配置信息 */
    llcf = ngx_http_get_module_loc_conf(r, ngx_http_login_module);

    /* 3. 提取URL參數
      在網址輸入欄輸入"localhost/query?user=qifeng&passwd=123456"
          則:r->args = "user=qifeng&passwd=123456",因此提取網頁參數只需對r->args進行解析即可. */
    query_string(r, &user, "user"); /* 函數:query_string()的功能是從字串r->args中找到對應的參數及值(請自己實現) */
    query_string(r, &passwd, "passwd");

    /* 4. 驗證URL參數合法性 */
    if((user.len == llcf->user.len)
        && (0 == strcmp(llcf->user.data, user.data)
        && (passwd.len == llcf->passwd.len)
        && (0 == strcmp(llcf->user.data, passwd.data))
    {
        ngx_str_set(&repmsg, "Success");
    }
    else
    {
        ngx_str_set(&repmsg, "Failed");
    }

    /* 5. 發送應答數據 */
    return ngx_http_send_rep(r, &repmsg);
}

/******************************************************************************
 **函數名稱: ngx_http_send_rep
 **功    能: 發送應答數據
 **輸入參數:
 **     r: Http request.
 **     repmsg: 應答消息
 **輸出參數: NONE
 **返    回: 0:Success !0:Failed
 **實現描述:
 **    1.發送應答頭
 **    2.發送應答體
 **注意事項:
 **作    者: # Qifeng.zou # 2014.05.26 #
 ******************************************************************************/
static int ngx_http_send_rep(ngx_http_request_t *r, const ngx_str_t *repmsg)
{
    ngx_int_t ret = 0;
    ngx_buf_t *b = NULL;
    ngx_chain_t out;
    ngx_str_t type = ngx_string("text/plain");


    /* 1.發送應答頭 */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_type = type;
    r->headers_out.content_length_n = repmsg->len;

    ret = ngx_http_send_header(r);
    if((NGX_ERROR == ret) || (ret > NGX_OK) || (r->header_only))
    {
        return ret;
    }

    /* 2.發送應答體 */
    b = ngx_create_temp_buf(r->pool, repmsg->len);
    if(NULL == b)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ngx_memcpy(b->pos, repmsg->data, repmsg->len);
    b->last = b->pos + repmsg->len;
    b->last_buf = 1;

    out.buf = b;
    out.next = NULL;

    return ngx_http_output_filter(r, &out);
}
代碼7 合法性驗證

4 編譯工程

  Apache, IIS等其他服務器的第三方模塊是通過動態鏈接庫的方式加入到程序中,而NGINX的第三方模塊需要加入NGINX源碼工程一同編譯。

  NGINX提供了一種簡單的方式將第三方開發的模塊編譯到NGINX中。首先,將源代碼全部放入一個目錄下,同時在該目錄下創建一個名爲config的文件(config的配置格式在4.1節描述);其次,在configure腳本執行時加入參數--add-module=PATH(PATH爲第三方模塊的源碼路徑)。


4.1 編輯編譯配置

  完成以上編輯工作後,最後的工作就是將編寫的代碼加入NGINX工程。其處理步驟如下:

    #cd src/ext/login

    #vim config

    在config文件中輸入如下內容:

ngx_addon_name=ngx_http_login_module
HTTP_MODULES="$HTTP_MODULES ngx_http_login_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_login_module.c"
代碼8 編譯配置

  注意:以上config文件中的等號(=)前後不能有空格,否則執行./configure --add-module=src/ext/login時,並不能將login模塊加入到工程編譯環境中.

圖2 變量值與等號之間有空格


4.2 加入編譯工程

  完成config的編輯後,LOGIN模塊還沒有加入到編譯工程中。NGINX的編譯腳本比較複雜,功能也十分強大,在編譯之前必須告知NGINX編譯腳本到指定的路徑去添加LOGIN模塊:

    #./configure --with-debug --add-module=src/ext/login

    #make

    #make install


4.3 最終測試結果

  輸入“localhost/login?user=qifeng&passwd=123456”將會返回成功。如下圖所示:


圖3 驗證成功


  輸入“localhost/login?user=zhangsan&passwd=123456”將會返回失敗。如下圖所示:


圖4 驗證失敗



發佈了66 篇原創文章 · 獲贊 91 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章