📘 第二章 简单的HTTP协议
📄 一句话概括

HTTP 本身不带状态:每个请求都是独立的,服务端不记得上一个。 这不是缺陷,是为了让任何一台机器都能处理任何一个请求。

📝 本节和前面几节的关系

上一节看报文结构,会发现里面根本没有「会话号」这种字段。 这一节把这件事说透,2.8 节再讲怎么在协议外面补上状态。

一、「无状态」到底缺了什么

连发三次请求,服务端收到的字节逐字节相同(除了源端口)。 它没有任何依据判断这三次是不是同一个人。 所以「购物车」「登录态」「多步表单」这些跨请求的东西,协议本身一概不管。 要管,就得让客户端每次主动带一个标识。 注意措辞:无状态说的是协议不要求也不携带状态,不是说服务端不能存数据。 服务端当然可以存,但它得先能认出「这是谁的请求」,而这一步协议不提供。

二、无状态换来了什么

换来的是可伸缩:任何一台服务器都能处理任何一个请求,因为处理它需要的全部信息都在这个请求里。 加机器、重启、换机房,都不影响正确性。 对比一下有状态协议(比如 FTP 的控制连接):它记着「当前目录是哪个」「上一条命令是什么」, 所以连接断了要重来,而且很难做负载均衡。 代价也很实在:每个请求都要把上下文重说一遍。Cookie、Token、签名,都是为了这件事付出的开销。

三、补状态的三种做法

做法 状态存在哪 代价
Cookie/Session 服务端内存或数据库 要靠共享存储做集群
Token(JWT) 客户端自己带着 无法即时吊销,体积大
隐藏字段/URL 参数 页面里 容易丢、容易被改
三种都不是协议的补丁,而是应用层的约定。协议一直无状态,变的是应用怎么在无状态上盖房子。
顺带一个常见误解:Connection: keep-alive 不是「保持登录状态」,它只是让 TCP 连接别关,
跟身份一点关系都没有。

四、动手 Demo:连发三次,看服务端能不能认出你

同一条连接上连发三次一模一样的请求,服务端把每次收到的 Cookie 首部打印出来——三次都是「没有」。

calls = []
def resp(raw, st):
    first, hd, body = H.parse_headers(raw)
    calls.append((first, hd.get(u'cookie')))
    return (u'HTTP/1.1 200 OK', [(u'Content-Type', u'text/plain')],
            (u'第 %d 次' % len(calls)).encode())
with H.server_multi(resp, keepalive=True) as port:
    buf = b''
    for _ in range(3):
        s.sendall(_req())
        _, buf = H.recv_response(s, buf)

实跑输出:

--------------------------------------------------------------------
无状态:服务端凭什么认出你
--------------------------------------------------------------------
  第 1 次请求                Cookie 首部:(没有)
  第 2 次请求                Cookie 首部:(没有)
  第 3 次请求                Cookie 首部:(没有)
  服务端记住了几次           3 次,但每次的请求一模一样

  三次请求逐字节相同,服务端**没有任何办法**判断
  这三次是不是同一个人——这就是「HTTP 是无状态协议」。
  注意:不是「不能保存状态」,是「协议本身不要求也不携带状态」。
  要认出同一个人,必须由客户端每次**主动带上一个标识**,
  那个标识就是 Cookie(见 2.8 节),或者 Token。
  无状态的好处:任何一个服务端实例都能处理任何一个请求,
  所以加机器就能扩容;坏处是每个请求都要重复带上下文。

三次都是 Cookie 首部:(没有)——服务端手上没有任何线索。 注意这里用的是同一条连接(keepalive=True),连源端口都一样,服务端仍然认不出。 连接复用不等于会话保持,这是最容易混的一对概念。 服务端记住了几次 3 次,但每次的请求一模一样——它记的是计数,不是人。

30 秒实验

  1. 把 keepalive 改成 False,让三次请求各用一条新连接,再看输出。
  2. 在第二次请求里手动加一行 Cookie: a=1,看服务端能不能读到。
  3. 想一想:如果服务端把状态存在自己的内存里,部署两台之后会怎样?

常见误区

  1. 「HTTP 无状态所以服务端不能存数据」——能存,只是没法定身份。
  2. 「keep-alive 就是保持会话」——它只管 TCP 连接不关,不管身份。
  3. 「加了 Cookie 就有状态了」——Cookie 只是让客户端带标识,状态仍然是应用自己存在别处的。
自测三问(点开对答案) 1. HTTP 无状态的确切含义是什么? —— 协议本身不要求也不携带跨请求的状态;每个请求必须自带处理它所需的全部信息。 2. 无状态带来的最大好处? —— 可伸缩:任何实例都能处理任何请求,加机器不影响正确性。 3. 连接复用和会话保持是一回事吗? —— 不是。前者是 TCP 层的事,后者要靠 Cookie/Token 之类的应用层标识。

状态的问题先记着。下一节看请求行里那个「要什么」是怎么写的。

相关笔记