详细说明http协议特别是conten-length和chunk编码,并且用linux的命令行演示整个过程
下面把 HTTP 请求体的两种主流传输方式——Content-Length
与 Transfer-Encoding: chunked
——讲清楚;再给一套你在 Linux 命令行就能复现的端到端演示(含“最小服务器 + 客户端原始包 + curl 流式上传”)。照抄命令即可跑通。
一、HTTP 基本结构(快速复盘)
<起始行> // 例:POST /upload HTTP/1.1
<首部> // 若干行 “Key: Value”// 重要:每行以 \r\n 结尾
\r\n // 空行:首部结束
<消息体/请求体> // 可为空
HTTP/1.1 如何知道“请求体到哪儿结束”? 有三种合法方式(必须至少有一种):
Content-Length:首部声明字节数,服务器据此读固定长度。
Transfer-Encoding: chunked:分块传输。体被切成若干 chunk,每块前先写十六进制长度行,结尾用0 块收尾。
连接关闭(HTTP/1.0 或显式
Connection: close
且无 1/2),靠对端断开判定结束(现代服务端很少用于请求体)。
注意:在 HTTP/1.1 中,
Content-Length
和Transfer-Encoding: chunked
互斥,不能同时出现。
二、Content-Length:一次说清
用在:发送方提前知道整个体的大小(例如完整 JPEG/JSON)。
规则:首部里写
Content-Length: N
,体就必须恰好 N 字节;多/少 1 个字节都是协议错误。优势:简单、高效、好缓存。
常见坑:
体里不能额外多出
\r\n
(很多串口/封装函数会自动追加,导致“宣称 512 实发 514/539”)。N
与实际长度不符 → 服务器 4xx/5xx,或客户端阻塞/截断。
三、Chunked:流式说清
用在:发送方一边产生数据,一边发送(边拍照边上传、未知总长)。
格式(每块):
<chunk-size-HEX>\r\n <chunk-data-RAW>\r\n ... 0\r\n \r\n
例:发送 5 字节
Hello
:5\r\n Hello\r\n 0\r\n \r\n
特点:不需要
Content-Length
;可以追加trailer 首部(需要Trailer:
声明)。常见坑:
每块长度是十六进制(
1024
字节应写400\r\n
),且每块数据后还有\r\n
。最后必须发 0 块(
0\r\n\r\n
)。缺它,服务器会一直等,最终 408/超时。一些代理/网关对 HTTP/2 会改用帧分段(HTTP/2 没有 chunked 这个概念),但请求侧写法不变,由库/代理负责转换。
四、Linux 命令行完整演示
A. 准备一个最小“镜像”服务器(仅打印你发来的原始包)
方式 1(推荐):socat
打印所有收发
# 安装:Ubuntu/Debian -> sudo apt-get install socat
socat -v -v tcp-l:8080,reuseaddr,fork - # 监听 8080,打印客户端发来的所有字节
方式 2(简易):nc
监听(不打印十六进制方向箭头,够用)
# BusyBox/常见发行版自带
nc -lv 8080
这只是“原始包观察器”。收到请求后你还需要回一段固定响应,避免客户端卡住。我们下面的示例会把响应一并通过管道发出。
B. 用 Content-Length 方式发送一个 POST(纯手工包)
1) 构造一个“固定响应”的服务器(能回 200 OK)
开一个终端执行:
# 简单回“HTTP 200 OK”的服务器:收到请求后回固定响应(含 Content-Length)
while true; do \{ printf 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK'; } \| socat -v -v - tcp-l:8080,reuseaddr; \
done
(你会在终端看到 socat 打印的客户端→服务器与服务器→客户端的原始字节)
2) 客户端:计算体长并发送
在另一个终端:
body='hello=world&n=42' # 体内容(17字节)
len=$(printf "%s" "$body" | wc -c) # 计算字节数{ printf "POST /upload HTTP/1.1\r\n"printf "Host: localhost:8080\r\n"printf "Content-Type: application/x-www-form-urlencoded\r\n"printf "Content-Length: %d\r\n" "$len"printf "Connection: close\r\n"printf "\r\n"printf "%s" "$body" # 注意:这里只能发 RAW,不能多发 \r\n
} | nc 127.0.0.1 8080
观察点:
服务器端窗口会打印你发来的原始请求;
Content-Length: 17
与体字节数严格一致。客户端会收到
OK
。
C. 用 chunked 方式发送(纯手工包)
关闭前面的 socat 服务器,换一个“回包服务器”(一样即可):
while true; do \{ printf 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\nConnection: close\r\n\r\nOK'; } \| socat -v -v - tcp-l:8080,reuseaddr; \
done
客户端:按块发送(两块 + 终止 0 块)
chunk1='Hello '
chunk2='World!'
# 计算长度 -> 转十六进制
h1=$(printf "%s" "$chunk1" | wc -c | awk '{printf "%X",$1}')
h2=$(printf "%s" "$chunk2" | wc -c | awk '{printf "%X",$1}'){ printf "POST /stream HTTP/1.1\r\n"printf "Host: localhost:8080\r\n"printf "Transfer-Encoding: chunked\r\n"printf "Content-Type: text/plain\r\n"printf "Connection: close\r\n"printf "\r\n"printf "%s\r\n" "$h1" # 块1长度(十六进制)+ CRLFprintf "%s\r\n" "$chunk1" # 块1数据 + CRLFprintf "%s\r\n" "$h2" # 块2长度(十六进制)+ CRLFprintf "%s\r\n" "$chunk2" # 块2数据 + CRLFprintf "0\r\n\r\n" # 终止块
} | nc 127.0.0.1 8080
观察点:
服务器端窗口能看到“
Transfer-Encoding: chunked
”首部,以及你发送的块格式(长度行是 ASCII 十六进制数字)。客户端仍收到
OK
。如果你漏了最后的0\r\n\r\n
,服务器会卡住等你结束。
D. 用 curl 演示“自动 chunked”(从标准输入流式上传)
当 curl
无法提前获知体长度(例如从管道/标准输入 @-
),在 HTTP/1.1 下会自动选择 chunked。
1) 起一个能回显请求信息的“迷你服务器”
下面是一行 Python(不依赖第三方库),把请求信息打印到终端并回 200:
python3 - <<'PY'
import sys, socket
s=socket.socket(); s.bind(('0.0.0.0',8080)); s.listen(1)
print('listening 8080...')
while True:c,_=s.accept(); data=b''# 读到一个空行,拿到首部(简单做法,演示用)while b'\r\n\r\n' not in data:data+=c.recv(4096)sys.stdout.buffer.write(b'\n--- REQUEST ---\n'+data); sys.stdout.flush()# 回一个固定响应c.sendall(b'HTTP/1.1 200 OK\r\nContent-Length:2\r\nConnection: close\r\n\r\nOK')c.close()
PY
2) 用 curl 发送“未知长度”的体(自动 chunked)
printf 'Hello via curl streaming\n' \
| curl -v -X POST --http1.1 --data-binary @- http://127.0.0.1:8080/stream
观察点:
curl -v
输出会显示:> Transfer-Encoding: chunked
(以及可能的Expect: 100-continue
)。服务器终端能看到完整首部与(如果你扩展了 server 继续读 body)分块体。上面简化的 server 没继续读体,只演示首部;在真实服务端会按 chunked 规则逐块读取。
想让 curl 用 Content-Length:把数据存成文件,
curl -v --data-binary @file
,curl 会自动算长度并发Content-Length
(若 HTTP/1.1 且来源是 stdin,才会偏向 chunked)。
五、对照你的嵌入式场景(重点提醒)
Content-Length 模式:计算整个 JPEG/表单体的总长度,填到首部;数据发送必须“裸字节”,不能自动追加 CRLF。
Chunked 模式:每块先告诉对端块长(十六进制),再发原始块数据,块后
\r\n
,最后0\r\n\r\n
;很多 蜂窝 AT 模组会替你写“块头/块尾”,你只需:用命令声明“下一块有 N 字节”;
原样发送 N 个字节(裸发、不加 CRLF);
按手册发送终止(有的要你发
0\r\n\r\n
,有的只要一个\r\n
)。
两种方式都要避免:把数据当“AT 命令”发(会自动加
\r\n
),导致宣称 N 实发 N+2,服务端 500/客户端 ERROR。
六、排错速查(遇到 4xx/5xx/超时)
Content-Length 不符:服务器日志常见 “request body too large/too small/invalid content-length”。
Chunked 终止缺失:没有
0\r\n\r\n
,服务器等到超时。额外 CRLF:体尾多出 2 字节,下一条请求/命令被“吞/粘”。
HTTP/2 差异:HTTP/2 没有
chunked
首部,改用帧;客户端(curl/库)会自动处理,不要手写Transfer-Encoding
给 HTTP/2。
需要我把上面的命令做成一页式测试脚本(test_http.sh
),你拷过去一键跑三种演示(Content-Length、chunked、curl 流式)吗?也可以顺手给你一个能完整接收 chunked 并回显体长度的 30 行小 Python 服务器以便联调。