mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2251 字
6 分钟
HTTP/0.9:单行协议
2024-04-25

1991 年,你写了一个网页,里面只有纯文本。没有图片,没有样式,没有状态码。如果请求失败了,浏览器只能显示白屏,因为服务器不会告诉你为什么失败。这就是 HTTP/0.9 的世界:客户端发送一行 GET /path,服务器返回 HTML 原始字节,然后关闭 TCP 连接。没有版本号、没有请求头、没有状态码、没有 Content-Type。整个协议只有一种操作,获取文档,别的一概不管。

为什么能做到这么简单?当时网页就是静态文档,对 MIME 类型、状态码、多媒体支持的需求不高,能最快上线比功能完整更重要。下面通过亲手实现一个 HTTP/0.9 服务器,用 nc/telnet 发起原始请求,来理解这个最简协议的设计哲学,以及它为什么活不过几年。

一、核心概念#

理解 HTTP/0.9 抓住三个要点就够了。

请求/响应的最小格式:客户端发一行 GET /path,服务器直接返回文件内容。没有请求头、没有响应头、没有状态行。一行请求,一坨字节,关闭连接,结束。

没有头的含义:客户端无法知道 MIME 类型、内容长度、编码方式。服务器返回一串二进制,浏览器只能猜:这是 HTML?JPEG?还是纯文本?猜对了正常显示,猜错了就是乱码。

EOF 语义:没有 Content-Length,客户端怎么知道内容传完了?靠 TCP 连接关闭。服务器写完数据就 close(),客户端收到 EOF 就知道结束了。这个设计简洁但代价很大:每个资源都要新建 TCP 连接,无法复用。

二、实验环境#

Note
  • 推荐:Linux / macOS(命令行工具更统一)

  • 需要工具:Python 3、nc (netcat)、telnet(可选)、curl(可选)、tcpdump 或 Wireshark(可选,用于抓包观察)

  • 在 Windows 上:使用 PowerShell 的 nc 或安装 WSL/Git Bash 来运行同样命令;Python 脚本相同

三、实验一:实现 HTTP/0.9 服务器#

目标:写一个 TCP 服务,接受一行 GET /path,返回 www/<path> 的文件内容(原始字节),然后关闭连接。

核心逻辑只有几行:读取请求的第一行,解析路径,读文件,写回,关闭连接。

def handle_conn(conn, addr):
try:
# 读取直到遇到换行(0.9 请求只有一行)
data = b''
while b'\n' not in data:
chunk = conn.recv(1024)
if not chunk:
break
data += chunk
if not data:
return
# 取第一行并解析
first_line = data.splitlines()[0].decode('iso-8859-1').strip()
parts = first_line.split()
if len(parts) >= 2 and parts[0].upper() == 'GET':
path = parts[1]
if path == '/':
path = '/index.html'
safe_path = os.path.normpath(path).lstrip(os.sep)
full = os.path.join(WWW, safe_path)
if os.path.isfile(full):
with open(full, 'rb') as f:
# 0.9 的关键:直接写回文件字节,没有任何 HTTP 头
conn.sendall(f.read())
else:
# 0.9 没有状态码机制,只能返回一段正文
conn.sendall(b'<html><body><h1>404 Not Found</h1></body></html>')
except Exception as e:
print("Error handling connection:", e)
finally:
conn.close() # 关闭连接 = 告诉客户端"传完了"

几个实现细节值得注意。用 iso-8859-1 解码是因为 HTTP 协议在字节层面面向网络,这个编码对任意字节序列都能安全解码。响应阶段直接写回文件字节并 close(),这正是 0.9 的设计要点:没有响应头,EOF 表示结束。当文件不存在时返回一段 HTML,但这不是”状态码 404”,客户端无法从协议层面区分这是错误还是正常内容,只有人眼读了才知道。

