Nginx的client_header_buffer_size和large_client_header_buffers學(xué)習(xí)

之前看到有人寫的一篇關(guān)于nginx配置中l(wèi)arge_client_header_buffers的問題排查的文章,其中提到:

large_client_header_buffers 雖然也可以在server{}內(nèi)生效,但是只有 低于 nginx主配置中的值才有意義。

對這個結(jié)論,我心存疑慮,總覺得這種設(shè)計很奇怪,于是自己做了個測試,希望能了解的更深入一些。

測試方法

nginx主配置中加入配置項:(在主配置中將header大小控制在1k)

http {
    include  mime.types;
    default_type  application/octet-stream;
    large_client_header_buffers  4 1k;
    ......
}

刪除所有干擾vhost,僅留下一個:

server {
    listen 80;
    server_name  www.job360.com;
    large_client_header_buffers  4 1m;
    ......
}

構(gòu)造請求的shell:(構(gòu)造header超過1k的請求)

#!/bin/bash

url="http://www.job360.com/test.html?debug=1"

for i in {0..1000}
do
    var="v$i"
    url="${url}&$var=$i"
done

curl $url -x 127.0.0.1:80 -v

第一次測試結(jié)果

測試得到的結(jié)果和之前看到的文章的結(jié)果不同,該長url請求成功被nginx處理。

什么情況?。坑谑遣榭春臀恼轮协h(huán)境上的不同,發(fā)現(xiàn)很重要的一點:我只有這一個vhost。

于是添加了另外一個vhost,添加vhost配置如下:(沒有設(shè)置 large_client_header_buffers)

server {
    listen 80;
    server_name db.job360.com;
    ......}

第二次測試結(jié)果

測試發(fā)現(xiàn),nginx依舊可以處理該長url請求。

再次思考不同點,想到:這些vhost是被主配置中include進來的,是否會和讀取順序有關(guān)呢?

于是再次調(diào)整配置,將兩個vhost放到了一個conf文件中,配置如下:

server {
    listen 80;
    server_name db.job360.com;
    ......
}

server {
    listen 80;
    server_name  www.job360.com;
    large_client_header_buffers  4 1m;
    ......
}

第三次測試結(jié)果

得到和文章中相同的結(jié)果,nginx返回414 Request-URI Too Large。

帶著好奇心,我顛倒了下兩個vhost的順序,如下:

server {
    listen 80;
    server_name  www.job360.com;
    large_client_header_buffers  4 1m;
    ......
}

server {
    listen 80;
    server_name db.job360.com;
    ......
}

第四次測試結(jié)果

nginx成功處理該長url請求。

初步結(jié)論

通過上面的現(xiàn)象,我得到一個初步結(jié)論:在第一個vhost中配置的large_client_header_buffers參數(shù)會起作用。

好奇怪的現(xiàn)象啊,我對自己得出的結(jié)論也是心存疑惑,于是決定從手冊中好好讀下控制header_buffer相關(guān)的指令。

從手冊上理解nginx有關(guān)header_buffer配置指令

從手冊上找到有兩個指令和header_buffer有關(guān):

  1. client_header_buffer_size
  2. large_client_header_buffers

對nginx處理header時的方法,學(xué)習(xí)后理解如下:

  1. 先處理請求的request_line,之后才是request_header。
  2. 這兩者的buffer分配策略相同。
  3. 先根據(jù)client_header_buffer_size配置的值分配一個buffer,如果分配的buffer無法容納 request_line/request_header,那么就會再次根據(jù)large_client_header_buffers配置的參數(shù)分配large_buffer,如果large_buffer還是無法容納,那么就會返回414(處理request_line)/400(處理request_header)錯誤。

根據(jù)對手冊的理解,我理解這兩個指令在配置header_buffer時的使用場景是不同的,個人理解如下:

  1. 如果你的請求中的header都很大,那么應(yīng)該使用client_header_buffer_size,這樣能減少一次內(nèi)存分配。
  2. 如果你的請求中只有少量請求header很大,那么應(yīng)該使用large_client_header_buffers,因為這樣就僅需在處理大header時才會分配更多的空間,從而減少無謂的內(nèi)存空間浪費。

為了印證自己對兩個配置指令的理解,我把large_client_header_buffer換成client_header_buffer_size,重新跑上面的多種測試,得到了和之前各種場景相同的結(jié)論。

手冊上也只是說明了這兩個指令的使用場景,沒有說更多的東西了,之前的疑惑還是沒有得到解答,那么只有最后一招了,也是絕招:從源碼中尋找答案!

源碼學(xué)習(xí)

這里從client_header_buffer_size指令入手,先查看這個指令的定義部分:

{ ngx_string("client_header_buffer_size"),
  NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1,              //可以定義在http{}或server{}中,需要攜帶一個參數(shù)
  ngx_conf_set_size_slot,                                           //參數(shù)意義為size,使用nginx預(yù)定義的解析size參數(shù)方法解析
  NGX_HTTP_SRV_CONF_OFFSET,                                         //將參數(shù)值放到srv級別的conf中
  offsetof(ngx_http_core_srv_conf_t, client_header_buffer_size),    //解析后放到ngx_http_core_srv_conf_t結(jié)構(gòu)體的client_header_buffer_size中
  NULL },

