この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。RFC 9110のRetry-After仕様とPython公式ドキュメントを確認し、ネットワークに接続しない検査プログラムを初回作成時のPython 3.13.5と今回の修復確認時のPython 3.12.14で実行しました。対象はヘッダー値の解析であり、実際のHTTP再試行処理は行っていません。
検証ステータス:ローカルPython実行確認済み(同一教材の単体テスト16件をPython 3.13.5と3.12.14で成功)/外部API・各OS実機未確認/同一実行内の5役セルフQA完了
APIから「しばらく待ってください」と返されたとき、どれだけ待つべきかを数字で確認できると障害調査が進めやすくなります。ところがRetry-Afterには、単なる秒数とGMTの日時という異なる書き方があります。120は比較的分かりやすくても、Sat, 24 Oct 2026 00:02:00 GMTを「今から何秒か」に変えるには現在時刻とタイムゾーンを意識する必要があります。この記事では、HTTPリクエストを送らずに文字列だけを解析する小さなPythonプログラムを使い、違いを観察します。
まず動かす:同じ120秒を2つの書き方で表す
Python 3.10以降を用意し、GitHubの教材版からretry_after.pyを取得して同じフォルダーで実行します。追加ライブラリやAPIキーは不要です。比較条件を固定するため、--nowには日時とUTCオフセットを含む文字列を渡します。
python retry_after.py 120 --now 2026-10-24T00:00:00+00:00
作成時のローカル実行では次の出力を確認しました。
{"kind": "delay-seconds", "wait_seconds": 120, "target_utc": "2026-10-24T00:02:00Z", "action": "inspect_only_no_sleep_no_request"}
続いて、値だけをHTTP-date形式へ変更します。
python retry_after.py 'Sat, 24 Oct 2026 00:02:00 GMT' --now 2026-10-24T00:00:00+00:00
こちらも実測のwait_secondsは120でした。ただしkindはhttp-dateです。前者は「受信した時点から120秒」、後者は「UTCの指定時刻まで」と、意味の起点が異なります。固定時刻を使うと、ネットワーク遅延や実行時刻の違いを除いて比較できます。実際の通信環境では受信時刻の取り方も設計上の論点になります。
なぜ2種類あるのか:RFCの規定を読む
RFC 9110 §10.2.3はRetry-Afterの値としてdelay-secondsまたはHTTP-dateを定義しています。前者は0以上の10進整数であり、負数や小数をそのまま待機秒数として受理する形式ではありません。後者は日時です。503応答ではサービスが利用可能になるまでの目安、3xx応答ではリダイレクト要求前の待機時間として使われます。
ここで重要なのは、HTTPステータスが429だったという事実だけで、このヘッダーが必ず存在すると決めつけないことです。ヘッダーがない場合のバックオフ方針はAPI側の利用条件やクライアント設計に依存します。この教材はヘッダー値を入力した場合の解析だけを担当し、429の発生条件やサーバーが指定する再試行ポリシー全体を実装するものではありません。
HTTP-dateには複数の許容表記があります。RFC 9110 §5.6.7には現在一般的なIMF-fixdateのほか、互換性のための古い日時形式もあります。RFCは送信側にIMF-fixdateを使うよう求める一方、受信側には互換性のため3種類すべてのHTTP-dateを受理するよう求めています。今回のコードは学習用としてIMF-fixdateだけを受理し、旧形式を推測変換しません。したがって、RFC準拠の汎用HTTPクライアントを置き換える実装ではありません。実運用へ流用するなら、obsolete形式を受理するテストを追加するか、利用するHTTPライブラリの仕様に従ってください。ここでは「秒数と現在形式の日付を固定時刻で比較する」という観察範囲を優先した、意図的な制限です。
コードの要点:日時と秒数を分岐させる
教材のparse_retry_after関数は、まず文字列が数字だけで構成されているかを確認します。数字なら整数に変換して現在時刻へ加算します。日時形式なら構文を検査したうえで、Python標準ライブラリのemail.utils.parsedate_to_datetimeを利用します。この関数は日時文字列をdatetimeへ変換するためのもので、仕様はPython公式ドキュメントにあります。
from datetime import datetime, timezone
from retry_after import parse_retry_after
now = datetime(2026, 10, 24, 0, 0, tzinfo=timezone.utc)
seconds, target, kind = parse_retry_after("120", now)
print(seconds, kind)
この短いコードの実測出力は120 delay-secondsです。関数は解析結果を返すだけで、sleepやHTTP通信を呼びません。学習時にAPIへ大量の再試行を送ってしまう心配を避けるため、観察する機能と実際に待つ機能を分離しています。プログラムを業務へ組み込む場合も、この分離は単体テストの作りやすさにつながります。
日時形式では曜日とカレンダー上の日付の一致も確認します。一般的な日時パーサーに任せるだけでは、曜日の誤りを黙って受け入れる可能性があるためです。また、nowにタイムゾーンがない場合はエラーにします。2026-10-24 09:00だけでは、UTCなのか日本時間なのか判断できず、待機秒数が大きく変わるからです。
1か所変えてみる:過去日時と端数秒
先ほどの日時を1秒前のFri, 23 Oct 2026 23:59:59 GMTへ変えてみます。基準時刻は同じ2026-10-24T00:00:00+00:00です。教材では待機時間を0秒として返します。過去日時を負の待機時間として扱わない設計です。ただし「0秒だから無条件で再送してよい」という意味ではありません。最大試行回数やAPI側の利用条件は別途守る必要があります。
一方、基準時刻に小数秒が含まれ、指定日時まで0.999999秒しか残っていない場合、待機秒数は1秒へ切り上げます。整数に切り捨てると指定日時より早く再試行する可能性があるためです。実際のクライアントでは、計測時計の種類、HTTP応答の受信タイミング、ネットワーク遅延も考慮する必要があります。ここでの計算はヘッダー解析の教材であり、分散システムの時刻同期問題をすべて解決するものではありません。
エラーを見分ける:黙って補正しない
Retry-After: -1やRetry-After: 1.5はdelay-secondsとして不正です。CLIではINVALID_RETRY_AFTERを標準エラーに表示し、終了コード2で終わります。曜日と日付が食い違う日時、存在しない日付、タイムゾーン情報のない--nowも受理しません。失敗したときに「とりあえず1秒待つ」と自動補正しないのは、運用時に不正なサーバー応答と正常な応答を区別できるようにするためです。
コマンドが起動しない場合は、Pythonのインストール、作業フォルダー、ファイル名を確認します。Windows環境でpythonが使えない場合はpy -3での起動を検討してください。日付の解釈が想定と異なるときは、まず--nowのオフセットを確認し、HTTP-date側がGMTであることを確かめます。旧形式の日時を入力して拒否された場合は、この教材の対象外です。勝手に文字列を変更せず、受け取った値を保持して対応方針を決めます。
仕事で使うなら:解析と再試行を混同しない
API運用では、障害時に「サーバーが何を返したか」と「クライアントが何をしたか」を分けて記録することが大切です。ヘッダーの文字列、HTTPステータス、受信時刻、解析結果、実際の次回要求時刻を別々に保持すれば、必要以上に短い間隔で再送していないかを後から確認できます。ただし認証トークン、個人情報、機密のURLやレスポンス本文をログへ不用意に残してはいけません。
実際の再試行処理には、タイムアウト、最大試行回数、指数バックオフ、冪等性、ネットワーク障害とサーバー応答の区別が必要です。たとえば更新系APIを同じ内容で再送したとき、処理が二重に実行される可能性を考慮しなければなりません。この教材の出力は待機時間の観察結果であって、再送可否を判断する完全なルールではありません。
初回作成時はPython 3.13.5、今回の修復確認時はPython 3.12.14のローカル環境で同一コミットの教材を実行し、どちらも単体テスト16件、構文検査、秒数形式と日時形式のCLI出力を確認しました。Codex CLI、実API接続、Windows/macOSの実機試験、別AIによる独立審査は実施していません。同一実行内の5役セルフQAは、別providerによる独立審査ではありません。追加の互換性を必要とする場合は、RFCの旧形式を受理するかどうかを先に仕様として決め、その形式ごとのテストを追加するのが安全です。
