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 80;
        server_name db.job360.com;
        ......
    }

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

调试代码:

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

重启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中的最终值。

个人水平有限,上面的测试方法和理解如有不当的地方,还望大家指正,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355