🎬 第六章 HTTP-FLV流媒体协议
📄 一句话概括

一个 HTTP 请求就三样东西:请求行、头、空行。 必填的头只有一个:Host。

📝 本节和前面几节的关系

6.2.1 补了 TLS,这一节开始拆 HTTP 报文的请求侧。 6.2.3 拆响应侧——两边结构对称。

一、请求行的三个部分

方法 路径 版本,中间用单个空格分隔,末尾是 CRLF。 方法是 GET/POST/PUT/DELETE 这些; 路径是 URL 里的 path 加 query,不含协议与主机; 版本在 HTTP/1.1 里就是 HTTP/1.1。

实测手写一个最小请求,一共 89 字节: GET /live.flv HTTP/1.1 加三个头加一个空行。

注意路径不能写成完整 URL。 写 GET http://a.com/x HTTP/1.1 是「代理请求」的格式, 直连服务器时这么写,很多服务端会直接回 400。

二、头部的三条规则

① 每行 名字: 值,冒号后面习惯加一个空格。 名字不区分大小写。 ② 必填的只有一个:Host。 HTTP/1.1 里没有它就直接回 400,因为一个 IP 可能挂多个站点。 ③ 头结束后必须有一个空行。 没有它,服务端会认为「请求还没发完」,一直等下去。

第 ③ 条是实操里最常见的坑。 自己用 socket 拼请求时,末尾要写两个 CRLF。 如果只写一个,服务端会挂在那儿等,直到超时。

还有一条规范要求但常被忽略:行尾必须是 CRLF,不是 LF。 大多数服务端能容忍单个 LF,但严格实现会拒绝。 自己的代码里一律写 CRLF,别赌对端宽容。

三、直播场景常用哪几个头

Host:必填。 User-Agent:服务端常按它做灰度或统计。 Range:点播拖进度条时用,直播不用。 Connection: keep-alive:要长连接就明说。 HTTP/1.1 默认就是长连接,写它是为了兼容老服务端。 Referer:防盗链常用,但可以伪造,只能挡君子。

一个真实细节:HTTP-FLV 的请求和普通 GET 没有区别。 服务端看到 GET /live.flv 就返回 Content-Type: video/x-flv, 然后一直往响应体里写——请求侧没有任何特殊之处。

四、动手 Demo:HTTP 请求——三行骨架加一个空行

手写一个 89 字节的请求,先看它的原文,再用 socket 真发一次。 服务端收到的字节数与发出去的一模一样。

import socket, threading

req = ("GET /live.flv HTTP/1.1\r\n"
       "Host: 127.0.0.1:8080\r\n"
       "User-Agent: demo/1.0\r\n"
       "Connection: close\r\n"
       "\r\n")
print("请求共 %d 字节" % len(req.encode()))
for ln in req.split("\r\n")[:-1]:
    print("  " + (ln if ln else "(空行,头结束)"))

rs = socket.socket()
rs.bind(("127.0.0.1", 0))
rs.listen(1)
port = rs.getsockname()[1]
box = {}

def serve():
    c, _ = rs.accept()
    box["d"] = c.recv(65535)
    c.sendall(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n")
    c.close()

th = threading.Thread(target=serve, daemon=True)
th.start()
s = socket.create_connection(("127.0.0.1", port), timeout=3)
s.sendall(req.encode())
s.recv(4096)
s.close()
th.join(3)
rs.close()

got = box["d"]
print("服务端收到 %d 字节" % len(got))
head = got.split(b"\r\n\r\n")[0].decode()
m, p, v = head.split("\r\n")[0].split(" ")
print("方法 %s,路径 %s,版本 %s" % (m, p, v))
for ln in head.split("\r\n")[1:]:
    k, _, val = ln.partition(":")
    print("  %s = %s" % (k, val.strip()))

实跑输出:

--------------------------------------------------------------------
HTTP 请求——三行骨架加一个空行
--------------------------------------------------------------------
**① 手写一个请求,看它到底长什么样**

  **一共 89 字节:**

    GET /live.flv HTTP/1.1
    Host: 127.0.0.1:8080
    User-Agent: demo/1.0
    Connection: close
    (空行,这里表示头结束)

  **注意最后那个空行:没有它,服务端会一直等下去。**
  **这是「HTTP 请求不完整」最常见的原因。**

**② 真发一次,看服务端收到什么**

  **服务端收到 89 字节**

  部件           值
  ---------------------------------
  方法           GET
  路径           /live.flv
  版本           HTTP/1.1
  Host           127.0.0.1:8080
  User-Agent     demo/1.0
  Connection     close

  **必填的头只有一个:Host。**
  **HTTP/1.1 里没有 Host 就直接回 400。**

89 字节里,请求行占 22、头占 62、空行占 2——空行也是实打实的字节。 服务端收到的字节数与发出的一致,说明 TCP 上没有额外包装。 split("\r\n")[:-1] 是为了只打印一次空行,否则结尾的 \r\n\r\n 会切出两个空串。

30 秒实验

  1. 把 Host 那行删掉,再发给真服务器——回 400,这就是「HTTP/1.1 必须带 Host」的直接证据。
  2. 把行尾的 \r\n 全改成 \n,看真服务器接不接受——多数能容忍,但严格实现会拒,别赌。
  3. 把最后一个空行删掉,看服务端会怎样——它会一直等,直到超时,这是「请求不完整」的典型表现。

常见误区

  1. 「路径要写完整 URL」——直连时只写路径,写全 URL 是代理格式。
  2. 「头越多越保险」——只 Host 必填,多写只是浪费字节。
  3. 「空行可有可无」——必须有,否则服务端一直等。
自测三问(点开对答案) 1. 请求行的三个部分是什么? —— **方法、路径、版本**,空格分隔、CRLF 结尾。 2. 唯一必填的头是什么? —— **Host**;HTTP/1.1 里没有它服务端直接回 400。 3. 行尾该用什么? —— **CRLF**;单个 LF 多数能容忍,但严格实现会拒。

相关笔记