Nginx的client_header_buffer_size和large_client_header_buffers學習


之前看到有人寫的一篇關於nginx配置中large_client_header_buffers的問題排查的文章,其中提到:

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

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

測試方法

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;
    ......
}

構造請求的shell:(構造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

第一次測試結果

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

什么情況啊?於是查看和文章中環境上的不同,發現很重要的一點:我只有這一個vhost。

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

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

第二次測試結果

測試發現,nginx依舊可以處理該長url請求。

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

於是再次調整配置,將兩個vhost放到了一個conf文件中,配置如下:

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

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

第三次測試結果

得到和文章中相同的結果,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;
......
}

第四次測試結果

nginx成功處理該長url請求。

初步結論

通過上面的現象,我得到一個初步結論:在第一個vhost中配置的large_client_header_buffers參數會起作用。

好奇怪的現象啊,我對自己得出的結論也是心存疑惑,於是決定從手冊中好好讀下控制header_buffer相關的指令。

從手冊上理解nginx有關header_buffer配置指令

從手冊上找到有兩個指令和header_buffer有關:

  1. client_header_buffer_size
  2. large_client_header_buffers

對nginx處理header時的方法,學習后理解如下:

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

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

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

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

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

源碼學習

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

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

src/http/ngx_http_core_module.c

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

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

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

為了印證我的結論,我重新寫了vhost配置,並在代碼中加入調試信息,把最終結果打印出來:

http {
    include  mime.types;
    default_type  application/octet-stream;
    large_client_header_buffers  4 1k;
    ......
server {
    listen <span class="hljs-number">80</span>;
    server_name db.job360.com;
    ......
}

server {
    listen <span class="hljs-number">80</span>;
    server_name  www.job360.com;
    large_client_header_buffers  <span class="hljs-number">4</span> <span class="hljs-number">1</span>m;
    ......
}

}

調試代碼:

    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

從值的最終結果看,的確是之前設置的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處理請求時的相關代碼如下:

    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中經過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  <span class="hljs-number">1</span>m;
......

}

重啟nginx觀察merge結果:

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結果沒有不同。測試請求,這次nginx成功處理該請求,和預期的效果一致。

結束語

筆者又測試了large_client_header_buffers,得到和client_header_buffer_size同樣的結果。可以得出結論:nginx在處理header時實際分配的buffer大小,是解析conf后,default_server中的最終值。

個人水平有限,上面的測試方法和理解如有不當的地方,還望大家指正,謝謝!

      </div>
    </div>
posted @ 2019-01-08 10:44  星朝  閱讀( 901)  評論( 0編輯  收藏


免責聲明!

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



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