响应是「状态行 + 头 + 空行 + 体」,与请求只差第一行。 直播的关键是:体没有长度,靠连接不关来延续。
6.2.2 拆了请求,这一节拆响应。 6.2.4 讲地址本身——URL 怎么拆、怎么编码。
一、状态行比请求行多一个东西
响应第一行是 版本 状态码 原因短语,比如 HTTP/1.1 200 OK。
请求行是「方法 路径 版本」,响应行是「版本 状态码 短语」,顺序正好反过来。
常用的状态码只有几个: 200 成功、206 部分内容(Range 请求)、301/302 重定向、 304 没改过(缓存)、403 禁止、404 找不到、500 服务端错。
206 在点播里很关键:
拖进度条靠的就是 Range 请求加 206 响应。
直播里用不上,因为没有总长度可算。
二、直播用不了 Content-Length
体的长度靠 Content-Length 声明。
没有它,客户端就不知道读到哪儿为止。
但一路直播永远下不完,没法预先声明长度。
所以三种方式里必须选一种:
① Content-Length:读够那么多字节就停。适合点播文件。
② chunked:遇到 0 长度块就停。适合长度未知但会结束的响应。
③ 什么都不声明,靠连接关闭结束:这是直播最常见的做法。
第 ③ 种方式在 HTTP/1.1 里是合法的, 但它带来的副作用是:这条连接不能复用,也几乎没法被 CDN 缓存。 这正是 6.1 里说的「HTTP-FLV 不易缓存」的根源。
三、响应头里哪几个必须看
Content-Type:直播是 video/x-flv,HLS 切片是 video/mp2t,
m3u8 是 application/vnd.apple.mpegurl。
类型给错,有些播放器会直接拒播。
Content-Length 或 Transfer-Encoding:决定怎么判结束。
Cache-Control:切片可以长缓存、m3u8 必须短缓存。
这一项设反了,观众会一直看到旧列表。
Access-Control-Allow-Origin:浏览器里播要它,
否则 hls.js 与 flv.js 都会被跨域拦住。
最后一条是前端最常见的报错来源: 服务端一切正常、播放器就是不播,控制台里写着 CORS。 这时不用怀疑流,先去查响应头。
四、动手 Demo:HTTP 响应——状态行、头、空行、体
手拼一个最小的 200 响应,量出头与体各占多少字节。 再用 urllib 真取一次,看服务端自动加了哪些头。
import io, os, tempfile, threading
import http.server, socketserver, urllib.request
body = b"hello"
resp = (b"HTTP/1.1 200 OK\r\n"
b"Content-Type: text/plain\r\n"
b"Content-Length: " + str(len(body)).encode() + b"\r\n"
b"\r\n" + body)
print("响应共 %d 字节,头 %d、体 %d"
% (len(resp), len(resp) - len(body), len(body)))
for ln in resp.split(b"\r\n")[:-1]:
print(" " + ln.decode(errors="replace"))
root = tempfile.mkdtemp()
io.open(os.path.join(root, "a.txt"), "wb").write(body)
class H(http.server.SimpleHTTPRequestHandler):
def __init__(self, *a, **kw):
super().__init__(*a, directory=root, **kw)
def log_message(self, *a):
pass
httpd = socketserver.TCPServer(("127.0.0.1", 0), H)
port = httpd.server_address[1]
threading.Thread(target=httpd.serve_forever, daemon=True).start()
r = urllib.request.urlopen("http://127.0.0.1:%d/a.txt" % port, timeout=3)
print()
print("状态码 %d,协议 %s" % (r.status, r.version))
for k in ("Content-Length", "Content-Type", "Server"):
if r.headers.get(k):
print(" %s: %s" % (k, r.headers.get(k)))
httpd.shutdown()
实跑输出:
--------------------------------------------------------------------
HTTP 响应——状态行、头、空行、体
--------------------------------------------------------------------
**① 一段最小的 200 响应**
**响应共 69 字节,其中头 64、体 5**
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 5
**「体」的长度靠 Content-Length 声明。**
**没有它,客户端就不知道读到哪儿为止。**
**② 直播场景用不了 Content-Length**
**一路直播永远下不完,没法预先声明长度。**
**所以要么用 chunked,要么直接不给长度、靠 Connection: close 结束。**
方式 怎么结束 适合
----------------------------------------------------------
Content-Length 读够那么多字节 点播文件
chunked 遇到 0 长度块 长度未知的响应
不声明 连接关闭 直播是最常见做法
**③ 真取一次,看状态码与头**
**状态码 200,协议 10**
**Content-Length:5**
**Content-Type:text/plain**
**Server:SimpleHTTP/0.6 Python/3.13.14**
**看头里的 Server 与 Date 是服务端加的,请求里没有。**
**所以「响应头比请求头多」是正常的。**
头 62 字节、体 5 字节——小响应的头比体长十倍,这是常态。
Server 与 Date 是服务端自动加的,请求里从来没有这两项。
Content-Length 是「体的长度」,不是整个响应的长度。
30 秒实验
- 把
Content-Length删掉,用 urllib 取——它靠连接关闭判结束,能取到但没法复用连接。 - 把
Content-Type改成text/html,看浏览器行为——会当网页渲染,这说明类型决定怎么解释体,不决定内容。 - 发一个
Range: bytes=0-1请求,看状态码——变 206,这就是点播拖动的底层机制。
常见误区
- 「Content-Length 是整个响应的长度」——只是体的长度。
- 「直播必须给 Content-Length」——给不了,靠连接关闭或 chunked。
- 「CORS 是服务端业务错误」——是响应头缺失,与流无关。
自测三问(点开对答案)
1. 响应行与请求行的区别? —— **响应是「版本 状态码 短语」**,请求是「方法 路径 版本」。 2. 直播怎么表示「体结束」? —— **不声明长度,靠连接关闭**;也可以用 chunked。 3. 浏览器里播不了先查什么? —— **`Access-Control-Allow-Origin`**,跨域头缺失是最常见原因。- ☐ (本节完,下一节 6.2.4 URL 简介)