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

Content-Location 说明「你手上这份内容,老家在另一个地址」。 它和 Location 只差一个词,但不会让客户端再发一次请求。

📝 本节和前面几节的关系

上一节定了正文边界,这一节说正文的「来历」。6.5.4 讲 Location 时留的对照,到这里补上。

一、它说的是「内容的地址」

请求 /report-copy 拿到 200 和完整正文,首部里写 Content-Location: /report, 意思是「内容给你了,它正经的地址是 /report」。 关键点:客户端不会因此去请求 /report。 这是它和 Location 的根本区别——后者配 3xx 时是命令「去那儿」,前者只是备注「它老家在那儿」。 Demo 里三次请求都是一次往返:/report 200、/report-copy 200 带 Content-Location、 /alias 302 带 Location。只有第三个需要再发一次。

二、Location 与 Content-Location

两个首部只差一个词,行为差一次往返: | 首部 | 含义 | 客户端会再请求吗 | |---|---|:---:| | Location | 你走错了,去那儿 | 会(配 3xx 时) | | Content-Location | 东西给你了,它老家在那儿 | 不会 | 同一个 Location 在 201 里又变成「新资源在这儿」,也不触发请求。 所以判断依据永远是状态码,不是首部名。 实践中最常见的误用,是把 Location 当 Content-Location 用:以为加了它客户端就会跳转, 结果什么都没发生。

三、实际用在哪

用处确实不多。 最典型的是同一份内容有多个入口:带版本号的 URL(/v2/report 与 /report 内容相同)、 镜像地址、内容协商后的具体版本。 另一处是 PUT:上传成功后回一份内容,用 Content-Location 说明「这就是 /a 那份的副本」, 客户端不用再 GET 一次。 它还能帮缓存做去重:两个 URL 内容相同,缓存看到同一个 Content-Location 就可以只存一份。 不过这属于优化,实现里很少做。

四、动手 Demo:同一个首部位置,两种行为

服务端准备三个入口:正主 /report、副本 /report-copy、跳转 /alias。 看客户端在这三种情况下分别发了几个请求。

def fn(raw, st):
    path = raw.split(b' ')[1].decode('latin-1')
    if path == u'/report':
        return X.reply(u'HTTP/1.1 200 OK', [(u'Content-Type', u'text/html')], DOC)
    if path == u'/report-copy':
        return X.reply(u'HTTP/1.1 200 OK',
                       [(u'Content-Type', u'text/html'),
                        (u'Content-Location', u'/report')], DOC)
    return X.reply(u'HTTP/1.1 302 Found', [(u'Location', u'/report')], b'moved')

实跑输出:

--------------------------------------------------------------------
Content-Location:这份内容的老家在哪儿
--------------------------------------------------------------------
  请求             状态                       首部里多了什么
  /report          HTTP/1.1 200 OK            (没有)
  /report-copy     HTTP/1.1 200 OK            Content-Location: /report
  /alias           HTTP/1.1 302 Found         Location: /report

  三次都只发了一次请求。`/report-copy` 拿到 200 和完整正文,
  但首部多出一行 `Content-Location: /report`,意思是「你手上这份内容,
  正经的地址是 /report」。客户端**不会**因此去请求 /report。

  对照 `/alias`:那是 302 加 `Location`,客户端得再发一次才拿到东西。
  两个首部只差一个词,行为差一次往返:
    Location          你走错了,去那儿(要再请求)
    Content-Location  东西给你了,它老家在那儿(不再请求)

  实际用途不多,最典型的是同一份内容有多个入口(带版本号的 URL、镜像地址);
  或者 PUT 之后回一份内容,说明「这是 /a 那份的副本」。

/report 200,什么都没有——它就是正主。 /report-copy 200,多一行 Content-Location: /report,正文完整。客户端没有再去请求 /report。 /alias 302,带 Location: /report。这才是「去那儿」,需要第二次往返。 三次请求、三次往返的差别,全在这两个首部的名字上。 记法:带 -Location 前缀的是「补充说明」,光秃秃的 Location 是「指令」。

30 秒实验

  1. 给 /report 的响应也加一行 Content-Location: /report-copy,想想要不要跟。
  2. 把 /report-copy 改成 301 加 Location,看行为变不变。
  3. 想一想:如果两个 URL 的内容不同,写同一个 Content-Location 会误导谁?

常见误区

  1. 「Content-Location 会触发跳转」——不会,它只是备注。
  2. 「它和 Location 可以互换」——一个是指令,一个是说明。
  3. 「内容不同的两个地址也能共用一个 Content-Location」——那是撒谎,会误导缓存。
自测三问(点开对答案) 1. `Content-Location` 会让客户端再发请求吗? —— 不会,它只说明内容的原始地址。 2. `Location` 在 201 里是什么含义? —— 新建资源的地址,也不触发请求。 3. 什么场景适合用 `Content-Location`? —— 同一份内容有多个入口,或者 PUT 之后说明副本来源。

地址说清了,接下来给正文配一个校验和:Content-MD5。

相关笔记