nginx源碼分析(9)——filter模組

來源:互聯網
上載者:User

        phase handler處理中介紹了content handler用於產生響應內容,隨便找一個content phase的模組,比如:ngx_http_static_module.c,會發現在content handler中會調用ngx_http_send_header,然後最後調用ngx_http_output_filter。這兩個函數就是發送回應標頭部和響應體的,在nginx中輸出內容是通過filter完成的。

        filter模組用於過濾和輸出響應內容,nginx將所有的filter組織成只有頭結點的單鏈表(實際上就是棧),這個頭結點分別是ngx_http_top_header_filter(過濾頭部)和ngx_http_top_body_filter(過濾響應體)。在每次初始化一個filter模組時,都會將當前filter鏈表的頭結點ngx_http_top_header_filter和ngx_http_top_body_filter分別儲存為ngx_http_next_header_filter和ngx_http_next_body_filter,同時將本模組的filter函數儲存為ngx_http_top_header_filter和ngx_http_top_body_filter,然後在本模組filter函數的最後部分調用ngx_http_next_header_filter和ngx_http_next_body_filter,這樣就實現了所有filter模組的鏈式調用。由於filter模組的添加實際上就是不停的向鏈表的頭push節點,所以後添加的模組比先添加的模組在filter鏈中靠前。

        先看一下ngx_http_send_header函數:

ngx_int_tngx_http_send_header(ngx_http_request_t *r){    if (r->err_status) {        r->headers_out.status = r->err_status;        r->headers_out.status_line.len = 0;    }    return ngx_http_top_header_filter(r);}
        實現很簡單,就是調用ngx_http_top_header_filter函數,然後這個函數後調用接下來的filter直到filter鏈遍曆完為止。ngx_http_output_filter與之類似,調用ngx_http_top_body_filter。

        filter的添加都是在http模組的postconfiguration回呼函數中完成的,這部分調用是在ngx_http_block中:

    for (m = 0; ngx_modules[m]; m++) {        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {            continue;        }        module = ngx_modules[m]->ctx;        if (module->postconfiguration) {            if (module->postconfiguration(cf) != NGX_OK) {                return NGX_CONF_ERROR;            }        }    }

        回呼函數的調用順序是遍曆ngx_modules數組,然後逐一調用postconfiguration函數,那麼filter模組在ngx_modules數組中的順序就決定了其filter函數在filter鏈中位置。下面是ngx_modules數組的內容:

