HTTP/2 强制转化小写

HTTP/2 把所有头部字段名强制改成小写,并不是为了“更规范”,而是为了配合新引入的“头部压缩”机制(HPACK)。

HPACK 以“静态表+动态表”做字典压缩,字段名作为 key 必须唯一且字节级一致。

如果允许 Content-Type / content-type / CONTENT-TYPE 三种写法,字典就要存三份,压缩率骤降,实现也复杂。

所以 RFC 7540 直接规定:编码前必须把字段名统一转成小写。这样

– 字典只需一份条目,压缩比最大化;

– 比较时直接按字节匹配,省掉大小写归一化步骤,性能更好;

– 仍然兼容语义(HTTP 语义上字段名本来就不区分大小写)。

维度HTTP/1.1HTTP/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 把大写字母强制转成小写的现场:

  1. 65-90 就是 ASCII 的 'A'-'Z'

  2. this.chr - -32 等价于 this.chr + 32
    把大写字母加上 32 后得到对应小写字母('A'+32='a')

  3. 直接写回 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;
 
    }




 

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