完整服务器实现(http09_server.py)
#!/usr/bin/env python3
# http09_server.py -- minimal HTTP/0.9 style server for learning
import socket, threading, os
HOST = '0.0.0.0'
PORT = 8080
WWW = 'www'
def handle_conn(conn, addr):
try:
data = b''
while b'\n' not in data:
chunk = conn.recv(1024)
if not chunk:
break
data += chunk
if not data:
return
first_line = data.splitlines()[0].decode('iso-8859-1').strip()
print(f"[{addr}] request: {first_line!r}")
parts = first_line.split()
if len(parts) >= 2 and parts[0].upper() == 'GET':
path = parts[1]
if path == '/':
path = '/index.html'
safe_path = os.path.normpath(path).lstrip(os.sep)
full = os.path.join(WWW, safe_path)
if os.path.isfile(full):
with open(full, 'rb') as f:
conn.sendall(f.read())
else:
conn.sendall(b'<html><body><h1>404 Not Found</h1></body></html>')
except Exception as e:
print("Error handling connection:", e)
finally:
conn.close()
def main():
os.makedirs(WWW, exist_ok=True)
idx = os.path.join(WWW, 'index.html')
if not os.path.exists(idx):
with open(idx, 'wb') as f:
f.write(b'<html><body><h1>HTTP/0.9 demo</h1><p>It works.</p></body></html>')
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(5)
print(f"HTTP/0.9 demo server listening on {HOST}:{PORT} (serve dir: {WWW})")
try:
while True:
conn, addr = s.accept()
threading.Thread(target=handle_conn, args=(conn, addr), daemon=True).start()
except KeyboardInterrupt:
print("Shutting down.")
finally:
s.close()
if __name__ == '__main__':
main()

四、实验二:用 nc 发起原始 HTTP/0.9 请求#

启动服务器后,开另一个终端用 nc 发请求。

1. 获取 HTML 页面

printf 'GET /index.html\r\n' | nc localhost 8080

你会看到 HTML 文本直接输出,没有任何状态行和响应头。nc 在服务器关闭连接后自动结束。

2. 用 telnet 手动交互

Terminal window
$ telnet localhost 8080
GET /index.html
# 按回车后等待服务器发送并断开

3. 请求不存在的文件

printf 'GET /nonexistent.html\r\n' | nc localhost 8080

对比观察:回想上一篇导读里提到的 HTTP/1.0 的 nc 输出,那里你能看到 HTTP/1.0 200 OK 状态行、Content-Type: text/html 响应头、Content-Length: 268。而这里的 0.9 输出什么都没有,只有原始 HTML 字节。协议层面的元信息完全缺失。

更要命的是请求不存在的文件时:服务器返回的 HTML 里写着”404 Not Found”,但这是给人看的文字,不是给程序读的状态码。浏览器无法自动判断请求是否成功,只能把这段 HTML 当作正常内容渲染出来。

五、实验三(可选):用 curl 请求 HTTP/0.9#

有些 curl 版本提供 --http0.9 选项:

curl --http0.9 http://localhost:8080/

不支持也没关系,用 nc/telnet 即可完成全部学习目的。

六、抓包观察(可选)#

tcpdump 或 Wireshark 观察 TCP 流,能清晰看到:请求只有 GET /index.html\r\n,响应只有 HTML 字节,没有头部帧。

sudo tcpdump -A -s 0 'tcp port 8080'

七、可以进一步尝试的方向#

可以让服务器在收到 GET /time 时返回当前 UTC 时间的纯文本;返回二进制图片并用 printf ... | nc ... > out.png 存盘,验证 0.9 传输二进制内容的可行性;在 index.html 前面加一段 HTML 注释(<!-- meta -->)模拟”隐式元信息”,对比后来 Content-Type / headers 方案的优劣。

八、为什么 0.9 无法长期存活#

0.9 做到了极致简单,但极致简单的代价是极致受限。每个限制都会在真实使用中变成具体的问题。

