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

1、Nginx的HTTP過濾模塊特徵

  一個請求能夠被任意個HTTP模塊處理html

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

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

 

2、編寫一個HTTP過濾模塊

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

1.config文件的編寫

  與前幾篇博文的HTTP模塊不一樣,HTTP過濾模塊須要HTTP_FILTER_MODULES一項以把全部過濾模塊一同編譯,所以config寫做:ide

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()。url

 

3.過濾功能實現

  初始化方法ngx_myfilter_init()的功能僅僅是把當前過濾模塊插入Nginx全部過濾模塊的鏈表中。spa

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()

 

3、過濾模塊測試

  根據原做者編寫的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》閱讀與實踐」系列可能到此爲止。

相關文章
相關標籤/搜索