【Nginx】開發一個HTTP過濾模組

來源:互聯網
上載者:User

標籤:部分   返回   lte   回應標頭   出錯   入口   stl   前向聲明   保留字   

與HTTP處理模組不同。HTTP過濾模組的工作是對發送給使用者的HTTP響應做一些加工。

server返回的一個響應能夠被隨意多個HTTP過濾模組以流水線的方式依次處理。HTTP響應分為頭部和包體,ngx_http_send_header和ngx_http_output_filter函數分別負責發送頭部和包體。它們會依次調用各個過濾模組對待發送的響應進行處理。
HTTP過濾模組能夠單獨處理響應的頭部或者包體或同一時候處理二者。處理頭部和包體的方法原型分別例如以下,它們在HTTP架構模組ngx_http_core_module.h中定義:

// 過濾模組處理HTTP頭部的方法原型typedef ngx_int_t (*ngx_http_output_header_filter_pt)(ngx_http_request_t *r); // 過濾模組處理HTTP包體的方法原型typedef ngx_int_t (*ngx_http_output_body_filter_pt)(ngx_http_request_t *r, ngx_chain_t *chain);


上面是兩個函數指標的聲明,HTTP過濾模組就是依賴這樣的指標串接成一個單鏈表的,每一個HTTP過濾模組至少定義一個上述函數指標。入口鏈表例如以下所看到的:
// HTTP過濾模組鏈表入口extern ngx_http_output_header_filter_pt  ngx_http_top_header_filter;extern ngx_http_output_body_filter_pt    ngx_http_top_body_filter;


當HTTP模組調用ngx_http_send_header發送頭部時,就從ngx_http_top_header_filter指向的模組開始遍曆全部的HTTP頭部過濾模組並處理;當HTTP模組調用ngx_http_output_filter發送包體時。就從ngx_http_top_body_filter指向的模組開始遍曆全部的HTTP包體過濾模組並處理。函數ngx_http_send_header代碼例如以下:
ngx_int_tngx_http_send_header(ngx_http_request_t *r){    if (r->header_sent) {        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,                      "header already sent");        return NGX_ERROR;    }     if (r->err_status) {        r->headers_out.status = r->err_status;        r->headers_out.status_line.len = 0;    }     // 從頭遍曆HTTP頭部過濾模組    return ngx_http_top_header_filter(r);}


函數ngx_http_top_header_filter代碼例如以下:
ngx_int_tngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in){    ngx_int_t          rc;    ngx_connection_t  *c;     c = r->connection;     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,                   "http output filter \"%V?%V\"", &r->uri, &r->args);     // 遍曆HTTP包體過濾模組    rc = ngx_http_top_body_filter(r, in);     if (rc == NGX_ERROR) {        /* NGX_ERROR may be returned by any filter */        c->error = 1;    }     return rc;}


在每一個HTTP過濾模組中至少存在上述兩種函數指標中的一個。聲明例如以下:
static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;