无法表达元信息。没有 Content-Type,浏览器收到一串二进制字节,不知道这是 HTML、JPEG 还是纯文本,只能猜。猜对了正常显示,猜错了就是乱码。也没有 Content-Length、编码方式、缓存控制等头,代理服务器想做缓存都做不到。

没有状态码。服务器返回的内容就是内容,没有 200 OK404 Not Found。如果请求的资源不存在,服务器只能返回一个写着”404”的 HTML 页面,但浏览器无法从协议层面区分”这是正常的 HTML”还是”这是错误页面”。机器不能自动处理错误,只能靠人眼阅读。

传输结束依赖连接关闭。没有 Content-Length,客户端只能等 TCP 连接关闭才知道内容传完了。这意味着每个资源都需要新建 TCP 连接,无法复用。一个有 10 张图片的页面需要 11 次 TCP 连接建立和关闭,性能很差。

扩展性差。需要 POST 提交表单?做不到。需要 Cookie 维持会话?做不到。需要认证?做不到。需要范围请求(Range)续传大文件?做不到。这些在 0.9 里都没有位置放,因为协议根本没有定义请求头和响应头的机制。

这些问题催生了 HTTP/1.0:引入请求头和响应头来传递元信息,引入状态码来让程序自动处理结果,引入 POST 和 HEAD 方法来支持更多操作。下一篇文章会看到这些改进如何逐一解决了 0.9 的痛点,以及 1.0 自己又带来了什么新问题。

九、HTTP/0.9 特性速查#

9.1 影响评估矩阵#

特性解决了什么没有它会怎样常见误区
单行请求格式极简实现,快速上线无法传递任何参数和偏好以为 0.9 是”精简设计”,其实是”功能缺失”
无请求头/响应头不需要解析,零开销无法传递 MIME 类型、长度、编码浏览器只能猜文件类型,经常猜错
无状态码不需要实现状态机客户端无法区分成功和失败返回的”404 Not Found”只是 HTML 文字,不是状态码
EOF 结束标记不需要 Content-Length 字段无法复用连接,每个资源都新建 TCP这是 0.9 性能最差的根源
仅 GET 方法获取文档够用无法提交表单、上传文件、检查链接POST/PUT/DELETE 在 0.9 里根本没位置放

9.2 格式对比参考#

0.9 / 1.0 / 1.1 协议特性对比
特性HTTP/0.9HTTP/1.0HTTP/1.1
请求格式单行 GET /path请求行 + 头部 + 空行同 HTTP/1.0
HTTP 版本标记必须包含必须包含
请求头不支持支持支持
响应头不支持支持支持
状态码不支持支持支持
Content-Type需猜测明确指定明确指定
Content-Length依赖 EOF支持支持
支持的方法GETGET, POST, HEADGET, POST, HEAD, PUT, DELETE, OPTIONS…
持久连接每次请求新建连接默认关闭(可选 keep-alive)默认开启
Host 头不存在可选必须包含
适用场景极简文档传输静态网站、简单交互现代 Web 应用
0.9 / 1.0 请求格式对比
字段HTTP/0.9 请求HTTP/1.0 请求
请求行GET /index.html\r\nGET /index.html HTTP/1.0\r\n
请求头Host: localhost:8080\r\n User-Agent: MyBrowser/1.0\r\n
空行\r\n
说明仅一行,无头部,无版本号请求行 + 头部 + 空行
0.9 / 1.0 响应格式对比
字段HTTP/0.9 响应HTTP/1.0 响应
状态行HTTP/1.0 200 OK\r\n
响应头Content-Type: text/html\r\n Content-Length: 1234\r\n
空行\r\n
主体<html><body>...</body></html><html><body>...</body></html>
说明直接返回内容,无状态行,无头部,以连接关闭标识结束状态行 + 头部 + 空行 + 内容

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

HTTP/0.9:单行协议
https://blog.souloss.cn/posts/web/http/web-http-0-9/
作者
Souloss
发布于
2024-04-25
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时