Retry-Afterヘッダー:429・503・413・3xxでの意味の違いとクライアントでの読み方

RFC 9110とRFC 6585をもとに、Retry-Afterの2つの値の形式、ステータスコードごとの意味、HTTP-dateや秒数を待ち時間に変換する方法、不正な値や大きすぎる値の扱いを整理しました。

開発Webネットワーク

Retry-After は、次のリクエストまでどれだけ待つべきかをサーバーがクライアントに伝えるレスポンスヘッダーです。HTTP のセマンティクスを定める RFC 9110 の 10.2.3節で定義されていて、一緒に返るステータスコードによって意味が少しずつ変わります。値の形式も2種類あるので、クライアントは両方を読めなければなりません。

値の2つの形式

Retry-After: Fri, 31 Dec 1999 23:59:59 GMT
Retry-After: 120

文法は Retry-After = HTTP-date / delay-seconds です。1行目は「その時刻まで待つ」、2行目は「レスポンスを受け取ってから120秒、つまり2分待つ」という意味です。

delay-seconds は 1*DIGIT、つまり負でない10進整数です。そのため 1.5、-1、+30、2m はこの文法に合いません。数え始めるのはリクエストを送った時点ではなく、レスポンスを受け取った時点です。

HTTP-date は 5.6.7節の日付形式です。送信側は必ず IMF-fixdate(Sun, 06 Nov 1994 08:49:37 GMT)で生成しなければなりません(MUST)が、受信側は古い2形式を含む3つの形式すべてを受け付けなければなりません(MUST)。

形式 例
IMF-fixdate(推奨) Sun, 06 Nov 1994 08:49:37 GMT
RFC 850 形式(廃止) Sunday, 06-Nov-94 08:49:37 GMT
asctime 形式(廃止) Sun Nov 6 08:49:37 1994

3形式とも UTC です。asctime 形式にはタイムゾーンの表記がありませんが、UTC とみなします。HTTP-date は大文字と小文字を区別します。2桁の年を使う RFC 850 形式では、50年以上先に見える日付を、下2桁が同じ直近の過去の年として解釈しなければなりません。また、ヘッダー値の前後の空白は値に含まれないので、パーサーは値を評価する前にそれを取り除く必要があります(5.5節)。

ステータスコードごとの意味

ステータス 根拠 Retry-After が示すもの 要求レベル
503 Service Unavailable RFC 9110 10.2.3、15.6.4節 サービスを利用できないと見込まれる期間 送ってよい(MAY)
3xx リダイレクト RFC 9110 10.2.3節 リダイレクト先へのリクエストを送る前に待つよう求める最短時間 規定なし
413 Content Too Large RFC 9110 15.5.14節 状態が一時的であること、いつから再試行してよいか 一時的なら送るべき(SHOULD)
429 Too Many Requests RFC 6585 4節 新しいリクエストを送る前に待つ時間 送ってよい(MAY)

表だけでは伝わらない点もあります。

503。 仕様は 503 を、一時的な過負荷や予定されたメンテナンスのために今はリクエストを処理できない状態と定義しています。ただし、過負荷のサーバーが必ず 503 を返すわけではなく、単に接続を拒否するサーバーもあると注記しています。503 に Retry-After がないからといって、すぐに回復するという意味にもなりません。

3xx。 10.2.3節は、あらゆる 3xx レスポンスについて、Retry-After をリダイレクトされたリクエストを送る前に待つよう求める最短時間と定義しています。「この時間が過ぎたら送れ」ではなく、「少なくともこれだけは待ってほしい」という依頼です。ただし、すべての 3xx にたどるべき Location があるわけではありません。15.4節は、301、302、303、307、308 のように Location で別の URI を示すリダイレクトと、選択肢を提示する 300、保存済みの結果を使わせる 304 を区別しています。レスポンスに Location があり、クライアントがその先へリダイレクトされたリクエストを送る場合は、この最短の待ち時間がそのリクエストにかかります。リダイレクトのステータスコードそれぞれの違いは301・302・307・308リダイレクトの違いで扱いました。

413。 リクエストのコンテンツが大きすぎて処理しない、というレスポンスです。仕様は、状態が一時的であればサーバーは Retry-After を送り、一時的であることと、いつから再試行してよいかを示すべきだ(SHOULD)と定めています。そのため、Retry-After 付きの 413 は、サーバーが一時的な状態だと知らせているものと読めます。逆は成り立ちません。Retry-After がないからといって、上限が恒久的だということにはなりません。SHOULD は MUST ではないので、一時的な状況でもヘッダーなしで 413 を返すサーバーはありえます。このヘッダーだけでは状態が一時的かどうかを判断できないので、再送するかどうかはレスポンスに含まれるほかの情報やアプリケーションのポリシーをもとに決める必要があります。

429。 RFC 6585 は、429 レスポンスには状況を説明する内容を含めるべきで(SHOULD)、Retry-After は含めてよい(MAY)と定めています。サーバーがユーザーをどう識別し、リクエストをどう数えるかは定義していません。リソースごとに数えることも、サーバー全体や複数のサーバーにまたがって数えることもありえます。そのため、待つべき範囲がその URL だけなのかホスト全体なのかは、ヘッダーだけではわかりません。429 レスポンスはキャッシュに保存してはいけません(MUST NOT)。

10.2.3節の定義そのものは特定のステータスコードに縛られていません。「後続のリクエストの前にユーザーエージェントが待つべき時間」という一般的な定義があり、503 と 3xx のときの意味が別に書かれている構成です。IANA の HTTP フィールド名レジストリでは、Retry-After のステータスは permanent、参照先は RFC 9110 10.2.3節です。

待ち時間への変換

以下は仕様のルールをもとにした実装例です。上限値と Date ヘッダーの使い方は仕様が定めたものではなく、この記事の提案です。このコードはそのままでは動かない部分的な疑似コードです。仕様に沿った HTTP-date パーサーはあえて省いていて、parseHttpDate はその場所を示す名前にすぎません。

// プレースホルダー: IMF-fixdate、RFC 850、asctime の3形式を扱う
// HTTP-date パーサーが入る場所。この記事では実装しない。
declare function parseHttpDate(value: string): number | null;

// 待つミリ秒を返す。解釈できない値なら null。
function retryAfterMs(
  value: string,
  receivedAt: number,        // レスポンスを受け取った時刻(ms、ローカルの時計)
  serverDate: number | null, // 同じレスポンスの Date ヘッダー(ms)。なければ null
  maxMs: number,             // このクライアントが決めた上限
): number | null {
  const v = value.trim();
  if (/^[0-9]+$/.test(v)) {
    return Math.min(Number(v) * 1000, maxMs);
  }
  const at = parseHttpDate(v); // プレースホルダー(上を参照)
  if (at === null) return null;
  const base = serverDate ?? receivedAt;
  return Math.min(Math.max(at - base, 0), maxMs);
}

このコードでの判断は次のとおりです。

  • 秒数はレスポンスを受け取った時刻から数えます。 仕様の文言が「レスポンスを受け取ってから」なので、リクエストを送った時刻を起点にすると往復時間の分だけ早く再送してしまいます。
  • 日付はサーバーの時計での時刻です。 クライアントの時計がずれていれば、日付 - 現在時刻 も同じだけずれます。そこでこの例では、同じレスポンスに Date ヘッダーがあれば Retry-After の時刻 - Date だけ待ちます。これは仕様に書かれた手順ではなくこの記事の提案で、時計のずれを避けるための控えめな代替手段にすぎません。RFC 9110 6.6.1節では Date はメッセージが生成された時刻を表しますが、クライアントが待ち始めるのはレスポンスを受け取ってからです。そのためこの計算では、レスポンスが生成されてからクライアントに届くまでの時間の分だけ長く待つことになり、キャッシュなどの中継者が保存済みのレスポンスを返した場合は、そのレスポンスが保存されていた時間の分だけさらに長く待つこともありえます。また同じ節によると、時計を持つオリジンサーバーは 2xx、3xx、4xx のレスポンスには Date を必ず付けなければなりませんが、5xx では付けなくてもかまいません。503 には Date がないこともあり、その場合この例はローカルの時計で測った受信時刻を基準にするので、先に述べた時計のずれをそのまま抱えます。
  • すでに過ぎた時刻なら0とみなします。 仕様に決まりはありません。それ以上待つ理由がない、という判断です。
  • 上限を設けます。 1*DIGIT には桁数の制限がないので、非常に大きな値も文法上は正しい値です。そのまま整数型に変換するとオーバーフローするおそれがあり、誤って入った値のせいでクライアントが何日も止まることもありえます。上限をいくつにするかはサービスによります。
  • 解釈できない値はないものとして扱います。 1.5 や形式の崩れた日付をどう扱うか、10.2.3節は何も述べていません。この例では null を返し、呼び出し側に独自のバックオフ規則を使わせています。

待ったあとに再送してよいかは別の話

Retry-After が伝えるのはいつ再送するかだけで、そのリクエストを再送しても安全かどうかは示しません。RFC 9110 9.2.2節は、冪等でないメソッドのリクエストについて、実際には冪等だとわかっているか、元のリクエストが適用されなかったと確かめる手段がない限り、自動で再送すべきでない(SHOULD NOT)と定めています。503 や 429 に Retry-After が付いてきても、この規則は緩みません。詳しくはHTTPメソッドの安全性と冪等性にまとめました。

10.2.3節の表現も「待つべき(ought to)」で、MUST のような要求レベルのキーワードは使っていません。サーバーが送るのは助言であり、従うかどうかはクライアント次第です。3xx のときの表現はもう少し具体的で、ユーザーエージェントに待つよう「求める」最短時間と書かれています。

サーバーから送るとき

以下は仕様のルールから導いた提案です。

  • 日付を使うなら IMF-fixdate で生成しなければなりません(MUST)。ほかの形式は受信側の互換性のためにあります。
  • 秒数はサーバーとクライアントの時計のずれに影響されません。メンテナンス終了時刻のように決まった時刻があるときは、日付のほうが意図をそのまま伝えられます。
  • 多くのクライアントに同じ日付を返すと、それらのクライアントが同じ時刻に一斉に戻ってくるかもしれません。仕様の範囲外ですが、サーバー側で値を少しずつ変える、クライアント側でランダムな遅延を足す、といった方法は検討に値します。
  • 429 では、なぜ制限されたのかを本文で説明すべきです(SHOULD)。Retry-After だけでは制限の範囲を伝えられません。

参考にした文書

リンクと内容は2026年10月14日に確認しました。

ブログへ