增加HTTP過濾模組單鏈表的方法例如以下:
static ngx_int_tngx_http_addition_filter_init(ngx_conf_t *cf){    // 從頭部增加    ngx_http_next_header_filter = ngx_http_top_header_filter;    ngx_http_top_header_filter = ngx_http_addition_header_filter;     // 從頭部增加    ngx_http_next_body_filter = ngx_http_top_body_filter;    ngx_http_top_body_filter = ngx_http_addition_body_filter;     return NGX_OK;}


當本模組被初始化時。調用上述函數。將自己增加到鏈表頭部。

類似上面ngx_http_addition_filter_init這種初始化函數在什麼時候被調用呢?答案是依該方法放在ngx_http_module_t結構體的哪個成員而定。

一般而言,大多數官方HTTP過濾模組通常放在ngx_http_module_t.postconfiguration函數指標中,讀取全然部配置項後被回調。各個模組初始化的順序是怎麼樣的呢?這由configure命令產生的ngx_modules.c檔案裡的ngx_modules數組的排列順序決定。數組中靠前的模組先初始化。因為過濾模組是將自己插入到鏈表頭部,使得ngx_modules數組中過濾模組的排列順序和它們實際啟動並執行順序相反。至於ngx_modules數組中的排列順序,又是由其他指令碼決定的。
以下是開發一個簡單的HTTP過濾模組的過程。

首先定義兩個結構體:

typedef struct{    ngx_flag_t  enable;     // 儲存on或者off} ngx_http_myfilter_conf_t; typedef struct{    ngx_int_t  add_prefix;} ngx_http_myfilter_ctx_t;  // HTTP上下文結構體


ngx_http_myfilter_conf_t用於儲存標誌是否開啟過濾功能的配置項,這裡我們使用預設的配置項解析方法對此配置項進行解析。ngx_http_myfilter_ctx_t則用於儲存一個HTTP請求的上下文,由於HTTP包體的處理過程不是一次完畢的,也就是說處理包體的函數會被調用多次,所以我們須要一個標誌來記錄當前是否已經由過濾模組進行過處理了。HTTP上下文相當於一張表,表中記錄了該請求當前的處理記錄。

這樣。當一個請求被拆分成多次處理時。同一個處理函數就行瞭解該請求已經運行到哪裡了,從而接著當前的進度進行處理。


HTTP模組結構ngx_http_module_t定義例如以下:

static ngx_http_module_t  ngx_http_myfilter_module_ctx ={    NULL,                             /* preconfiguration */    ngx_http_myfilter_init,           /* postconfiguration */     NULL,                             /* create_main_conf */    NULL,                             /* init_main_conf */     NULL,                             /* create_srv_conf */    NULL,                             /* merge_srv_conf */     ngx_http_myfilter_create_conf,    /* create_loc_conf */    ngx_http_myfilter_merge_conf      /* merge_loc_conf */};


三個函數借口的作用分別例如以下:
  • ngx_http_myfilter_init:初始化該過濾模組。也就是將該模組插入到HTTP過濾模組單鏈表中,插入方法已經在上面介紹過了。
  • ngx_http_myfilter_create_conf:給儲存配置項的結構體ngx_http_myfilter_conf_t分配空間並初始化結構體成員
  • ngx_http_myfilter_merge_conf:合并出如今main、srv層級的同名配置項

另一個很重要的結構ngx_command_t定義例如以下:
static ngx_command_t  ngx_http_myfilter_commands[] ={    {        ngx_string("myfilter"),        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_conf_set_flag_slot解析配置項myfilter,將解析出的配置項參數存入結構體ngx_http_myfilter_conf_t的enable成員中。配置項myfilter僅僅能是“on”或者“off”。


注意,HTTP過濾模組仍然屬於一個HTTP模組,也就是說:ngx_module_t.type = NGX_HTTP_MODULE
以下是HTTP過濾模組的核心部分,即兩個指標分別指向的函數。

首先來看看處理HTTP回應標頭部的函數:

// 處理請求的頭部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;     //假設不是返回成功。這時是不須要理會是否加首碼的,直接交由下一個過濾模組    //處理響應碼非200的情形    if (r->headers_out.status != NGX_HTTP_OK)        return ngx_http_next_header_filter(r);     //擷取http上下文    ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);    if (ctx)        //該請求的上下文已經存在,這說明該模組已經被調用過1次。直接交由下一個過濾模組處理        return ngx_http_next_header_filter(r);     //擷取儲存配置項的ngx_http_myfilter_conf_t結構體    conf = ngx_http_get_module_loc_conf(r, ngx_http_myfilter_module);     //假設enable成員為0。也就是設定檔裡沒有配置add_prefix配置項,    //或者add_prefix配置項的參數值是off,這時直接交由下一個過濾模組處理    if (conf->enable == 0)        return ngx_http_next_header_filter(r);     //構造http上下文結構體ngx_http_myfilter_ctx_t    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_myfilter_ctx_t));    if (ctx == NULL)        return NGX_ERROR;     //add_prefix為0表示不加首碼    ctx->add_prefix = 0;     //將構造的上下文設定到當前請求中    ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);     //myfilter過濾模組僅僅處理Content-Type是"text/plain"類型的http響應    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;    //1表示須要在http包體前增加首碼         //假設處理模組已經在Content-Length寫入了http包體的長度,因為        //我們增加了前置詞字元串,所以須要把這個字串的長度也增加到        //Content-Length中        if (r->headers_out.content_length_n > 0)            r->headers_out.content_length_n += filter_prefix.len;    }     //交由下一個過濾模組繼續處理    return ngx_http_next_header_filter(r);}


這裡有幾點要注意。當該模組遇到錯誤的響應或模組本身出錯時,不應該退出程式而是應該交由下一個HTTP過濾模組處理,這就是上述代碼中多次調用ngx_http_next_header_filter函數的原因。還有就是函數首先要推斷在設定檔裡是否開啟此過濾功能並檢查響應的類型。流程圖例如以下:

接下來是處理HTTP包體的函數:
//將在包體中加入這個首碼static ngx_str_t filter_prefix = ngx_string("~~~~~~~This is a prefix~~~~~~~\n"); // 處理請求的包體static ngx_int_t ngx_http_myfilter_body_filter(ngx_http_request_t *r, ngx_chain_t *in){    ngx_http_myfilter_ctx_t  *ctx;      // 獲得HTTP上下文    ctx = ngx_http_get_module_ctx(r, ngx_http_myfilter_module);      //假設擷取不到上下文,或者上下文結構體中的add_prefix為0或者2時,    //都不會加入首碼,這時直接交給下一個http過濾模組處理    if (ctx == NULL || ctx->add_prefix != 1)        return ngx_http_next_body_filter(r, in);      //將add_prefix設定為2,這樣即使ngx_http_myfilter_body_filter再次回調時。也不會反覆加入首碼    ctx->add_prefix = 2;      //從請求的記憶體池中分配記憶體,用於儲存字串首碼    ngx_buf_t* b = ngx_create_temp_buf(r->pool, filter_prefix.len);      //將ngx_buf_t中的指標正確地指向filter_prefix字串    b->start = b->pos = filter_prefix.data;    b->last = b->pos + filter_prefix.len;      //從請求的記憶體池中產生ngx_chain_t鏈表,將剛分配的ngx_buf_t設定到    //其buf成員中,並將它加入到原先待發送的http包體前面    ngx_chain_t *cl = ngx_alloc_chain_link(r->pool);    cl->buf = b;    cl->next = in;      //調用下一個模組的http包體處理方法。注意這時傳入的是新產生的cl鏈表    return ngx_http_next_body_filter(r, cl);}


依據這一句cl->next = in能夠推斷。我們將filter_prefix包括的字串加入到了HTTP響應包體的前面。

以上函數的流程圖例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbmVzdGxlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

以下是編譯及示範過程。我們在第一個mytest模組的基礎上對發送的資訊加入首碼。為了把兩個模組同一時候編譯進nginx,我們在config時輸入例如以下命令:

./configure --add-module="/work/nginx/modules/mytest /work/nginx/modules/myfilter"


然後檢查是否成功包括了兩個模組,這裡我們能夠查看儲存全部模組的數組ngx_modules[]:
vim objs/ngx_modules.c


結果例如以下:

能夠看到,HTTP模組mytest和HTTP過濾模組myfilter都被包括進來了。

接下來就是make和make install了。成功安裝後。我們還要改動設定檔:

vim /usr/local/nginx/conf/nginx.conf


設定檔改動後例如以所看到的:

當client輸入的URI為/nestle時,就會啟動mytest和myfilter兩個模組,執行結果例如以下所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvbmVzdGxlcg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

能夠看到,HTTP過濾模組成功的在響應包體的內容前面加入了我們預設的字串。
整個HTTP過濾模組的完整代碼例如以下:

#include <ngx_config.h>#include <ngx_core.h>#include <ngx_http.h> // 配置項結構體typedef struct {ngx_flag_t enable;} ngx_http_myfilter_conf_t;// 上下文結構體typedef struct {ngx_int_t add_prefix;} ngx_http_myfilter_ctx_t;ngx_module_t ngx_http_myfilter_module;// 前向聲明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_str_t filter_prefix = ngx_string("~~~~~~~This is a prefix~~~~~~~\n");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;// 0表示不須要加入首碼ngx_http_set_ctx(r, ctx, ngx_http_myfilter_module);// 僅僅處理Content-Type為text/plain類型的HTTP請求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;// 1表示須要加入首碼if (r->headers_out.content_length_n > 0)r->headers_out.content_length_n += filter_prefix.len;// 響應包體長度添加}return ngx_http_next_header_filter(r);}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;// 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 *cl = ngx_alloc_chain_link(r->pool);cl->buf = b;cl->next = in;return ngx_http_next_body_filter(r, cl);// 跳到下一個過濾模組}// 初始化HTTP過濾模組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;}// 建立儲存配置項的結構體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;}// 合并配置項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;}static ngx_command_t ngx_http_myfilter_commands[] = {    {        ngx_string("myfilter"),        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}; // HTTP架構初始化時調用的八個函數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,}; // 定義一個HTTP模組ngx_module_t ngx_http_myfilter_module = {    NGX_MODULE_V1,  // 0,0,0,0,0,0,1    &ngx_http_myfilter_module_ctx,    ngx_http_myfilter_commands,    NGX_HTTP_MODULE,    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NGX_MODULE_V1_PADDING,  // 0,0,0,0,0,0,0,0,保留欄位};



參考:《深入理解Nginx》第六章。

【Nginx】開發一個HTTP過濾模組

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.