ngx_module_t *ngx_modules[] = {    &ngx_core_module,    //……    &ndk_http_module,    &ngx_http_write_filter_module,    &ngx_http_header_filter_module,    &ngx_http_chunked_filter_module,    &ngx_http_range_header_filter_module,    &ngx_http_gzip_filter_module,    &ngx_http_postpone_filter_module,    &ngx_http_ssi_filter_module,    &ngx_http_charset_filter_module,    &ngx_http_userid_filter_module,    &ngx_http_headers_filter_module,    &ngx_http_set_misc_module,    &ngx_http_echo_module,    &ngx_http_copy_filter_module,    &ngx_http_range_body_filter_module,    &ngx_http_not_modified_filter_module,    NULL};
        可以看到ngx_http_write_filter_module和ngx_http_header_filter_module兩個模組是最靠前的兩個filter模組,所以它們處於filter鏈的最末尾,也就是最後執行。這兩個模組就是用來輸出header和body的,下面具體分析一下ngx_http_write_filter_module,ngx_http_header_filter_module與之類似。

        ngx_http_write_filter_module模組用於輸出響應體,在調用時需要傳入兩個參數ngx_request_t和ngx_chain_t,第一個就是請求,第二個參數是輸出內容的buffer鏈表,通過buffer的last欄位標識最後一個buffer。這個模組的filter函數是ngx_http_write_filter,完成的功能很簡單就是遍曆buffer鏈表,然後輸出響應內容。這裡還需要注意一點,ngx_request_t有個out欄位用來儲存上一次沒有發送的chain,當接收到新chain時,需要將新chain串連到舊chain。下面看一下代碼。

    c = r->connection;    if (c->error) {        return NGX_ERROR;    }    size = 0;/* 待輸出的內容的大小 */    flush = 0;/* 是否需要flush */    last = 0;/* 是否是最後一個buffer */    ll = &r->out;/* 儲存上次沒有輸出的ngx_buf_t */
        這裡對使用的變數初始化。
    /* find the size, the flush point and the last link of the saved chain */    for (cl = r->out; cl; cl = cl->next) {        ll = &cl->next;        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,                       "write old buf t:%d f:%d %p, pos %p, size: %z "                       "file: %O, size: %z",                       cl->buf->temporary, cl->buf->in_file,                       cl->buf->start, cl->buf->pos,                       cl->buf->last - cl->buf->pos,                       cl->buf->file_pos,                       cl->buf->file_last - cl->buf->file_pos); #if 1        // …… #endif        size += ngx_buf_size(cl->buf);        if (cl->buf->flush || cl->buf->recycled) {            flush = 1;        }        if (cl->buf->last_buf) {            last = 1;        }    }
        這段代碼統計上次沒有發送的chain的大小,以及是否需要flush還有是不是最後一個buffer。
    /* add the new chain to the existent one */    for (ln = in; ln; ln = ln->next) {        cl = ngx_alloc_chain_link(r->pool);        if (cl == NULL) {            return NGX_ERROR;        }        cl->buf = ln->buf;        /* ll原本儲存的是old buf的最後一個節點的next的地址,這裡會將new buf串連到old buf後 */        *ll = cl;        ll = &cl->next;        ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,                       "write new buf t:%d f:%d %p, pos %p, size: %z "                       "file: %O, size: %z",                       cl->buf->temporary, cl->buf->in_file,                       cl->buf->start, cl->buf->pos,                       cl->buf->last - cl->buf->pos,                       cl->buf->file_pos,                       cl->buf->file_last - cl->buf->file_pos);#if 1        // ……#endif        size += ngx_buf_size(cl->buf);        if (cl->buf->flush || cl->buf->recycled) {            flush = 1;        }        /* 需要輸出的最後一個buf */        if (cl->buf->last_buf) {            last = 1;        }    }
        將新chain添加的舊chain後,並統計size、flush和last。
        接下來就是對chain進行處理,這會涉及一些標記,先來處理一下它們:

        1. clcf->postpone_output:由於處理postpone_output指令,用於設定延時輸出的閾值。比如指令“postpone s”,當輸出內容的size小於s,並且不是最後一個buffer,也不需要flush,那麼就延時輸出。

        2. c->write->delayed:表示當前串連是否因為某種原因需要延時輸出,比如超過發送速率限制等,只有在其他地方取消delayed標記後才能繼續輸出。在delayed設定的情況下,需要設定c->buffered,並返回NGX_AGAIN。

        3. c->buffered:在buffer沒有輸出完的情況下,標記具體在哪個模組被buffer住了。buffered的取值:

#define NGX_HTTP_LOWLEVEL_BUFFERED         0xf0#define NGX_HTTP_WRITE_BUFFERED            0x10#define NGX_HTTP_GZIP_BUFFERED             0x20#define NGX_HTTP_SSI_BUFFERED              0x01#define NGX_HTTP_SUB_BUFFERED              0x02#define NGX_HTTP_COPY_BUFFERED             0x04
        4. r->limit_rate:對應limit_rate指令,表示request的發送速率限制值。一般地,通過這個值去設定c->write->delayed,當發送速率超過limit時就會設定c->write->delayed,這種情況下就會延遲發送,從而降低request的發送速率。

    /*     * avoid the output if there are no last buf, no flush point,     * there are the incoming bufs and the size of all bufs     * is smaller than "postpone_output" directive     */    /* 在不是最後一個buf,並且不需要flush的情況下,postpone_output指令可以設定當響應內容小於某個值時,延時輸出 */    if (!last && !flush && in && size < (off_t) clcf->postpone_output) {        return NGX_OK;    }    /* 請求被delay,設定buffered標記,直接返回 */    if (c->write->delayed) {        c->buffered |= NGX_HTTP_WRITE_BUFFERED;        return NGX_AGAIN;    }    /* 輸出內容大小為0,並且沒有設定buffered標記,進入清理工作 */    if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) {    /* 如果是最後一個標記,清空buffered標記 */        if (last) {            r->out = NULL;            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;            return NGX_OK;        }        /* 如果需要flush */        if (flush) {        /* 這塊不清楚幹什麼用的 */            do {                r->out = r->out->next;            } while (r->out);            /* 清空buffered標記 */            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;            return NGX_OK;        }        ngx_log_error(NGX_LOG_ALERT, c->log, 0,                      "the http output chain is empty");        ngx_debug_point();        return NGX_ERROR;    }    /* 設定了limit rate */    if (r->limit_rate) {        limit = r->limit_rate * (ngx_time() - r->start_sec + 1)                - (c->sent - clcf->limit_rate_after);        /* 超出發送速率限制 */        if (limit <= 0) {        /* 設定delayed標記,延遲發送請求 */            c->write->delayed = 1;            ngx_add_timer(c->write,                          (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));            /* 設定buffered標記 */            c->buffered |= NGX_HTTP_WRITE_BUFFERED;            return NGX_AGAIN;        }    } else if (clcf->sendfile_max_chunk) {    /* sendfile用的的limit */        limit = clcf->sendfile_max_chunk;    } else {        limit = 0;    }
        這段代碼根據上述標記進行一些處理,具體看注釋。接下來就是發送資料的過程以及後續處理。
    sent = c->sent;    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,                   "http write filter limit %O", limit);    /* send_chain返回的是沒有發完的chain */    chain = c->send_chain(c, r->out, limit);    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,                   "http write filter %p", chain);    if (chain == NGX_CHAIN_ERROR) {        c->error = 1;        return NGX_ERROR;    }    /* 對limit_rate處理,可能會設定delayed標記 */    if (r->limit_rate) {        nsent = c->sent;        if (clcf->limit_rate_after) {            sent -= clcf->limit_rate_after;            if (sent < 0) {                sent = 0;            }            nsent -= clcf->limit_rate_after;            if (nsent < 0) {                nsent = 0;            }        }        delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate + 1);        if (delay > 0) {            c->write->delayed = 1;            ngx_add_timer(c->write, delay);        }    } else if (c->write->ready               && clcf->sendfile_max_chunk               && (size_t) (c->sent - sent)                      >= clcf->sendfile_max_chunk - 2 * ngx_pagesize)    {        c->write->delayed = 1;        ngx_add_timer(c->write, 1);    }    /* 釋放已經發送的chain的記憶體 */    for (cl = r->out; cl && cl != chain; /* void */) {        ln = cl;        cl = cl->next;        ngx_free_chain(r->pool, ln);    }    /* 重新賦值尚未發送的chain */    r->out = chain;    /* 如果chain不為空白,那麼buf沒有發送完,需要設定buffered標記,並返回NGX_AGAIN */    if (chain) {        c->buffered |= NGX_HTTP_WRITE_BUFFERED;        return NGX_AGAIN;    }    /* 如果已經沒有未發送的chain,就清空buffered標記 */    c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;    /* 如果其他filter模組buffer了chain並且postponed為NULL,那麼返回NGX_AGAIN,需要繼續處理buf */    if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {        return NGX_AGAIN;    }    return NGX_OK;
        上面就是ngx_http_write_filter的處理過程,ngx_http_header_filter與之類似,只是它處理的是回應標頭,然後在最後也是通過ngx_http_write_filter輸出。





聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.