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

Set-Cookie 只出现在响应里,一个首部行只装一个 cookie,要发三个就写三行。 它靠 Expires/Max-Age 定寿命,靠 Secure/HttpOnly 定边界。

📝 本节和前面几节的关系

实体首部讲完了。 这一节和下一节是 Cookie 的两半:服务端用它种,客户端用 Cookie 首部回。

一、为什么它不能合并成一行

别的首部同名可以合:Accept: a 加 Accept: b 等价于 Accept: a, b。 Set-Cookie 不行——它的值里本身就允许出现逗号(Expires 的日期就带逗号),一旦合并, 接收方没法可靠地再切开。 所以规矩是:一个 cookie 一行。 Demo 里种四个 cookie,响应里就有四行,整条响应 320 字节,其中大半是这四行。 这也带来一个副作用:cookie 多了,首部会显著变长。 HTTP/2 用 HPACK 压缩首部,能缓解一部分,但压不动随机串。

二、寿命的三种写法

Max-Age=3600 是相对秒数,从现在起算一小时。Expires 是绝对时刻,格式是 HTTP 日期。 两个都写时 Max-Age 优先,因为不怕时钟不对。 什么都不写,就是会话 cookie:存在内存里,关掉浏览器就没了。 Demo 里 sid 那条只写了 Path 和 HttpOnly,就是会话级的。 Max-Age=0 表示立刻删掉这一条。Demo 里 tmp=x 那条就是用来清场的。 反过来 Max-Age=-1 在有些实现里也按删除处理,但不如写 0 明确。

三、两个安全属性

Secure 限定只在 HTTPS 连接上发送。注意它不加密 cookie,只是不让它在明文连接里出现。 HttpOnly 限定脚本读不到(document.cookie 里看不到它)。这是防 XSS 偷 cookie 的主要手段之一。 这两个属性本机都没法验证行为——没有浏览器,也没有 HTTPS 服务端。 Demo 只验证了「首部确实发出去了、属性确实解析得出来」,行为层面这里不编结论。 真正要确认,得上浏览器加真证书试。

四、动手 Demo:一次种四个,看属性怎么拆

服务端在同一个响应里发四行 Set-Cookie,分别带 HttpOnly、Max-Age、Expires 加 Secure、 以及 Max-Age=0;客户端逐条拆出名字、值、属性和寿命。

return X.reply(u'HTTP/1.1 200 OK', [
    (u'Set-Cookie', u'sid=abc123; Path=/; HttpOnly'),
    (u'Set-Cookie', u'theme=dark; Path=/; Max-Age=3600'),
    (u'Set-Cookie', u'seen=1; Expires=%s; Secure' % exp),
    (u'Set-Cookie', u'tmp=x; Max-Age=0'),
], TEXT)

实跑输出:

--------------------------------------------------------------------
Set-Cookie:一次能发好几条,每条只装一个
--------------------------------------------------------------------
  响应里一共 4 行 Set-Cookie,整条响应 320 字节:
    Set-Cookie: sid=abc123; Path=/; HttpOnly
    Set-Cookie: theme=dark; Path=/; Max-Age=3600
    Set-Cookie: seen=1; Expires=Mon, 26 Oct 2026 06:44:58 GMT; Secure
    Set-Cookie: tmp=x; Max-Age=0

  客户端逐条拆开,属性各归各位:
  名字     值       Path     有效期与标志
  sid      abc123   /        HttpOnly
  theme    dark     /        Max-Age=3600(相对秒数)
  seen     1        (无)   Expires(绝对时刻)、Secure
  tmp      x        (无)   Max-Age=0(立即删)

  四行里藏着四条规矩:
    一、一个 Set-Cookie 只能装一个 cookie。四个 cookie 就要四行,
       不能像别的首部那样用逗号合并——因为值里本身就可能有逗号。
    二、`Max-Age` 是相对秒数,`Expires` 是绝对时刻,两个都写时 Max-Age 优先。
    三、`Max-Age=0` 是「立刻删掉这一条」,可不是「永不过期」。
    四、没有任何有效期属性的,是会话 cookie,关掉浏览器就没了。

  至于 `HttpOnly` 到底能不能挡住脚本读取——本机没有浏览器,
  这一条只验证了首部确实发出去了,行为层面测不到,这里不编结论。

四行全在,整条响应 320 字节。四个 cookie 就是四行,没有合并。 拆开之后:sid 只有 HttpOnly,是会话 cookie;theme 有 Max-Age=3600,相对一秒数; seen 有 Expires 和 Secure;tmp 的 Max-Age=0 是删除指令。 第三行的 Expires 是个绝对时刻,写法里带逗号——这正是「不能把多行 Set-Cookie 合并」的原因。 四行属性加起来才把「这个 cookie 是谁的、活多久、能不能被脚本读」说全。 少写一个 Path,它的作用域就默认成当前目录,很容易埋坑。

30 秒实验

  1. 把 Max-Age=0 改成 Max-Age=-1,看解析出来的值是什么。
  2. 删掉 sid 那行的 HttpOnly,比一比响应少多少字节。
  3. 想一想:为什么 Secure 不能代替 HTTPS?

常见误区

  1. 「多个 Set-Cookie 可以用逗号合并」——不行,值里本身可能有逗号。
  2. 「Max-Age=0 是永不过期」——正好相反,是立刻删除。
  3. 「Secure 会加密 cookie」——只是限定在加密连接上发送。
自测三问(点开对答案) 1. 要种三个 cookie,响应里要写几行 `Set-Cookie`? —— 三行,一个 cookie 一行,不能合并。 2. `Max-Age` 和 `Expires` 都写时听谁的? —— 听 `Max-Age`,它是相对秒数,不怕时钟不对。 3. 什么都不写寿命属性的 cookie 会怎样? —— 成为会话 cookie,存内存里,关掉浏览器就没了。

服务端种下去,客户端还得回送。下一个字段就是回送时用的:Cookie。

相关笔记