一个 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 秒实验
- 把
Host那行删掉,再发给真服务器——回 400,这就是「HTTP/1.1 必须带 Host」的直接证据。 - 把行尾的
\r\n全改成\n,看真服务器接不接受——多数能容忍,但严格实现会拒,别赌。 - 把最后一个空行删掉,看服务端会怎样——它会一直等,直到超时,这是「请求不完整」的典型表现。
常见误区
- 「路径要写完整 URL」——直连时只写路径,写全 URL 是代理格式。
- 「头越多越保险」——只 Host 必填,多写只是浪费字节。
- 「空行可有可无」——必须有,否则服务端一直等。
自测三问(点开对答案)
1. 请求行的三个部分是什么? —— **方法、路径、版本**,空格分隔、CRLF 结尾。 2. 唯一必填的头是什么? —— **Host**;HTTP/1.1 里没有它服务端直接回 400。 3. 行尾该用什么? —— **CRLF**;单个 LF 多数能容忍,但严格实现会拒。- ☐ (本节完,下一节 6.2.3 HTTP 响应内容)