之前看到有人寫的一篇關(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處理岸霹。
什么情況啊将饺?于是查看和文章中環(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):
對nginx處理header時的方法授舟,學(xué)習(xí)后理解如下:
- 先處理請求的request_line救恨,之后才是request_header。
- 這兩者的buffer分配策略相同释树。
- 先根據(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時的使用場景是不同的寂纪,個人理解如下:
- 如果你的請求中的header都很大,那么應(yīng)該使用client_header_buffer_size赌结,這樣能減少一次內(nèi)存分配捞蛋。
- 如果你的請求中只有少量請求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é)果〈拦牛可以得出結(jié)論:nginx在處理header時實際分配的buffer大小奴曙,是解析conf后,default_server中的最終值草讶。
個人水平有限洽糟,上面的測試方法和理解如有不當(dāng)?shù)牡胤剑€望大家指正堕战,謝謝坤溃!