APIのサーバー側でタイムアウトを適切に判定するには、API全体のタイムアウト管理と個別の処理(外部API・内部処理)のタイムアウト管理を適切に設計する必要があります。本記事では、タイムアウト判定を行う場所と方法について詳しく解説します。
1. タイムアウト判定を行う場所
① API全体のタイムアウト管理
判定するタイミング:
- 処理の開始時に 開始時間を記録(
$startTime = microtime(true);
) - 各処理の前に 残り時間を計算(
getRemainingTime()
で確認) - 残り時間が 0 以下 になったら 即エラーを返す
② 外部API呼び出し時(リトライあり)
判定するタイミング:
- 外部APIのレスポンスが 指定時間を超えた場合
curl_setopt($ch, CURLOPT_TIMEOUT, min(getRemainingTime(), 5));
を設定し、API全体のタイムアウトを考慮- リトライ時に
getRemainingTime()
を確認
phpコピーする編集するif (getRemainingTime() <= 0) {
throw new TimeoutException("External API timeout.", "external_api");
}
③ 内部処理(データベース処理やロジック処理)(リトライなし)
判定するタイミング:
- 処理開始前に残り時間を確認
phpコピーする編集するif (getRemainingTime() <= 0) {
throw new TimeoutException("Internal processing timeout.", "internal_process");
}
- データベース接続時に
PDO::ATTR_TIMEOUT
を設定
phpコピーする編集するPDO::ATTR_TIMEOUT => min(getRemainingTime(), 5);
2. タイムアウト判定の実装
以下のコードでは、各処理の前に getRemainingTime()
を呼び出し、残り時間が 0 以下なら即エラーを返します。
API全体の処理
phpコピーする編集するheader("Content-Type: application/json");
define("TOTAL_TIMEOUT", 10); // API全体のタイムアウト(秒)
define("EXTERNAL_API_MAX_RETRIES", 3); // 外部APIの最大リトライ回数
$startTime = microtime(true); // 開始時間
set_time_limit(TOTAL_TIMEOUT); // PHPスクリプトの最大実行時間を設定
try {
// 外部APIの呼び出し(リトライあり & タイムアウト監視)
$externalData = callExternalApiWithRetry("https://api.example.com/data", EXTERNAL_API_MAX_RETRIES);
// 内部処理の実行(リトライなし & タイムアウト監視)
$internalData = executeInternalProcessing();
// 全体のレスポンスを返す
response(200, ["external" => $externalData, "internal" => $internalData]);
} catch (TimeoutException $e) {
response(504, [
"status" => "error",
"message" => "Gateway Timeout: " . $e->getMessage(),
"error_source" => $e->getSource()
]);
} catch (Exception $e) {
response(500, [
"status" => "error",
"message" => "Internal Server Error: " . $e->getMessage(),
"error_source" => "unknown"
]);
}
外部API呼び出し時(リトライあり & タイムアウト監視)
phpコピーする編集するfunction callExternalApiWithRetry($url, $maxRetries)
{
global $startTime;
$attempt = 0;
while ($attempt < $maxRetries) {
$attempt++;
try {
// API全体の残り時間をチェック
if (getRemainingTime() <= 0) {
throw new TimeoutException("API request exceeded total timeout.", "external_api");
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, min(getRemainingTime(), 5)); // 外部APIのタイムアウト
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && $response) {
return json_decode($response, true);
}
throw new Exception("HTTP $httpCode - API response error");
} catch (Exception $e) {
if ($attempt >= $maxRetries) {
throw new TimeoutException("External API failed after retries.", "external_api");
}
sleep(1); // 1秒待機後に再試行
}
}
}
内部処理(リトライなし & タイムアウト監視)
phpコピーする編集するfunction executeInternalProcessing()
{
// API全体の残り時間をチェック
if (getRemainingTime() <= 0) {
throw new TimeoutException("Internal processing exceeded total timeout.", "internal_process");
}
try {
$pdo = new PDO("mysql:host=localhost;dbname=testdb", "user", "password", [
PDO::ATTR_TIMEOUT => min(getRemainingTime(), 5),
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$stmt = $pdo->prepare("SELECT * FROM users WHERE active = 1");
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
throw new TimeoutException("Database Error: " . $e->getMessage(), "internal_process");
}
}
タイムアウト計測
phpコピーする編集するfunction getRemainingTime()
{
global $startTime;
return max(0, TOTAL_TIMEOUT - (microtime(true) - $startTime));
}
タイムアウト発生時のエラーハンドリング
phpコピーする編集するclass TimeoutException extends Exception
{
private $source;
public function __construct($message, $source)
{
parent::__construct($message);
$this->source = $source;
}
public function getSource()
{
return $this->source;
}
}
3. タイムアウト判定のまとめ
判定する処理 | どこで判定する? | どのように判定する? | エラーメッセージ |
---|---|---|---|
API全体のタイムアウト | 各処理の開始前 | getRemainingTime() <= 0 | "API request exceeded total timeout." |
外部APIのレスポンスが遅い | cURLオプション | CURLOPT_TIMEOUT = min(getRemainingTime(), 5) | "External API failed after retries." |
内部処理(DB処理含む)が遅い | 処理開始前 & DB接続 | PDO::ATTR_TIMEOUT = min(getRemainingTime(), 5) | "Internal processing exceeded total timeout." |
4. まとめ
- API全体のタイムアウト (
TOTAL_TIMEOUT
) を統一し、各処理がAPI全体のタイムアウトを超えないようにする getRemainingTime()
を使い、各処理の前に残り時間を確認し、超えていたら即エラーを返す- 外部APIのタイムアウトは
CURLOPT_TIMEOUT
で設定し、最大リトライ回数 (EXTERNAL_API_MAX_RETRIES
) を制御 - 内部処理は
PDO::ATTR_TIMEOUT
を使い、リトライなしで処理 - タイムアウト発生時のエラーメッセージに
error_source
を追加し、どこで発生したかを明確にする
この実装により、クライアント側から見ても統一されたレスポンスになりつつ、内部でのタイムアウト発生場所も特定できるようになります。
コメントを残す