API 的 Sunset 响应头:什么时候该发送它
阅读约 1 分钟
Sunset 是一个单独的响应头,定义在 RFC 8594 里,它告诉
调用方一个资源什么时候会停止应答。API 停用讲的是宣布、提醒、限时停
机到服务下线这整条时间线,以及沿途需要发出的各种通知;这篇文章讲的是那条时间线里唯一一个机器
可读的信号,它到底说了什么,以及 RFC 本身明确说不应该发送它的那一种情况。
Sunset 响应头到底说了什么,又没说什么
它携带一个单独的 HTTP 日期,也就是这个资源预计会停止应答的那个时间点:
Sunset: Sat, 31 Dec 2028 23:59:59 GMT
RFC 把它称为一个提示,而不是一个保证:它并不承诺这个资源会一直正常工作到那个时间戳为止,也完 全没有说清楚之后失败会是什么样子。调用方可能会收到一个 4xx、一次重定向,或者干脆什么响应都没 有;这个响应头并不区分这些情况。一个已经过去的时间戳,意思是”现在,或者随时都可能”,而不是这 个值本身出了错。这一切都不是协议强制要求的。一个从来不读这个响应头的客户端,行为跟以前完全一 样,最终发现资源已经消失的方式,也和它本来会发现的方式一模一样。
到底应该在什么时候发送它
只有当这个资源真的即将停止应答的时候才发送,而不是它只是不再是推荐选项的时候。RFC 明确说明停
用分成两个阶段,而 Sunset 响应头只属于第二个阶段:在第一个阶段里,也就是宣布某个版本不再被
推荐的阶段,这个 API 依然完全正常运作,这个响应头在这个阶段并不适用。它只适用于版本真的已经被
安排好、即将停止应答的那个阶段。
这正好对应到停用的时间线上:Deprecation 响应头从第一天,也就是宣布那一步开始发出;Sunset
描述的是旧行为真正停止的那个日期,也就是那条四步时间线里所说的服
务下线。在第一天就发送 Sunset 并没有错,因为那时日期已经确定了,但如果没有先宣布停用就发送
它,或者把它设置给一个你其实还没有真正决定要下线的版本,就是在告诉调用方一件你自己都还没决定
的事情。
它会和缓存互相影响吗
不会,RFC 里直接这么说:Sunset 和 HTTP 缓存解决的是两个互不相关的问题,应该把它们理解成互补
关系,而不是有重叠。缓存响应头说的是一份缓存副本什么时候可以安全地被重复使用;Sunset 完全不
涉及资源当前的状态,它说的只是这个资源本身将会不再存在。一个响应完全可以一直保持可缓存,直到
它真正下线的那一刻。不要用其中一个去近似替代另一个,也不要以为一个很长的 max-age 就能抵消掉
一个临近的下线日期,反过来也一样。
一个响应头能不能同时下线多个端点
这个响应头本身只适用于返回它的那个资源,但 RFC 允许一个服务把作用范围写得更宽:一个 API 主资
源上的 Sunset 日期,可以被定义成代表整个 API 都将下线,而不只是那一个 URL。问题在于,这种做
法只对已经知道你这条作用范围规则的调用方有效。一个只按字面读取响应头的调用方,看到的只是它请
求的那一个资源被下线了,仅此而已,所以更宽的作用范围必须写在调用方能找到的地方,而不能只是心
照不宣。
这个响应头应该搭配什么一起发出
一个指向服务下线说明页面的链接。RFC 8594 专门为此注册了自己的 sunset 链接关系:指向一个专门
描述下线政策、即将到来的日期,或者迁移方法的资源,而不只是响应头里那个光秃秃的时间戳。
HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2028 23:59:59 GMT
Link: <https://example.com/docs/sunset-policy>; rel="sunset"
把这个链接指向你自己的体验日志范例或者一个专门的迁移页面,能把一个几乎
没有任何客户端代码会去检查的响应头,变成一个真的去查找的人能立刻找到的东西。把它和停用响应头
里的 successor-version 关系结合起来,调用方仅凭这一个响应,就能同时知道该去哪里,以及是什么
取代了这个版本。
完整走一遍是什么样子
假设 v1 将在 2027 年 3 月 1 日下线。按照停用响应头的做法,第一
天的停用公告会给每一个 v1 响应加上 Deprecation 和 Link: rel="successor-version",但会先
按住 Sunset,直到那个下线日期真正确定下来,而不是一个占位值。一旦确定,每一个 v1 响应就会
带上:
HTTP/1.1 200 OK
Deprecation: @1756425600
Sunset: Mon, 01 Mar 2027 00:00:00 GMT
Link: <https://api.example.com/v2/reports>; rel="successor-version"
Link: <https://example.com/docs/sunset-policy>; rel="sunset"
调用方的网关或监控系统可以针对这两个响应头分别独立地发出警报:Deprecation 说明存在一个更新
的版本,Sunset 说明这个版本已经有了一个倒计时。在 3 月 1 日之前,这两个响应头都不需要发生任
何变化;真正发生变化的是响应本身,在那一天,以及在那之前安排好的任何限时停机窗口期间。
限时停机会改变这个响应头说的内容吗
为了一次计划内的限时停机,响应头本身的值并不需要跟着变动:下线日期还是那个下线日期,不管这个
资源在那之前是不是间歇性地出错。真正变化的是响应本身,而不是响应头。像API 停用
里说的那样,在公布的日期前几周安排几段短暂的 410 Gone 窗口,能让调用方第一次遇到这个失败,
变成一次预演,而不是等到响应头上那个日期真正到来的那一天才第一次遇到真正的情况。
FAQ
真的有 HTTP 客户端或工具会去读 Sunset 响应头吗? 在客户端这一侧很少见。它的价值主要是给那些运营着你和调用方之间基础设施的人准备的:一个 API 网关,或者一个你配置好去监视这个响应头的监控工具,能在调用方的代码注意到之前很久,就提醒你自己的团队,或者对方的团队。把它当成一个你需要自己搭建工具去围绕它构建的信号,而不要假设对方已经有现成的东西在读它。
Sunset 和 Cache-Control: max-age 是一回事吗?
不是。max-age 说的是一份缓存副本能保持有效多久;Sunset 说的是这个资源到底什么时候会彻底不存在。一个响应完全可以同时带着一个很短的 max-age,和一个还有好几年才会到达的 Sunset 日期,反过来也一样,这两个响应头互相都不会限制对方。
只有一个字段要下线,而不是整个端点,能发送 Sunset 吗?
不能,这个响应头的作用范围是资源本身,也就是那个 URL,而不是响应正文里的某个字段。对于一个字段、一个参数或一个枚举值即将消失,而端点本身继续保留的情况,应该改用 Deprecation 响应头和一条体验日志条目;API 停用讲的正是如何宣布这一类变更。
如果下线日期需要推迟怎么办? 更新响应头的值,并且在最初宣布它的那条体验日志条目里说明原因;悄无声息地改动一个已经公布的日期,正是调用方会觉得你所有的日期都不可信的原因。RFC 把这个值定义成一个提示,正是因为日期有时候确实会变动,但一个没有解释的改动日期,也会让下一次的日期同样失去可信度。
本文的技术内容未经独立审核。如有错误,请告诉我们,我们会更正。