bigywater.tech
网络协议 · 74

HTTP/1.1 报文:请求行、头部、空行与消息体

HTTP/1.1 是文本协议:请求行、头部、一个空行、消息体。看起来简单,但细节里藏着很多规则:怎么划分头部结束、名字大小写、必须有的 Host、什么时候关连接。这篇手工拼请求发给 example.com,逐项看服务器的真实反应,包括它比规范更宽容的地方。

56 / 928 字节
最小 GET 请求与 example.com 的响应大小
0x241
响应消息体第一行的十六进制块长度(= 577 字节),因为用的是 chunked
130000~200000
单个头部值通过与被拒绝(400)的字节数区间

报文结构

实测 · 本机执行输出
  发出的请求(56 字节):b'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n'
  = 请求行 "GET / HTTP/1.1"(方法 + 空格 + 目标 + 空格 + 版本)+ CRLF;头部 "Host: example.com" CRLF "Connection: close" CRLF;空行 CRLF 表示头部结束
  收到 928 字节,用时 426 ms,服务器随后关闭了连接;头部 335 字节 + CRLFCRLF 4 字节 + 消息体 589 字节
     0  HTTP/1.1 200 OK
     1  Date: Thu, 15 Oct 2026 07:39:32 GMT
     2  Content-Type: text/html; charset=utf-8
     3  Transfer-Encoding: chunked
     4  Connection: close
     5  Server: cloudflare
     6  Last-Modified: Tue, 13 Oct 2026 20:08:28 GMT
     7  Allow: GET, HEAD
     8  Accept-Ranges: bytes
     9  Age: 6
    10  cf-cache-status: HIT
    11  CF-RAY: a4ad2ba5db00b5bc-HKG
    12  alt-svc: h3=":443"; ma=86400
  状态行:HTTP/1.1 200 OK → 版本 HTTP/1.1,状态码 200,原因短语 "OK"
  头部共 12 个;Content-Length = None,实际收到的消息体 589 字节;Content-Type = ['text/html; charset=utf-8'];Transfer-Encoding = ['chunked']
  消息体开头:b'241\r\n<!doctype html><html lang=en><head><meta charset=utf-8>'

请求行或状态行、若干头部、空行 CRLFCRLF、消息体。响应用 chunked 划界,首行是十六进制块长度。

宽容与拒绝

实测 · 本机执行输出
  正常                                → HTTP/1.1 200 OK
  头部名字全大写 / 乱大小写                    → HTTP/1.1 200 OK
  冒号后无空格、值后有多余空格                    → HTTP/1.1 200 OK
  缺少 Host 头(HTTP/1.1 必须有)           → HTTP/1.1 400 Bad Request
  行尾只用 LF(没有 CR)                    → HTTP/1.1 200 OK
  未知方法 FOO                          → HTTP/1.1 405 Method Not Allowed
  版本号写成 HTTP/9.9                    → HTTP/1.1 505 HTTP Version Not Supported
  请求目标是绝对 URI                       → HTTP/1.1 200 OK
  同一个头出现两次(Host 写两遍)                → HTTP/1.1 200 OK

大小写乱、LF 行尾、绝对 URI 都被接受;缺 Host → 400,未知方法 → 405,HTTP/9.9 → 505。

持久连接默认值

实测 · 本机执行输出
  请求 HTTP/1.0:响应状态行 HTTP/1.1 200 OK;响应里的 Connection = ['close'];服务器在 498 ms 时关闭了连接
  请求 HTTP/1.1:响应状态行 HTTP/1.1 200 OK;响应里的 Connection = ['keep-alive'];5 秒内服务器没有关闭连接(保持着,等下一个请求)

1.0 默认关闭,1.1 默认保持;响应头的 Connection 会明确说明。

头部大小

实测 · 本机执行输出
  X-Pad 长度  1000:HTTP/1.1 200 OK
  X-Pad 长度 16000:HTTP/1.1 200 OK
  X-Pad 长度 64000:HTTP/1.1 200 OK
  X-Pad 长度 100000:HTTP/1.1 200 OK
  X-Pad 长度 130000:HTTP/1.1 200 OK
  X-Pad 长度 200000:HTTP/1.1 400 Bad Request
  X-Pad 长度 400000:HTTP/1.1 400 Bad Request

这个服务器单个头部值的上限在 130000~200000 字节之间,超过就是 400。

分步演示

HTTP/1.1 报文
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。全部是对 example.com:80(Cloudflare)的明文 HTTP/1.1 请求;每种畸形请求只发一次;其它服务器的宽容度不同,这里的结论只对这一个服务器成立;响应里的 Age、CF-RAY 等每次不同
    • 没有验证:请求走私(同时带 Content-Length 和 Transfer-Encoding);头部折行(obs-fold);请求体与 Expect: 100-continue;HTTP/1.1 流水线(下一篇);其它服务器(nginx、Apache、Python http.server)对同样畸形请求的反应;头部上限的精确值
    京ICP备2026067502号-1京公网安备11010502063487号