src/http/ngx_http_core_module.c

由定義看到,我們在server{}中解析到的值會和http{}中的值做一次merge,作為該server{}下的最終值。查看merge相關(guān)的邏輯:

ngx_conf_merge_size_value(conf->client_header_buffer_size,        //conf代表server{},prev代表http{}
                          prev->client_header_buffer_size, 1024); 

src/http/ngx_http_core_module.c
#define ngx_conf_merge_size_value(conf, prev, default)                       \
    if (conf == NGX_CONF_UNSET_SIZE) {                                       \
        conf = (prev == NGX_CONF_UNSET_SIZE) ? default : prev;               \
    }

src/core/ngx_conf_file.h

從這段邏輯中得到結(jié)論:如果我們在server{}中配置了client_header_buffer_size,那么針對這個server{}塊的最終值應(yīng)該就是我們配置的值。

為了印證我的結(jié)論,我重新寫了vhost配置,并在代碼中加入調(diào)試信息,把最終結(jié)果打印出來:

http {
    include  mime.types;
    default_type  application/octet-stream;
    large_client_header_buffers  4 1k;
    ......

    server {
        listen 80;
        server_name db.job360.com;
        ......
    }

    server {
        listen 80;
        server_name  www.job360.com;
        large_client_header_buffers  4 1m;
        ......
    }
}

調(diào)試代碼:

    printf("buffer before merge:\nchild: %lu\nparent: %lu\n\n", conf->client_header_buffer_size, prev->client_header_buffer_size);
......
    ngx_conf_merge_size_value(conf->client_header_buffer_size,
                              prev->client_header_buffer_size, 1024);
......
    printf("buffer after merge:\nchild: %lu\nparent: %lu\n\n", conf->client_header_buffer_size, prev->client_header_buffer_size);

src/http/ngx_http_core_module.c

重新編譯nginx,測試每個server{}中client_header_buffer_size的最終值為:

buffer before merge:
child: 18446744073709551615    //由于第一個server{}中沒有配置,所以這個是-1(NGX_CONF_UNSET_SIZE)的unsigned long int表示
parent: 1024    //http{}中配置為1k

buffer after merge:
child: 1024
parent: 1024

buffer before merge:
child: 1048576    //第二個server{}中配置為1m
parent: 1024

buffer after merge:
child: 1048576
parent: 1024

從值的最終結(jié)果看,的確是之前設(shè)置的1m,但是請求時卻返回了414。

由于將兩個server{}的位置顛倒后可以正常處理請求,所以在顛倒的情況下又測試了下最終值,輸出如下:

buffer before merge:
child: 1048576
parent: 1024

buffer after merge:
child: 1048576
parent: 1024

buffer before merge:
child: 18446744073709551615
parent: 1024

buffer after merge:
child: 1024
parent: 1024

最終值的輸出還是1m,但是這次就可以正常處理請求了。

看來nginx在實際處理請求的過程中,一定還有之前不知道的一套邏輯,用來判斷client_header_buffer_size的最終值。

nginx處理請求時的相關(guān)代碼如下:

    ngx_http_core_srv_conf_t   *cscf;
......
    /* the default server configuration for the address:port */
    cscf = addr_conf->default_server;
......
    if (c->buffer == NULL) {
        c->buffer = ngx_create_temp_buf(c->pool,
                                        cscf->client_header_buffer_size);

src/http/ngx_http_request.c

這里真相大白:

原來client_header_buffer_size的最終值,是nginx在解析conf后,default_server中經(jīng)過merge的最終值。

而default_server在nginx中的定義為:在listen指令中定義:

The default_server parameter, if present, will cause the server to become the default server for the specified address:port pair. If none of the directives have the default_server parameter then the first server with the address:port pair will be the default server for this pair.

為了驗證這一點,我修改vhost配置為:

server {
    listen 80;
    server_name db.job360.com;
    ......
}

server {
    listen 80 default;
    server_name  www.job360.com;

    large_client_header_buffers  1m;
    ......
}

重啟nginx觀察merge結(jié)果:

buffer before merge:
child: 18446744073709551615
parent: 1024

buffer after merge:
child: 1024
parent: 1024

buffer before merge:
child: 1048576
parent: 1024

buffer after merge:
child: 1048576
parent: 1024

merge結(jié)果沒有不同。測試請求,這次nginx成功處理該請求,和預(yù)期的效果一致。

結(jié)束語

筆者又測試了large_client_header_buffers,得到和client_header_buffer_size同樣的結(jié)果??梢缘贸鼋Y(jié)論:nginx在處理header時實際分配的buffer大小,是解析conf后,default_server中的最終值。

個人水平有限,上面的測試方法和理解如有不當?shù)牡胤剑€望大家指正,謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容