《深入理解Nginx》閱讀與實踐(四):簡單的HTTP過濾模塊


一、Nginx的HTTP過濾模塊特征

  一個請求可以被任意個HTTP模塊處理

  在普通HTTP模塊處理請求完畢並調用ngx_http_send_header()發送HTTP頭部或調用ngx_http_output_filter()發送HTTP包體時,才會由這兩個方法一次調用所有的HTTP過濾模塊來處理這個請求。HTTP過濾模塊僅處理服務器發送到客戶端的響應,而不處理客戶端發往服務器的HTTP請求。

  多個過濾模塊的順序的形成以及Nginx自帶的過濾模塊請參考原書。

 

二、編寫一個HTTP過濾模塊

   以向返回給用戶的文本格式響應包體前加一段字符串"[my filter prefix]"為例,展示如何編寫一個HTTP過濾模塊。源代碼來自於《深入理解Nginx》。

1.config文件的編寫

  與前幾篇博文的HTTP模塊不同,HTTP過濾模塊需要HTTP_FILTER_MODULES一項以把所有過濾模塊一同編譯,因此config寫作:

ngx_addon_name=ngx_http_myfilter_module
HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES ngx_http_myfilter_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_myfilter_module.c"

  進行configure時,--add-module=PATH是一樣的。

 

2.編寫模塊基本內容:模塊定義、配置項處理

  由於需要在nginx.conf中加入一項flag類型的add_fix來控制這個過濾模塊的使用與否,與這個配置項處理相關的ngx_http_myfilter_create_conf()、ngx_http_myfilter_merge_conf()、ngx_http_mytest_commands[]需要對應地進行處理。

typedef struct {
    ngx_flag_t enable;
} ngx_http_myfilter_conf_t;

typedef struct {
    ngx_int_t add_prefix;
} ngx_http_myfilter_ctx_t;

 

static void* ngx_http_myfilter_create_conf(ngx_conf_t *cf)
{
    ngx_http_myfilter_conf_t *mycf;
    mycf = (ngx_http_myfilter_conf_t *)ngx_pcalloc(cf->pool,sizeof(ngx_http_myfilter_conf_t));
    if(mycf == NULL) {
        return NULL;
    }
    mycf->enable = NGX_CONF_UNSET;
    return mycf;
}
ngx_http_myfilter_create_conf()
static char* ngx_http_myfilter_merge_conf(ngx_conf_t *cf,void *parent, void *child)
{
    ngx_http_myfilter_conf_t *prev = (ngx_http_myfilter_conf_t *)parent;
    ngx_http_myfilter_conf_t *conf = (ngx_http_myfilter_conf_t *)child;

    ngx_conf_merge_value(conf->enable,prev->enable,0);
    return NGX_CONF_OK;
}
ngx_http_myfilter_merge_conf
static ngx_command_t ngx_http_mytest_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_myfilter_conf_t,enable),
        NULL },
    ngx_null_command
};
ngx_http_mytest_commands[]

  這樣之后才是模塊的上下文和模塊定義:

static ngx_http_module_t ngx_http_myfilter_module_ctx = {
    NULL,
    ngx_http_myfilter_init,
    NULL,
    NULL,

    NULL,
    NULL,
    ngx_http_myfilter_create_conf,
    ngx_http_myfilter_merge_conf
};
ngx_http_myfilter_module_ctx
ngx_module_t ngx_http_myfilter_module = {
    NGX_MODULE_V1,
    &ngx_http_myfilter_module_ctx,
    ngx_http_mytest_commands,
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};
ngx_http_myfilter_module

  從模塊上下文可以看出,過濾功能在模塊完成配置項處理后開始,其初始化方法為ngx_myfilter_init()。

 

3.過濾功能實現

  初始化方法ngx_myfilter_init()的功能僅僅是把當前過濾模塊插入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;

static ngx_int_t ngx_http_myfilter_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_myfilter_header_filter;


    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_myfilter_body_filter;
    return NGX_OK;
}
ngx_int_t ngx_http_myfilter_init()

 

  頭部處理方法是為了確定返回的類型是否為text/plain。如果是,則包體處理方法需要添加前綴。這里把前綴硬編碼至模塊源碼中。

static ngx_int_t
ngx_http_myfilter_header_filter(ngx_http_request_t *r)
{
    ngx_http_myfilter_ctx_t  *ctx;
    ngx_http_myfilter_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_myfilter_module);
    if(ctx) {
        return ngx_http_next_header_filter(r);
    }
    conf = ngx_http_get_module_loc_conf(r,ngx_http_myfilter_module);
    if(conf->enable == 0)
    {
        return ngx_http_next_header_filter(r);
    }
    ctx = ngx_pcalloc(r->pool,sizeof(ngx_http_myfilter_ctx_t));
    if(ctx == NULL)
    {
        return NGX_ERROR;
    }
    ctx->add_prefix = 0;

    ngx_http_set_ctx(r,ctx,ngx_http_myfilter_module);
    if(r->headers_out.content_type.len >= sizeof("text/plain")-1 
            && ngx_strncasecmp(r->headers_out.content_type.data,(u_char *)"text/plain",
                sizeof("text/plain")-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_myfilter_header_filter(r);
}
ngx_http_myfilter_header_filter()

 

  包體處理方法根據頭部處理方法的結果來為包體添加前綴。

static ngx_int_t
ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_http_myfilter_ctx_t *ctx;
    ctx = ngx_http_get_module_ctx(r,ngx_http_myfilter_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 *c1 = ngx_alloc_chain_link(r->pool);
    c1->buf = b;
    c1->next = in;
    return ngx_http_next_body_filter(r,c1);
}
ngx_http_myfilter_body_filter()

 

三、過濾模塊測試

  根據原作者編寫的nginx.conf

    server {
        listen 8080;

        location / {
            root /;
            add_prefix on;
        }
    }

可以看出,需要在/目錄下(系統根目錄)添加一個或多個任意內容的文本文件來進行測試。我寫了一個內容為test的文本文件test.txt。

  輸入curl http://localhost:8080/test.txt,可以看到返回的內容是[my filter prefix]test。

  當然,如果你放在/的不是純文本文件,而是html文件或者其他類型文件,是不會增加這個前綴的。

  另外,把on改成off,你會發現前綴不再出現,說明過濾模塊功能已經關閉。

 

  p.s.此書的后續章節是源碼分析,實踐環節比較少,“《深入理解Nginx》閱讀與實踐”系列可能到此為止。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM