Retry-After 告诉客户端「过多久再来」,有两种写法:秒数,或者一个 HTTP 日期。
它配 503 和 429 出现,没有它客户端只能瞎猜。
上一节是「你不够格」,这一节是「你来得不是时候」。前者要凭据,后者只要耐心。
一、两种写法,都要会认
第一种是纯数字,单位秒:Retry-After: 120。
第二种是 HTTP 日期:Retry-After: Sun, 25 Oct 2026 06:35:47 GMT。
判断方法很简单:头一个字符是数字就按秒算,否则按日期解析。
Demo 里把「现在 + 0/+3/+60 秒」各生成一个日期串,可以看出它和秒数是同一件事的两种表达。
只支持一种的服务器,遇上只认另一种的客户端就白说了。实际用到秒数的场合更多,因为不用管时钟对不对。
二、它配哪些状态码
最典型的是 503 Service Unavailable:服务器暂时扛不住,但过一阵能好。
次典型的是 429 Too Many Requests:限流了,等一会儿再试。
严格说 Retry-After 也可以出现在 301/302 里,意思是「别急着立刻跟过来」。但那种用法很少见。
注意它和 503 的关系是必须,不是可选。
一个光秃秃的 503,客户端只能按自己的经验退避——猜短了把刚缓过来的服务器又打趴,猜长了就是白等。
三、客户端该怎么做
照做就行,但要有上限:单次等待封顶、总重试次数封顶、并且只对幂等请求重试。
POST 盲目重试可能建出两条订单。
Demo 里服务端前两次回 503 带 Retry-After: 1,第三次给 200。
客户端老老实实睡 1 秒,累计耗时 0.00 → 1.03 → 2.03 秒,节奏完全是被那三个数字牵着走的。
反过来说,如果你在写服务端,别为了「显得友好」乱发一个很大的值。
客户端可能真会等那么久,那期间你的服务对它就是不存在的。
四、动手 Demo:两次 503,一次 200
服务端前两次回 503 并带上 Retry-After: 1,第三次才给内容;
客户端每收到一次就按那个数字睡,记录累计耗时。然后单独演示日期写法的换算。
def fn(raw, st):
st[u'n'] = st.get(u'n', 0) + 1
if st[u'n'] < 3:
return X.reply(u'HTTP/1.1 503 Service Unavailable',
[(u'Retry-After', u'1')], b'busy')
return X.reply(u'HTTP/1.1 200 OK', [], TEXT)
for i in range(3):
r = X.ask(p, X.req(u'GET', u'/s', u'127.0.0.1'))
ra = [ln for ln in X.head_lines(r) if ln.lower().startswith(u'retry-after:')]
if ra:
time.sleep(float(ra[0].split(u':')[1])) # 照它说的睡
实跑输出:
--------------------------------------------------------------------
Retry-After:多久之后再来
--------------------------------------------------------------------
服务端前两次说「1 秒后再来」,第三次才给内容。客户端老实照做:
第 1 次 HTTP/1.1 503 Service Unavailable Retry-After: 1 累计 0.00 秒
第 2 次 HTTP/1.1 503 Service Unavailable Retry-After: 1 累计 1.02 秒
第 3 次 HTTP/1.1 200 OK 累计 2.04 秒
Retry-After 也能写成 HTTP 日期,这时候要看的是「什么时候」,不是「多久」:
现在 + 0 秒 → Sun, 25 Oct 2026 06:37:56 GMT
现在 + 3 秒 → Sun, 25 Oct 2026 06:37:59 GMT
现在 + 60 秒 → Sun, 25 Oct 2026 06:38:56 GMT
两种写法客户端都要能认:纯数字按秒算,带字母的按日期解析再减当前时间。
只写一种的服务器,遇上只认另一种的客户端就白说了。
这条首部配 503 和 429 是必须的。没有它,客户端只能瞎猜——
猜短了是把刚缓过来的服务器又打趴,猜长了是白等。
上面那三次请求累计花了 2.03 秒,正是客户端照着 Retry-After 睡出来的。
三次请求的状态码依次是 503、503、200,累计耗时 0.00、1.03、2.03 秒。 两次等待正好两秒,多的那 0.03 是本机开销。 第一行时间是 0.00 不是 1.00,因为第一次还没睡——是「收到 503 之后才开始等」。 下面那三行日期串是同一次的换算:现在、加 3 秒、加 60 秒。 客户端拿到日期串要先减当前时间,减出负数就按 0 处理。 整个 Demo 里,睡多久完全由服务端那一个数字决定。 这就是它的价值:把「等多久」这个决定权从客户端手里收回来。
30 秒实验
- 把
Retry-After改成 3,看累计耗时会不会变成 6 秒。 - 改成日期串,用
time.mktime解析后相减,看能不能对上秒数。 - 想一想:如果 503 不带 Retry-After,客户端应该按什么策略退避?
常见误区
- 「Retry-After 只能写秒数」——也能写 HTTP 日期。
- 「503 不带 Retry-After 也行」——能跑,但客户端只能瞎猜,容易被重试打垮。
- 「所有请求都该重试」——POST 不幂等,盲目重试会重复建单。
自测三问(点开对答案)
1. `Retry-After` 的两种写法怎么区分? —— 首字符是数字按秒算,否则按 HTTP 日期解析再减当前时间。 2. 它最常配哪两个状态码? —— 503 Service Unavailable 和 429 Too Many Requests。 3. 为什么服务端不该随手写一个很大的值? —— 客户端可能真等那么久,那期间你的服务对它等于不存在。「等会儿再来」是缓和手段。下面三个字段转向自报家门与内容区分:Server、Vary、WWW-Authenticate。
- ☐ (本节完,下一节 6.5.7 Server)