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

Location 就一个作用:告诉客户端「东西在另一个 URI 上」。 但它配 3xx 时是「去这里」,配 201 时是「新东西在这里」,同一个首部两种语义,只能靠状态码分。

📝 本节和前面几节的关系

前面 6.3.x 讲通用首部,6.4.x 讲请求首部,这里开始是真正「服务端说话」的字段。 第一个就是重定向的核心。

一、配 3xx:跟我走

301/302/303/307 都靠它指路。 301 是永久,302 是临时,303 强制换 GET,307 强制保持原方法——四个状态码的差别在 4.3 那几节讲过, 这里只看首部本身。 Demo 里 /old 回 302 加 Location: /new,客户端补上主机名再发一次,第一次 81 字节、 第二次 57 字节,正文只有最后那次算数,第一次那 5 字节 moved 被直接丢掉了。 这就是重定向的真实代价:多一个往返。移动端弱网下,一次多余的往返可能就是几百毫秒。 所以能配服务器直接跳就别用页面跳。

二、配 201:新东西在这儿

201 Created 表示「刚建好了一个资源」,Location 给的是这个新资源的地址。 客户端不会自动跟过去——它只是知道以后该去哪儿找。 Demo 里 POST 到 /item 回 201 加 Location: /item/7,响应就此结束,没有任何后续请求。 这是 REST 接口的标准做法:服务器分配了 ID,得告诉调用方。 容易搞混的是:同样是 Location,配 3xx 时客户端会跟,配 201 时不会。 判断依据只有状态码,不能看首部。

三、相对还是绝对

Location: /new 是相对路径,客户端按 RFC 3986 的规则用当前 URI 解析,补上协议和主机名。 Location: http://example.com/new 是绝对地址,客户端要丢掉原来的主机重连——跨域跳转就是这么来的。 规范允许两种写法,但实践中倾向于绝对地址:有些老客户端对相对路径处理不一致, 而且绝对地址能明确表达「去另一台机器」。 安全上要注意:跳转目标是用户可控的时候,必须做白名单校验。 否则一个 Location: https://evil.example/,就能把刚登录完的用户连人带 cookie 送出去。

四、动手 Demo:三种入口,三种 Location

一个服务端同时扮演三个角色:/old 是临时搬家、/abs 是搬到别的主机、/item 是刚创建完的新资源。 看同一个首部在这三种场景下的不同含义。

def fn(raw, st):
    path = raw.split(b' ')[1].decode('latin-1')
    if path == u'/old':
        return X.reply(u'HTTP/1.1 302 Found', [(u'Location', u'/new')], b'moved')
    if path == u'/abs':
        return X.reply(u'HTTP/1.1 302 Found',
                       [(u'Location', u'http://example.com/new')], b'moved')
    if path == u'/new':
        return X.reply(u'HTTP/1.1 200 OK', [], b'')
    return X.reply(u'HTTP/1.1 201 Created', [(u'Location', u'/item/7')], b'created')

实跑输出:

--------------------------------------------------------------------
Location:让浏览器去别处
--------------------------------------------------------------------
  请求 /old     → HTTP/1.1 302 Found
           Location: /new

  请求 /abs     → HTTP/1.1 302 Found
           Location: http://example.com/new

  请求 /item    → HTTP/1.1 201 Created
           Location: /item/7

  只有相对路径是能自动跟的:/old 回了 /new,客户端补上主机名再发一次。
    第 1 次 81 字节 → HTTP/1.1 302 Found
    第 2 次 57 字节 → HTTP/1.1 200 OK
    合计两次往返,正文只有最后那次算数(moved 那 5 字节被丢掉了)

  `/abs` 指向另一台主机,客户端要丢掉原主机名重连——跨域跳转就是这么来的。
  `201 Created` 的 Location 语义不同:它不是「去这里」,是「新东西在这里」,
  客户端不会自动跟过去。同一个首部,两个用途,只能靠状态码分。

/old 回 302 加相对路径 /new,客户端补主机名再发一次:第一次 81 字节、第二次 57 字节。 /abs 回的是完整地址,指向另一台主机。客户端必须丢掉原主机重连,这就是跨域跳转。 /item 回 201 加 /item/7, 注意客户端没有跟着发第二次请求——201 的 Location 只是「记录一下」,不是「现在就去」。 同一个首部,两种语义,只靠状态码区分。写客户端时这一点必须分清。

30 秒实验

  1. 把 /old 的状态码改成 301,看客户端行为有没有区别。
  2. 把相对路径改成 ../new,看客户端怎么解析。
  3. 想一想:为什么登录成功后跳转,最忌讳用用户传入的 URL 当 Location?

常见误区

  1. 「Location 一定表示跳转」——配 201 时不是,它只是新资源的地址。
  2. 「相对路径不能用」——能用,规范允许,客户端按当前 URI 解析。
  3. 「跳转是免费的」——每次多一个往返,实测 81 加 57 字节两次。
自测三问(点开对答案) 1. `201 Created` 里的 Location 是什么含义? —— 新建资源的 URI,客户端不会自动请求它。 2. `Location: /new` 是相对路径还是绝对?客户端怎么处理? —— 相对路径,按 RFC 3986 用当前 URI 补全协议和主机名。 3. 为什么开放重定向是漏洞? —— 攻击者能把跳转目标改成自己的站点,把用户和凭据一起带走。

跳转是给人看的。接下来几个字段是给「中间那台机器」看的——先是代理的认证,再是「等会儿再来」。

相关笔记