http请求 header 头小写转化问题
HTTP/2 强制转化小写
HTTP/2 把所有头部字段名强制改成小写,并不是为了“更规范”,而是为了配合新引入的“头部压缩”机制(HPACK)。
HPACK 以“静态表+动态表”做字典压缩,字段名作为 key 必须唯一且字节级一致。
如果允许 Content-Type / content-type / CONTENT-TYPE 三种写法,字典就要存三份,压缩率骤降,实现也复杂。
所以 RFC 7540 直接规定:编码前必须把字段名统一转成小写。这样
– 字典只需一份条目,压缩比最大化;
– 比较时直接按字节匹配,省掉大小写归一化步骤,性能更好;
– 仍然兼容语义(HTTP 语义上字段名本来就不区分大小写)。
| 维度 | HTTP/1.1 | HTTP/2 |
| 连接模型 | 每条 TCP 连接同一时刻只能处理一个请求(若无 pipeline/pipelining 几乎不用)。 | 一条 TCP 连接上并行 多路复用(stream),默认上限 128 并发流(http2_max_concurrent_streams)。 |
| 头部 | 文本格式,可混用大小写;每次请求完整传输。 | 二进制分帧;强制小写;HPACK 压缩(静态+动态表),典型 80 % 以上头部体积节省。 |
| 请求体 | 分段/压缩由 Transfer-Encoding 控制。 | 仍用相同指令(client_max_body_size 等),但底层改为 DATA 帧;不允许同时出现 Transfer-Encoding: chunked 与 Content-Length,Nginx 会自动纠错。 |
tomcat 强制转化小写
在 Tomcat(8.5+ 以及 9.0 系列)中,"HTTP 头名称被强制转成小写"并不是 Spring 或应用框架的行为,而是 Tomcat 在解析请求字节流时就把大写字母当场减 32 变成小写——这发生在最底层的 Coyote 解析器里,因此上层 HttpServletRequest.getHeader("Message-Id") 无论传什么大小写都能命中,但 getHeaderNames() 返回的永远是小写形式。
if (this.chr >= 65 && this.chr <= 90) {
this.byteBuffer.put(pos, (byte)(this.chr - -32));
}
Tomcat 把大写字母强制转成小写的现场:
-
65-90就是 ASCII 的'A'-'Z' -
this.chr - -32等价于this.chr + 32
把大写字母加上 32 后得到对应小写字母('A'+32='a') -
直接写回
byteBuffer—— 后续再也不会用到原始大写形式
源码
org.apache.coyote.http11.Http11InputBuffer#parseHeader()
(tomcat-embed-core-*.jar 里同包同名类)
private HeaderParseStatus parseHeader() throws IOException {
while(true) {
if (this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_START) {
if (this.byteBuffer.position() >= this.byteBuffer.limit() && !this.fill(false)) {
return Http11InputBuffer.HeaderParseStatus.NEED_MORE_DATA;
}
this.prevChr = this.chr;
this.chr = this.byteBuffer.get();
if (this.chr == 13 && this.prevChr != 13) {
continue;
}
if (this.chr == 10) {
return Http11InputBuffer.HeaderParseStatus.DONE;
}
if (this.prevChr == 13) {
this.byteBuffer.position(this.byteBuffer.position() - 2);
} else {
this.byteBuffer.position(this.byteBuffer.position() - 1);
}
}
if (this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_START) {
this.headerData.start = this.byteBuffer.position();
this.headerData.lineStart = this.headerData.start;
this.headerParsePos = Http11InputBuffer.HeaderParsePosition.HEADER_NAME;
}
while(this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_NAME) {
if (this.byteBuffer.position() >= this.byteBuffer.limit() && !this.fill(false)) {
return Http11InputBuffer.HeaderParseStatus.NEED_MORE_DATA;
}
int pos = this.byteBuffer.position();
this.chr = this.byteBuffer.get();
if (this.chr == 58) {
if (this.headerData.start == pos) {
return this.skipLine(false);
}
this.headerParsePos = Http11InputBuffer.HeaderParsePosition.HEADER_VALUE_START;
this.headerData.headerValue = this.headers.addValue(this.byteBuffer.array(), this.headerData.start, pos - this.headerData.start);
pos = this.byteBuffer.position();
this.headerData.start = pos;
this.headerData.realPos = pos;
this.headerData.lastSignificantChar = pos;
break;
}
if (!HttpParser.isToken(this.chr)) {
this.headerData.lastSignificantChar = pos;
this.byteBuffer.position(this.byteBuffer.position() - 1);
return this.skipLine(false);
}
if (this.chr >= 65 && this.chr <= 90) {
this.byteBuffer.put(pos, (byte)(this.chr - -32));
}
}
if (this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_SKIPLINE) {
return this.skipLine(false);
}
while(true) {
while(this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_VALUE_START || this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_VALUE || this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_MULTI_LINE) {
if (this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_VALUE_START) {
while(true) {
if (this.byteBuffer.position() >= this.byteBuffer.limit() && !this.fill(false)) {
return Http11InputBuffer.HeaderParseStatus.NEED_MORE_DATA;
}
this.chr = this.byteBuffer.get();
if (this.chr != 32 && this.chr != 9) {
this.headerParsePos = Http11InputBuffer.HeaderParsePosition.HEADER_VALUE;
this.byteBuffer.position(this.byteBuffer.position() - 1);
this.chr = 0;
break;
}
}
}
if (this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_VALUE) {
boolean eol = false;
while(true) {
if (eol) {
this.headerData.realPos = this.headerData.lastSignificantChar;
this.headerParsePos = Http11InputBuffer.HeaderParsePosition.HEADER_MULTI_LINE;
break;
}
if (this.byteBuffer.position() >= this.byteBuffer.limit() && !this.fill(false)) {
return Http11InputBuffer.HeaderParseStatus.NEED_MORE_DATA;
}
this.prevChr = this.chr;
this.chr = this.byteBuffer.get();
if (this.chr != 13 || this.prevChr == 13) {
if (this.chr == 10) {
eol = true;
} else {
if (this.prevChr == 13) {
return this.skipLine(true);
}
if (this.chr != 9 && HttpParser.isControl(this.chr)) {
return this.skipLine(true);
}
if (this.chr != 32 && this.chr != 9) {
this.byteBuffer.put(this.headerData.realPos, this.chr);
++this.headerData.realPos;
this.headerData.lastSignificantChar = this.headerData.realPos;
} else {
this.byteBuffer.put(this.headerData.realPos, this.chr);
++this.headerData.realPos;
}
}
}
}
}
if (this.byteBuffer.position() >= this.byteBuffer.limit() && !this.fill(false)) {
return Http11InputBuffer.HeaderParseStatus.NEED_MORE_DATA;
}
byte peek = this.byteBuffer.get(this.byteBuffer.position());
if (this.headerParsePos == Http11InputBuffer.HeaderParsePosition.HEADER_MULTI_LINE) {
if (peek != 32 && peek != 9) {
this.headerParsePos = Http11InputBuffer.HeaderParsePosition.HEADER_START;
break;
}
this.byteBuffer.put(this.headerData.realPos, peek);
++this.headerData.realPos;
this.headerParsePos = Http11InputBuffer.HeaderParsePosition.HEADER_VALUE_START;
}
}
this.headerData.headerValue.setBytes(this.byteBuffer.array(), this.headerData.start, this.headerData.lastSignificantChar - this.headerData.start);
this.headerData.recycle();
return Http11InputBuffer.HeaderParseStatus.HAVE_MORE_HEADERS;
}
}
}
客户端与服务端对应的表现
myService服务使用的nginx集群,默认开启了HTTP/2协议。
1、如果客户端用 HTTP/2 进行请求myService服务,nginx会将请求头的key 自动转化为小写字母, 转发给myService;
2、如果客户端用 HTTP1.1 进行请求myService服务,nginx 不会动请求头的key, 原封转发给 myService服务,myService服务对应的tomcat容器,tomcat 也会自动转化为小写字母。
header头正确的获取逻辑
HttpServletRequest getHeader方法
servlet容器会自动处理,key大小写不敏感
String serial = request.getHeader("Wechatpay-Serial");
自定义获取header头逻辑
// 获取请求头中的所有内容
public static Map<String, String> getAllHeaders(HttpServletRequest request) {
Map<String, String> headers = new HashMap<>();
// 获取所有请求头的名称
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
headers.put(headerName, headerValue);
}
return headers;
}
// 从请求头里取值, headerKey 是大小写不敏感串,遵循 RFC 7540规范
public static String getHeaderValue(Map<String, String> headerMap, String headerKey) {
if (headerMap == null || headerMap.size() == 0) {
return null;
}
String headerValue = headerMap.get(headerKey);
if (Strings.isNullOrEmpty(headerValue)) {
for (Map.Entry<String, String> entry : headerMap.entrySet()) {
if (entry.getKey().equalsIgnoreCase(headerKey)) {
headerValue = entry.getValue();
break;
}
}
return headerValue;
}
更多推荐
所有评论(0)