缘起
通过Nginx做反向代理的时候,遇到头部名称带下划线的话,可能会被Nginx丢弃掉,这个问题估计很多人都遇到过。简单来说,就是underscores_in_headers参数控制的问题,默认是off的。Nginx官方也有解释:
If you do not explicitly set underscores_in_headers on;, NGINX will silently drop HTTP headers with underscores (which are perfectly valid according to the HTTP standard). This is done in order to prevent ambiguities when mapping headers to CGI variables as both dashes and underscores are mapped to underscores during that process.
遇到非下划线的情况
但是昨天遇到另外一个现象,先介绍一下背景。
昨天有同学说,用一个服务调用另外一个服务,通过头部传递一些信息(由一个公共组件进行传递)。发现在本地是可以的,在k8s环境也是可以的,但是用本地服务调用k8s服务就不行,会取不到头部。回忆一下定位过程:
POST http://ip:port/test HTTP/1.1
Content-Length: 2
trackId: 123456
extend.x: 1
{}
-
分析了一下网络差异,发现本地调用服务,中间多了一层Nginx代理(k8s NGINX ingress controller)。 -
这里被同学误导了一把,一直以为是另外一个叫trackId的普通头部取不到,就会尝试绕过Nginx代理,发现也是类似报错,就更加奇怪了。后来去翻组件的代码才发现代码是走进去的,这个头是可以取到的。 -
后来同学提醒说另外一个头extend.x也是取不到的,终于回过来想,是不是这种头被过滤了。 -
网上搜了一下有个ignore_invalid_headers的参数配置,直接在环境上修改验证一下,果然就可以了。
问题回头看经常是简单的,但是过程通常是不顺利的。回头想,一开始就想看看收到的请求是什么内容,想做个对比,当时手上没什么工具,回去的时候想了下,测试环境可以临时修改k8s service的endpoints,这样把请求转到一个你可以看的地方,这样就方便多了。另外一方面,之前没人用这个组件这么传东西,预想它是正常的,没想到它的实现和Nginx的匹配不上。
请求头标准是怎样的?
那实际上,请求头能不能用下划线或者其他字符,例如点。还是先找找RFC是怎么说的。
具体见rfc2616#section-4.2,提到相关要求在rfc822#section-3.1.2进行说明。
The field-name must be composed of printable ASCII characters (i.e., characters that have values between 33. and 126., decimal, except colon)
例如上面的点,在ASCII中是46(2e),至少是满足RFC规范的。
Nginx是怎么处理头部的?
这说明Nginx还是有不少特殊处理情况的。先看看两个配置参数的描述。
-
ignore_invalid_headers
Controls whether header fields with invalid names should be ignored. Valid names are composed of English letters, digits, hyphens, and possibly underscores (as controlled by the underscores_in_headers directive).
-
underscores_in_headers
Enables or disables the use of underscores in client request header fields. When the use of underscores is disabled, request header fields whose names contain underscores are marked as invalid and become subject to the ignore_invalid_headers directive.
还是文档没真正细读,Nginx文档对ignore_invalid_headers的规定就是英文字母,数字,连字号和下划线(可以通过underscores_in_headers控制)。
翻翻代码看看Nginx是怎么处理的吧?
在ngx_http_parse.c#L837定义了一些合法的字符,可以看到上面的2e是被当成非法字符来的(例如第一行是00-1f)。
static u_char lowcase[] =
""
"-" "0123456789"
"abcdefghijklmnopqrstuvwxyz"
"abcdefghijklmnopqrstuvwxyz"
""
""
""
"";
在ngx_http_parse.c#L911有具体的解析逻辑,会用到上面定义的lowcase来判断是否有效字符,但是会对下划线进行特殊处理,最终带非法字符的头部会被打上invalid_header的标记。
case sw_name:
c = lowcase[ch];
if (c) {
hash = ngx_hash(hash, c);
r->lowcase_header[i++] = c;
i &= (NGX_HTTP_LC_HEADER_LEN - 1);
break;
}
if (ch == '_') {
if (allow_underscores) {
hash = ngx_hash(hash, ch);
r->lowcase_header[i++] = ch;
i &= (NGX_HTTP_LC_HEADER_LEN - 1);
} else {
r->invalid_header = 1;
}
break;
}
在ngx_http_request.c#L1416,会对解析得到的头部进行处理,如果带invalid_header且打开ignore_invalid_headers配置参数,就会输出日志并忽略。
rc = ngx_http_parse_header_line(r, r->header_in,
cscf->underscores_in_headers);
if (rc == NGX_OK) {
r->request_length += r->header_in->pos - r->header_name_start;
if (r->invalid_header && cscf->ignore_invalid_headers) {
/* there was error while a header line parsing */
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client sent invalid header line: "%*s"",
r->header_end - r->header_name_start,
r->header_name_start);
continue;
}
/* a header line has been parsed successfully */
h = ngx_list_push(&r->headers_in.headers);
注意到这里,其实是有error日志的,正常会输出到error.log去。当时处理问题的时候,我也去检查了error.log,但是并没有输出,后来看到这个代码的时候,想起这里有日志级别控制,回头去看配置,如下所示。
error_log /var/log/nginx/error.log notice;
而error.log的日志级别是debug, info, notice, warn, error, crit, alert, emerg(从左到右输出变少),刚好上面是info级别的,尝试调整一下日志级别就有警告信息输出了。
client sent invalid header line: “extend.x: 1” while reading client request headers, client: x.x.x.x, server: _, request: “POST /test HTTP/1.1”
总结
还是那句话,问题并不复杂,但是受限于环境,还有对规范、文档、具体实现的理解偏差,会导致问题并没有那么简单。
-
对于使用的工具,还是要多多阅读官方文档,多多理解实现的特异性。 -
对于问题定位,多掌握一些工具和思路,还有总结形成体系化,便于快速找到方向。
原文始发于微信公众号(程序员的胡思乱想):除了下划线,Nginx还可能丢弃哪些请求头?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/22621.html