HTTP Request Executor
関連ドキュメント
- Mapping Functions 開発ガイド - リクエスト/レスポンスのデータ変換(19個のFunction)
- 外部サービス連携ガイド - 完全な実装例
このドキュメントでは、idp-server における HTTP Request Executor システムについて説明します。
これは、外部サービスとの HTTP 通信において、堅牢な再試行メカニズム、包括的なエラーハンドリング、および**動的なデータマッピング(Mapping Functions)**を提供することを目的としています。
🎯 目的
- 外部 API との通信における一時的なネットワーク障害やサーバーエラーに対する自動再試行
- RFC 7231 準拠の Retry-After ヘッダーサポート
- 499 レスポンスの動的再試行制御
- OAuth 2.0 認証との統合
- 設定ベースの柔軟な再試行ポリシー
- リクエスト/レスポンスのデータ変換(Mapping Functions統合)
🔽 図:HTTP Request Executor の全体像
📚 主要コンポーネント
HttpRequestExecutor
HTTP リクエストの実行と再試行を管理するメインクラスです。
主要メソッド
// 設定ベースの実行(自動再試行サポート)
public HttpRequestResult execute(
HttpRequestExecutionConfigInterface configuration,
HttpRequestBaseParams params
)
// 明示的な再試行実行
public HttpRequestResult executeWithRetry(
HttpRequest request,
HttpRetryConfiguration retryConfig
)
// 単純実行(再試行なし)
public HttpRequestResult execute(HttpRequest request)
HttpRetryConfiguration
再試行の詳細設定を管理するクラスです。
設定項目
public class HttpRetryConfiguration implements JsonReadable {
private int maxRetries = 0; // 最大再試行回数
private Duration[] backoffDelays = new Duration[0]; // バックオフ遅延
private Set<Integer> retryableStatusCodes = Set.of(); // 再試行可能ステータスコード
private boolean idempotencyRequired = false; // 冪等性要求
private String strategy = "EXPONENTIAL_BACKOFF"; // 再試行戦略
}
デフォルト設定
// デフォルト再試行設定
HttpRetryConfiguration defaultConfig = HttpRetryConfiguration.defaultRetry();
// - 最大再試行: 3回
// - バックオフ: 1秒 → 5秒 → 30秒
// - 再試行可能ステータス: 408, 429, 500, 502, 503, 504
// - 冪等性要求: false
// 再試行無効化
HttpRetryConfiguration noRetry = HttpRetryConfiguration.noRetry();
🔧 実装クラス
以下のクラスが HttpRequestExecutionConfigInterface を実装し、JSON からの自動マッピングをサポートしています:
1. HttpRequestExecutionConfig
基本的な HTTP リクエスト設定クラス
2. SecurityEventHttpRequestConfig
セキュリティイベント用 HTTP リクエスト設定
3. IdentityVerificationHttpRequestConfig
身元確認用 HTTP リクエスト設定
4. AdditionalParameterHttpRequestConfig
追加パラメータ用 HTTP リクエスト設定
すべてのクラスは @JsonIgnoreProperties(ignoreUnknown = true) と JsonReadable を実装し、
JsonConverter による自動マッピングをサポートしています。
📖 使用例
基本的な使用方法
// 1. 設定ベースの実行(推奨)
@Autowired
private HttpRequestExecutor executor;
public void callExternalApi() {
// JSON から自動マッピングされた設定
HttpRequestExecutionConfig config = loadConfigFromDatabase();
HttpRequestBaseParams params = HttpRequestBaseParams.builder()
.body("request data")
.build();
HttpRequestResult result = executor.execute(config, params);
if (result.isSuccess()) {
// 成功処理
processResponse(result.body());
} else {
// エラー処理
handleError(result.statusCode(), result.body());
}
}
明示的な再試行設定
public void callWithCustomRetry() {
HttpRetryConfiguration retryConfig = HttpRetryConfiguration.builder()
.maxRetries(5)
.backoffDelays(
Duration.ofSeconds(2),
Duration.ofSeconds(10),
Duration.ofMinutes(1)
)
.retryableStatusCodes(Set.of(500, 502, 503, 504, 408, 429))
.idempotencyRequired(true)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/endpoint"))
.POST(BodyPublishers.ofString("data"))
.build();
HttpRequestResult result = executor.executeWithRetry(request, retryConfig);
}
JSON 設定例
{
"url": "https://api.example.com/webhook",
"method": "POST",
"auth_type": "oauth",
"oauth_authorization": {
"client_id": "your-client-id",
"scope": "api:write"
},
"retry_configuration": {
"max_retries": 3,
"backoff_delays": [1000, 5000, 30000],
"retryable_status_codes": [500, 502, 503, 504, 408, 429],
"idempotency_required": false,
"strategy": "EXPONENTIAL_BACKOFF"
}
}
🔐 OAuth 2.0 認証統合
HttpRequestExecutor は OAuth 2.0 認証を透過的にサポートしています。
OAuth 設定
{
"url": "https://api.example.com/webhook",
"method": "POST",
"auth_type": "oauth",
"oauth_authorization": {
"type": "client_credentials",
"token_endpoint": "https://auth.example.com/token",
"client_authentication_type": "client_secret_basic",
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"scope": "api:write webhooks:send",
"cache_enabled": true,
"cache_buffer_seconds": 30,
"cache_ttl_seconds": 3600
}
}
キャッシュ設定
cache_enabled: トークンキャッシュの有効/無効cache_buffer_seconds: トークン期限前のバッファ時間(秒)cache_ttl_seconds: キャッシュのデフォルトTTL(秒)
キャッシュキー生成
キャッシュキーは以下の形式で自動生成されます:
oauth_token:type={grant_type}:client={client_id}:scope={scope}:user={username}:endpoint={token_endpoint}
例:
oauth_token:type=client_credentials:client=my_client_id:scope=api_write:endpoint=https://auth.example.com/token
oauth_token:type=password:client=app_client:scope=read_write:user=john_doe:endpoint=https://auth.example.com/token
- 特殊文字は
_に置換 - 各要素は50文字以内に制限
- デバッグ・ログ解析が容易な人間可読形式
自動トークン管理
// OAuth設定があるリクエストでは OAuthAuthorizationResolvers が自動的にトークンを解決します
HttpRequestExecutionConfig config = loadConfigWithOAuth();
HttpRequestResult result = executor.execute(config, params);
// 内部処理:
// 1. OAuthAuthorizationResolvers がトークンを解決
// 2. Authorization ヘッダーに Bearer トークン設定
// 3. リクエスト実行
✅ 最適化済み: OAuth認証とリトライ機能は効率的に統合されており、トークン解決の重複実行はありません。
401/403 エラー時の自動リトライ
外部サービスから 401 Unauthorized または 403 Forbidden が返却された場合、HttpRequestExecutor は自動的にキャッシュされたトークンを無効化し、新しいトークンを取得してリトライします。
処理フロー(詳細)
┌─────────────────────────────────────────────────────────────────────────────┐
│ execute(config, params) │
│ │ │
│ ▼ │
│ executeWithOAuthRetry(config, params, isRetry=false) │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. HttpRequestBuilder.build(config, params) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ - URL/Header/Body のマッピング │ │
│ │ - auth_type == "oauth2" の場合: │ │
│ │ OAuthAuthorizationResolver.resolve() でトークン取得 │ │
│ │ → Authorization: Bearer {token} ヘッダー追加 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. HTTPリクエスト実行 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ hasRetryConfiguration? │ │
│ │ YES → executeWithRetryAndCriteria() (指数バックオフリトライ) │ │
│ │ NO → executeWithCriteria() (単発実行) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. レスポンス評価 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ auth_type == "oauth2" │ │
│ │ AND (statusCode == 401 OR statusCode == 403) │ │
│ │ AND isRetry == false │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
┌──────────────┴──────────────┐
│ │
YES NO
│ │
▼ ▼
┌───────────────────────────────────┐ ┌─────────────────────┐
│ 4. トークンリフレッシュ & リトライ │ │ 結果を返却 │
│ ┌─────────────────────────────┐ │ │ │
│ │ a. ログ出力: │ │ │ return result; │
│ │ "Received 401/403, │ │ │ │
│ │ invalidating cached │ │ └─────────────────────┘
│ │ token and retrying" │ │
│ │ │ │
│ │ b. resolver.invalidateCache()│ │
│ │ → キャッシュからトークン削除│ │
│ │ → ログ: "Invalidated │ │
│ │ cached access token" │ │
│ │ │ │
│ │ c. executeWithOAuthRetry() │ │
│ │ (isRetry=true で再帰) │ │
│ └─────────────────────────────┘ │
└───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. リト ライ実行 (isRetry=true) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ - 新しいトークンを取得 (キャッシュにないため) │ │
│ │ - HTTPリクエスト再実行 │ │
│ │ - 401/403でも isRetry=true なのでリトライしない (無限ループ防止) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────┐
│ 結果を返却 │
└───────────┘
シーケンス図
┌────────┐ ┌──────────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │ │HttpRequestExecutor│ │HttpRequestBuilder│ │OAuthResolver │ │External API │
└───┬────┘ └────────┬─────────┘ └────────┬────────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │ │
│ execute(config) │ │ │ │
│──────────────────>│ │ │ │
│ │ │ │ │
│ │ build(config) │ │ │
│ │───────────────────────>│ │ │
│ │ │ │ │
│ │ │ resolve(oAuthConfig)│ │
│ │ │────────────────────>│ │
│ │ │ │ │
│ │ │ access_token │ │
│ │ │<────────────────────│ │
│ │ │ │ │
│ │ HttpRequest │ │ │
│ │<───────────────────────│ │ │
│ │ │ │ │
│ │ POST /api (Bearer token) │ │
│ │────────────────────────────────────────────── ───────────────────>│
│ │ │ │ │
│ │ │ │ 401 Unauthorized
│ │<─────────────────────────────────────────────────────────────────│
│ │ │ │ │
│ │ invalidateCache() │ │ │
│ │─────────────────────────────────────────────>│ │
│ │ │ │ │
│ │ build(config) [RETRY] │ │ │
│ │───────────────────────>│ │ │
│ │ │ │ │
│ │ │ resolve(oAuthConfig)│ │
│ │ │────────────────────>│ │
│ │ │ │ │
│ │ │ NEW access_token │ │
│ │ │<────────────────────│ │
│ │ │ │ │
│ │ HttpRequest │ │ │
│ │<───────────────────────│ │ │
│ │ │ │ │
│ │ POST /api (NEW Bearer token) │ │
│ │─────────────────────────────────────────────────────────────────>│
│ │ │ │ │
│ │ │ │ 200 OK │
│ │<─────────────────────────────────────────────────────────────────│
│ │ │ │ │
│ result │ │ │ │
│<──────────────────│ │ │ │
│ │ │ │ │
ログ出力
リトライ時には以下のログが出力されます(運用時の確認に有用):
INFO HttpRequestExecutor - Received 401 Unauthorized, invalidating cached token and retrying: uri=https://api.example.com/endpoint
INFO SmartCachedOAuthAuthorizationResolver - Invalidated cached access token for key: oauth_token:type=password:client=my-client:scope=api_access:endpoint=https://auth.example.com/token
設計ポイント
| ポイント | 説明 |
|---|---|
| リトライは1回のみ | 無限ループ防止。リトライ後も401/403なら最終結果として返却 |
| キャッシュ無効化 | 期限切れや失効したトークンをキャッシュから削除 |
| 新トークン取得 | キャッシュミスにより、OAuthサーバーから新規取得 |
| 対象エラー | 401(認証失敗)と403(認可失敗)の両方に対応 |
関連クラス
| クラス | 責務 |
|---|---|
HttpRequestExecutor | HTTPリクエスト実行、401/403リトライ制御 |
HttpRequestBuilder | 設定からHttpRequestを構築、OAuthトークン付与 |
OAuthAuthorizationResolver | OAuthトークン取得・キャッシュ管理 |
SmartCachedOAuthAuthorizationResolver | トークンキャッシュ、TTL管理、キャッシュ無効化 |
CachedAccessToken | キャッシュされたトークン、有効期限判定 |
HttpRetryStrategy | 指数バックオフリトライ(ネットワークエラー用) |
関連Issue
- Issue #1139: キャッシュTTL個別設定
- Issue #1143: 401/403エラー時のキャッシュクリア&リトライ機能
トークンキャッシュの有効期限管理
TTL(Time To Live)の決定
トークンのキャッシュTTLは、OAuthサーバーから返却される expires_in を使用して動的に設定されます:
// OAuthサーバーからのレスポンス
{
"access_token": "eyJhbGciOiJSUzI1...",
"expires_in": 1800, // 30分
"token_type": "Bearer"
}
// キャッシュ保存時
cacheStore.put(cacheKey, cachedToken, expiresIn); // TTL = 1800秒
バッファ時間(Buffer Seconds)
トークン期限切れ直前のリクエスト失敗を防ぐため、バッファ時間を設定できます:
|←────────── expires_in (1800秒) ──────────→|
| |←─buffer─→|
├──────────────────────────────┼───────────┤
取得時刻 ↑ 有効期限
ここでisValid()=false
(リクエスト前に新トークン取得)
// CachedAccessToken.isValid()
public boolean isValid() {
long bufferTime = bufferSeconds * 1000L;
return currentTime < (expirationTimestamp - bufferTime);
}