📘 第六章 HTTP首部
📄 一句话概括

Cookie 是客户端回送的那一半:所有 cookie 挤在一行里,用分号加空格分隔。 它按 Path/Domain 过滤,每发一个请求就带一次——这也是它吃带宽的原因。

📝 本节和前面几节的关系

上一节是种,这一节是收。两个字段合起来才构成「HTTP 无状态,但能记住你是谁」。

一、回送时是一行

服务端可以发四行 Set-Cookie,客户端回送时却只有一行 Cookie,值之间用 ; 分隔: sid=abc123; theme=dark; lang=zh-CN。 这是很多人第一次抓包时的困惑:种下去是四行,收回来是一行。 原因很简单——回送只需要「名字=值」,属性和作用域已经在客户端那边生效过了。 所以 Cookie 首部里不会出现 Path、Expires、Secure。只带键值对,别的一概不带。

二、发之前先按作用域筛

客户端不是把整个 jar 都塞进去,而是先按 Path 和 Domain 筛一遍。 Demo 里四条 cookie,其中 lang 的 Path=/docs。 三种路径的实测:请求 / 发三条,Cookie 那行 37 字节、请求共 74 字节; 请求 /docs/intro 发四条,Cookie 行 49 字节、请求 96 字节;请求 /admin 又回到三条。 差别全在 lang 身上。这条 12 字节的 lang=zh-CN 在 /docs 下面才出现,路径一换就消失。 作用是「别把无关的 cookie 到处送」。

三、带宽账要算清

cookie 是跟着每一个请求走的。一个页面引 60 个资源,就是 60 次带 cookie 的请求; cookie 总共 500 字节,这一页就白扔 30 KB。 所以规范建议单个 cookie 不超过 4 KB,单个域名的总数不超过几十个。 静态资源放在独立域名下(不带 cookie),就是省这笔钱的老办法。 另一条路是 Domain 与 Path 尽量写窄:只给 /api 用的 cookie 就别设成 Path=/, 否则整站的每个请求都替它付费。

四、动手 Demo:四条 cookie,三种路径,看筛掉哪条

本地维护一个四条 cookie 的 jar,其中一条的 Path 是 /docs; 按三条不同路径各构造一次请求,量出 Cookie 那行和整个请求的字节数。

jar = [(u'sid', u'abc123', u'/'),
       (u'theme', u'dark', u'/'),
       (u'lang', u'zh-CN', u'/docs'),
       (u'tmp', u'x', u'/')]

def build(path):
    out = []
    for name, val, cpath in jar:
        if path.startswith(cpath):      # Path 前缀匹配
            out.append(u'%s=%s' % (name, val))
    return u'; '.join(out)              # 一行,分号加空格

实跑输出:

--------------------------------------------------------------------
Cookie:回送时全挤在一行,用分号隔开
--------------------------------------------------------------------
  客户端手上存了四条 cookie,作用域不同:
    sid=abc123  Path=/
    theme=dark  Path=/
    lang=zh-CN  Path=/docs
    tmp=x  Path=/

  请求 /              Cookie: sid=abc123; theme=dark; tmp=x
                    请求共 74 字节,其中 Cookie 那行 37 字节
  请求 /docs/intro    Cookie: sid=abc123; theme=dark; lang=zh-CN; tmp=x
                    请求共 96 字节,其中 Cookie 那行 49 字节
  请求 /admin         Cookie: sid=abc123; theme=dark; tmp=x
                    请求共 79 字节,其中 Cookie 那行 37 字节

  真发一次,看服务端收到什么:
    发出去的:sid=abc123; theme=dark; lang=zh-CN; tmp=x
    服务端回:收到 sid=abc123; theme=dark; lang=zh-CN; tmp=x

  三件事:
    一、所有 cookie 挤在**一行**里,用 `; ` 分隔,不是多行。
    二、`Path=/docs` 那条只在 `/docs` 开头的路径上才发;请求 `/admin` 时它不在。
    三、每请求都带,所以它吃的带宽是「每条 cookie 乘上请求数」——
       这就是为什么规范建议单个 cookie 别超过 4 KB,总数别太多。

/ 和 /admin 都发了三条,Cookie 行 37 字节、请求 74 与 79 字节。 /admin 多那 5 字节是路径本身变长了。 /docs/intro 发四条,Cookie 行 49 字节、请求 96 字节。 多出来的 12 字节正是 lang=zh-CN 加分隔符。 服务端那一次真收到了 sid=abc123; theme=dark; lang=zh-CN; tmp=x,和客户端构造的字符串一字不差。 注意 Path 匹配是前缀匹配,而且按目录边界算:/docs 能匹配 /docs/intro, 但一般不该匹配 /docs2——这一层细节各实现略有差别,别把安全边界压在它上面。

30 秒实验

  1. 把 lang 的 Path 改成 /,看三个请求的 Cookie 行长度会不会都一样。
  2. 加一条 Path=/docs 但名字相同的 cookie,看同名时哪个生效。
  3. 想一想:为什么登录态的 cookie 一般不设 Path=/?

常见误区

  1. 「Cookie 首部会带上 Path 和 Expires」——不带,只带键值对。
  2. 「可以用多行 Cookie」——回送只有一行,多条用分号分隔。
  3. 「cookie 只在需要时才发」——只要路径和域匹配,每个请求都发。
自测三问(点开对答案) 1. 客户端回送 cookie 时是一行还是多行? —— 一行,多个键值对用分号加空格分隔。 2. `Path=/docs` 的 cookie 会在请求 `/admin` 时发送吗? —— 不会,路径不匹配就被筛掉。 3. 为什么静态资源常放在不带 cookie 的域名下? —— cookie 跟着每个请求走,省掉它就是省带宽。

Cookie 两节完了。最后四个字段都是「附加约定」,而且四个里有三个已经退场——先从还能用的那个说起。

相关笔记