fix: enhance client ID retry logic for backend requests and improve error handling

This commit is contained in:
Radek Davidek 2026-07-02 10:07:32 +02:00
parent a3db5082ac
commit d3d6b4d850
2 changed files with 36 additions and 25 deletions

View File

@ -33,12 +33,6 @@ public class ProcessHandler extends BaseContextHandler {
return; return;
} }
int index = clientIdIndex.get() % clientIds.size();
String currentClientId = clientIds.get(index);
TokenPayloadIdp token = tokenService.getToken(currentClientId);
String accessToken = token.getAccessToken();
String backendBaseUrl = appConfig.getBackendUrl(); String backendBaseUrl = appConfig.getBackendUrl();
if (backendBaseUrl == null || backendBaseUrl.isBlank()) { if (backendBaseUrl == null || backendBaseUrl.isBlank()) {
logger.error("backend_url is not configured"); logger.error("backend_url is not configured");
@ -84,34 +78,50 @@ public class ProcessHandler extends BaseContextHandler {
body = exchange.getRequestBody().readAllBytes(); body = exchange.getRequestBody().readAllBytes();
} }
logger.info("Forwarding {} request to backend: {} (Client ID: {})", method, targetUrl, currentClientId); BackendResponse backendResponse = null;
BackendResponse backendResponse = callBackend(targetUrl, method, exchange.getRequestHeaders(), body, accessToken); int maxAttempts = clientIds.size();
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
int index = clientIdIndex.get() % clientIds.size();
if (index < 0) index += clientIds.size();
String currentClientId = clientIds.get(index);
int retryCount = 0; if (attempt > 1) {
int maxRetries = clientIds.size() - 1; logger.info("Retrying request to backend with next Client ID: {} (attempt {}/{})", currentClientId, attempt, maxAttempts);
while (backendResponse.statusCode == 429 && retryCount < maxRetries) {
retryCount++;
int nextIndex = clientIdIndex.incrementAndGet() % clientIds.size();
String retryClientId = clientIds.get(nextIndex);
logger.warn("Backend returned 429. Retrying ({}/{}) with next client ID: {}...",
retryCount, maxRetries, retryClientId);
token = tokenService.getToken(retryClientId);
accessToken = token.getAccessToken();
logger.info("Retrying request to backend with new token (Client ID: {})", retryClientId);
backendResponse = callBackend(targetUrl, method, exchange.getRequestHeaders(), body, accessToken);
} }
if (backendResponse.statusCode == 429) { try {
TokenPayloadIdp token = tokenService.getToken(currentClientId);
String accessToken = token.getAccessToken();
backendResponse = callBackend(targetUrl, method, exchange.getRequestHeaders(), body, accessToken);
if (backendResponse.statusCode != 429) {
break; // Success or fatal backend error, exit loop
}
logger.warn("Backend returned 429 Too Many Requests for Client ID: {}. Switching to next identity.", currentClientId);
} catch (Exception e) {
logger.warn("Failed to process request with Client ID: {}: {}", currentClientId, e.getMessage());
if (attempt == maxAttempts) {
throw e; // Last identity failed, propagate exception
}
// Else, treat as retryable failure and move to next identity
backendResponse = new BackendResponse(429, null, null);
}
clientIdIndex.incrementAndGet();
}
if (backendResponse != null && backendResponse.statusCode == 429) {
logger.error("Throttling limit reached. Exhausted all {} identities and backend still returns 429.", clientIds.size()); logger.error("Throttling limit reached. Exhausted all {} identities and backend still returns 429.", clientIds.size());
sendError(exchange, 429, ERR_TOO_MANY_REQUESTS, sendError(exchange, 429, ERR_TOO_MANY_REQUESTS,
"All configured client identities are currently throttled by the backend after " + retryCount + " retries."); "All configured client identities are currently throttled by the backend after " + maxAttempts + " attempts.");
return; return;
} }
if (backendResponse != null) {
sendResponse(exchange, backendResponse.statusCode, backendResponse.contentType, backendResponse.body); sendResponse(exchange, backendResponse.statusCode, backendResponse.contentType, backendResponse.body);
}
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
sendError(exchange, 400, ERR_INVALID_CONFIG, e.getMessage()); sendError(exchange, 400, ERR_INVALID_CONFIG, e.getMessage());

View File

@ -14,6 +14,7 @@ adfs:
audience: "https://fs.komercpoj.loc/adfs/oauth2/token" audience: "https://fs.komercpoj.loc/adfs/oauth2/token"
resource: "urn:kamma:api" resource: "urn:kamma:api"
clientIds: clientIds:
- "db2edf05-7af2-4523-97f9-39f930afc634a"
- "db2edf05-7af2-4523-97f9-39f930afc634" - "db2edf05-7af2-4523-97f9-39f930afc634"
certificate: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFRENDQWZpZ0F3SUJBZ0lRRjhOQTNUQytVWUpJL1VRNTNTSFVGakFOQmdrcWhraUc5dzBCQVFVRkFEQWIKTVJrd0Z3WURWUVFEREJCUFFYVjBhQ0JEYkdsbGJuUWdTbGRVTUI0WERUSTJNRFl6TURFME1UazBOVm9YRFRJMwpNRFl6TURFME16azBOVm93R3pFWk1CY0dBMVVFQXd3UVQwRjFkR2dnUTJ4cFpXNTBJRXBYVkRDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1UVzFpL0tNUFY1cTl2TXgzTGV4V1lsNFdFTmJ6UDAKQVV5ZjhlNm1GcHdEcE5PWUVXV1RQYnAwUm5HRmY4SGpCaUFwTWFDSFZCbmJqMC81Q3pSWWk0N3lWWEMzaGRiQgpYdDdZRGNCeEduYnN5Zy9LdTB5SFRQRW5YblB5VCtpYXUzS2JqQXgyOGU1OXhzRHlFMzZxaktveGRTbURZT0M4CmpzM0hsMHNGTWNjd3JzZ0JxOFEyYllIVUt1TERORmRveE1DaEhOTHlxOE1sNFdnNmVHdWtMN2p4R0tGNXlzczEKM096emJoZkc5dEpWY290WUt0WDlHUThUckdpREF2UGFOREs2WXMxUTZWbDNKVjY5dmdET1ZSVlJCUzhpWmdLaQpQbTdob1hHb2s3VVEzV2R0c3dpU09ReE1iMWZhVDFLOWp0T3lmTm1vNjgyUkluU1VJSi9sTm9rQ0F3RUFBYU5RCk1FNHdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0QKQVRBZEJnTlZIUTRFRmdRVTlRNUJSVnpRVDVNbG5mY3N6VzFwUHZiZmRud3dEUVlKS29aSWh2Y05BUUVGQlFBRApnZ0VCQUpUMVlIYk03UCtrYi9wSmJ5bENLUUd6OEZ6eGFpeDVyay9MdndJNDVmcVJTQ0NUaERmemUrdTNiK05ZCjcxV2V0Z2Y2cUhubkVrdkRnVXJyWXU1QUhrZE9WWXlDUkJnN0dWKzIrQUtpWExEYUsrV0FnM2U4Zkk2cFNvOUMKeXdvc0lNNFcyaWJWSnhzVWNIZGhTUFBVVlkrVzNSY1B4VlZmWmV4eTZXUXBsVlV0Z3JYQmpYSEdEWE5YQUw3awo4ZUFwSEpDQVByKytabktQc3VzSWFyeVNqRXRrZXR0UHViMGp0MnJlajdqaHJuU1EyRlg4czhqcU43L0lCeWRlCjd2N2YwMlowMjVPV2hZRHozRVVQaWZLOXpmM2Nac2hZRDJ3OE1jdGVUZkZKaUxPMzM2bGhCRjBNVW5laDFEaDQKOWhSVEp2NVUwNXd3bnRRbWc3bloxOFpJRGNFPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" certificate: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFRENDQWZpZ0F3SUJBZ0lRRjhOQTNUQytVWUpJL1VRNTNTSFVGakFOQmdrcWhraUc5dzBCQVFVRkFEQWIKTVJrd0Z3WURWUVFEREJCUFFYVjBhQ0JEYkdsbGJuUWdTbGRVTUI0WERUSTJNRFl6TURFME1UazBOVm9YRFRJMwpNRFl6TURFME16azBOVm93R3pFWk1CY0dBMVVFQXd3UVQwRjFkR2dnUTJ4cFpXNTBJRXBYVkRDQ0FTSXdEUVlKCktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCQU1UVzFpL0tNUFY1cTl2TXgzTGV4V1lsNFdFTmJ6UDAKQVV5ZjhlNm1GcHdEcE5PWUVXV1RQYnAwUm5HRmY4SGpCaUFwTWFDSFZCbmJqMC81Q3pSWWk0N3lWWEMzaGRiQgpYdDdZRGNCeEduYnN5Zy9LdTB5SFRQRW5YblB5VCtpYXUzS2JqQXgyOGU1OXhzRHlFMzZxaktveGRTbURZT0M4CmpzM0hsMHNGTWNjd3JzZ0JxOFEyYllIVUt1TERORmRveE1DaEhOTHlxOE1sNFdnNmVHdWtMN2p4R0tGNXlzczEKM096emJoZkc5dEpWY290WUt0WDlHUThUckdpREF2UGFOREs2WXMxUTZWbDNKVjY5dmdET1ZSVlJCUzhpWmdLaQpQbTdob1hHb2s3VVEzV2R0c3dpU09ReE1iMWZhVDFLOWp0T3lmTm1vNjgyUkluU1VJSi9sTm9rQ0F3RUFBYU5RCk1FNHdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUNCZ2dyQmdFRkJRY0QKQVRBZEJnTlZIUTRFRmdRVTlRNUJSVnpRVDVNbG5mY3N6VzFwUHZiZmRud3dEUVlKS29aSWh2Y05BUUVGQlFBRApnZ0VCQUpUMVlIYk03UCtrYi9wSmJ5bENLUUd6OEZ6eGFpeDVyay9MdndJNDVmcVJTQ0NUaERmemUrdTNiK05ZCjcxV2V0Z2Y2cUhubkVrdkRnVXJyWXU1QUhrZE9WWXlDUkJnN0dWKzIrQUtpWExEYUsrV0FnM2U4Zkk2cFNvOUMKeXdvc0lNNFcyaWJWSnhzVWNIZGhTUFBVVlkrVzNSY1B4VlZmWmV4eTZXUXBsVlV0Z3JYQmpYSEdEWE5YQUw3awo4ZUFwSEpDQVByKytabktQc3VzSWFyeVNqRXRrZXR0UHViMGp0MnJlajdqaHJuU1EyRlg4czhqcU43L0lCeWRlCjd2N2YwMlowMjVPV2hZRHozRVVQaWZLOXpmM2Nac2hZRDJ3OE1jdGVUZkZKaUxPMzM2bGhCRjBNVW5laDFEaDQKOWhSVEp2NVUwNXd3bnRRbWc3bloxOFpJRGNFPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg=="
privateKey: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRREUxdFl2eWpEMWVhdmIKek1keTNzVm1KZUZoRFc4ejlBRk1uL0h1cGhhY0E2VFRtQkZsa3oyNmRFWnhoWC9CNHdZZ0tUR2doMVFaMjQ5UAorUXMwV0l1TzhsVnd0NFhXd1Y3ZTJBM0FjUnAyN01vUHlydE1oMHp4SjE1ejhrL29tcnR5bTR3TWR2SHVmY2JBCjhoTitxb3lxTVhVcGcyRGd2STdOeDVkTEJUSEhNSzdJQWF2RU5tMkIxQ3Jpd3pSWGFNVEFvUnpTOHF2REplRm8KT25ocnBDKzQ4UmloZWNyTE5kenM4MjRYeHZiU1ZYS0xXQ3JWL1JrUEU2eG9nd0x6MmpReXVtTE5VT2xaZHlWZQp2YjRBemxVVlVRVXZJbVlDb2o1dTRhRnhxSk8xRU4xbmJiTUlramtNVEc5WDJrOVN2WTdUc256WnFPdk5rU0owCmxDQ2Y1VGFKQWdNQkFBRUNnZ0VBTlk3ZktKR3pVSmVTazNQY3NQeThhVmJWUkRzTGp1OU5pelBaK0QxbzJYcUUKVnE2QnpVbUsramk1RWhwbzhMQjg4ak5ETlpLYzU0dytLbHh4R0FVZnMvMXNFZ2RkZTFtU0hzQmF2WW9JMFdNTQpkOCtKdHdENWhvUlh4dVNmcVlLd2pqYVVuSytnbEd2VXNKc3Rnc0dWRkpud2J5TTRNTzRkVFJDSnNmZnYyMnFCClNVRVh2MVFzRmhlbTBySnFuendPRlpycXVObXlvSnhTUzREYUJ4bmtqY3VHVG90Mk9qMlpVRjBTZW5HaU16NVkKRjZkR2dtWHNDTXBtc1k2czBrRUtDcExGMXIwS2VaUG8vZmVheU1GUDdONUtxVmxUSWExVGJjS2dQaWJFMGRqNwpXckhwR0l0Rm40ZHhJc2FCQlJ2dTlkZ2JJTVJuc0NhOFE1bURwbVg1SVFLQmdRRFVJQndFZjd1S0JqQ0M3OFFHClZzTmRLY1RuWTRrVDVxOFZiRlpVdkI2M052N0NDYmVCc1d0ZDRSMDFNUnkwVnhVUG4zekpWYk1DNUN5ckFHZ1EKTVRrVzlZQmVxb1A3aEk1ZnM5a1B5cG42SHlJN1NZS0ZldHF5RW5Ca1B4RklPSzUzRXJRWGxkZlRWbUdGdWpNUwpXYVpkOWpEOURmcGZKSzR6S2xuWHlXVlZkd0tCZ1FEdGpWVW9kNldRVEp5eHl1Vjk2Z29xbEl4L2t2UFNoQ2pqCkVqM2ZXY21HTGVBaUU2Z0lBNUh3YnFGM0tCSUVaSURpVVlJTjNkUUxLUlpVdXhDRkxpak9Ea3lBUkV1WFFUeFkKaUl0dWR3a3BmTS90OXdaQ09YSUczSWNXMDUvZHJrSzl5eXloMkRGTEJuZ3JERitoMzhDbWo0bExoc2RadDBQcgpXZjUxWnB6VC93S0JnR3d5aEpmMjN4MmowcEsyNFhHcVI3UDVYaW40Snk4emR4S2lVOWFjcmI0ZUd3dTJFUmZoCit6WERZVGFFZW5PeUIxZ1VyWDIwYkw2SXpBL2RBVGRoSkJHRjM1aHB1VEJOaUtGZ0J0TjdMOWJZa29sVEVYUXMKR2VqQ1p2bDdBY0dveDdTTW9iZDJBc1FWUjJFQ2ZKSmJqL1JWWXQ5d2hjaUoyU0RYOVVPUHdsUTVBb0dBWWp5VwpRTnZwemRqQTNBMktCaDRwQWg3WVUxR1VIelNrSy9NNVB3cEVlb2F5TDZWdFVaTVlZUk4vRm1XdHZiOUtSVTFyCnVReEpTaXc5bmVDV0hsMU9acGduTHN3UGJvZDl5eWI4Y2p3cnY4cHJ1bjd6U2FPejhmNTBwdzN4Q0oydDRBc0wKZEFxUnAvTU84czUxSmQ0QUwyRWdaK2xldTAwOGV6R0dOMHF1QkVVQ2dZRUF1VDZRS0FRUndxeWV4WlZPbmJPZwowZWVtelJSWDJYQStVV3J6YTRKbzV2MnZhYTR0cXhuQ2prQ1BaTldTc0diWUlTZVJNUUV0UXEwdUl0ZW9zMTFWCjJWM25BNXhwZ1ErbmZWNnpyQTFYK3VGekltWHpTbWgxSkpkK0RGUVhNT2xBWWNGY2J2aFdYWUVoaS9SR01DT3kKdU1MNWxBa2swV2JXRHFicFNGM1RMMlk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=" privateKey: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRREUxdFl2eWpEMWVhdmIKek1keTNzVm1KZUZoRFc4ejlBRk1uL0h1cGhhY0E2VFRtQkZsa3oyNmRFWnhoWC9CNHdZZ0tUR2doMVFaMjQ5UAorUXMwV0l1TzhsVnd0NFhXd1Y3ZTJBM0FjUnAyN01vUHlydE1oMHp4SjE1ejhrL29tcnR5bTR3TWR2SHVmY2JBCjhoTitxb3lxTVhVcGcyRGd2STdOeDVkTEJUSEhNSzdJQWF2RU5tMkIxQ3Jpd3pSWGFNVEFvUnpTOHF2REplRm8KT25ocnBDKzQ4UmloZWNyTE5kenM4MjRYeHZiU1ZYS0xXQ3JWL1JrUEU2eG9nd0x6MmpReXVtTE5VT2xaZHlWZQp2YjRBemxVVlVRVXZJbVlDb2o1dTRhRnhxSk8xRU4xbmJiTUlramtNVEc5WDJrOVN2WTdUc256WnFPdk5rU0owCmxDQ2Y1VGFKQWdNQkFBRUNnZ0VBTlk3ZktKR3pVSmVTazNQY3NQeThhVmJWUkRzTGp1OU5pelBaK0QxbzJYcUUKVnE2QnpVbUsramk1RWhwbzhMQjg4ak5ETlpLYzU0dytLbHh4R0FVZnMvMXNFZ2RkZTFtU0hzQmF2WW9JMFdNTQpkOCtKdHdENWhvUlh4dVNmcVlLd2pqYVVuSytnbEd2VXNKc3Rnc0dWRkpud2J5TTRNTzRkVFJDSnNmZnYyMnFCClNVRVh2MVFzRmhlbTBySnFuendPRlpycXVObXlvSnhTUzREYUJ4bmtqY3VHVG90Mk9qMlpVRjBTZW5HaU16NVkKRjZkR2dtWHNDTXBtc1k2czBrRUtDcExGMXIwS2VaUG8vZmVheU1GUDdONUtxVmxUSWExVGJjS2dQaWJFMGRqNwpXckhwR0l0Rm40ZHhJc2FCQlJ2dTlkZ2JJTVJuc0NhOFE1bURwbVg1SVFLQmdRRFVJQndFZjd1S0JqQ0M3OFFHClZzTmRLY1RuWTRrVDVxOFZiRlpVdkI2M052N0NDYmVCc1d0ZDRSMDFNUnkwVnhVUG4zekpWYk1DNUN5ckFHZ1EKTVRrVzlZQmVxb1A3aEk1ZnM5a1B5cG42SHlJN1NZS0ZldHF5RW5Ca1B4RklPSzUzRXJRWGxkZlRWbUdGdWpNUwpXYVpkOWpEOURmcGZKSzR6S2xuWHlXVlZkd0tCZ1FEdGpWVW9kNldRVEp5eHl1Vjk2Z29xbEl4L2t2UFNoQ2pqCkVqM2ZXY21HTGVBaUU2Z0lBNUh3YnFGM0tCSUVaSURpVVlJTjNkUUxLUlpVdXhDRkxpak9Ea3lBUkV1WFFUeFkKaUl0dWR3a3BmTS90OXdaQ09YSUczSWNXMDUvZHJrSzl5eXloMkRGTEJuZ3JERitoMzhDbWo0bExoc2RadDBQcgpXZjUxWnB6VC93S0JnR3d5aEpmMjN4MmowcEsyNFhHcVI3UDVYaW40Snk4emR4S2lVOWFjcmI0ZUd3dTJFUmZoCit6WERZVGFFZW5PeUIxZ1VyWDIwYkw2SXpBL2RBVGRoSkJHRjM1aHB1VEJOaUtGZ0J0TjdMOWJZa29sVEVYUXMKR2VqQ1p2bDdBY0dveDdTTW9iZDJBc1FWUjJFQ2ZKSmJqL1JWWXQ5d2hjaUoyU0RYOVVPUHdsUTVBb0dBWWp5VwpRTnZwemRqQTNBMktCaDRwQWg3WVUxR1VIelNrSy9NNVB3cEVlb2F5TDZWdFVaTVlZUk4vRm1XdHZiOUtSVTFyCnVReEpTaXc5bmVDV0hsMU9acGduTHN3UGJvZDl5eWI4Y2p3cnY4cHJ1bjd6U2FPejhmNTBwdzN4Q0oydDRBc0wKZEFxUnAvTU84czUxSmQ0QUwyRWdaK2xldTAwOGV6R0dOMHF1QkVVQ2dZRUF1VDZRS0FRUndxeWV4WlZPbmJPZwowZWVtelJSWDJYQStVV3J6YTRKbzV2MnZhYTR0cXhuQ2prQ1BaTldTc0diWUlTZVJNUUV0UXEwdUl0ZW9zMTFWCjJWM25BNXhwZ1ErbmZWNnpyQTFYK3VGekltWHpTbWgxSkpkK0RGUVhNT2xBWWNGY2J2aFdYWUVoaS9SR01DT3kKdU1MNWxBa2swV2JXRHFicFNGM1RMMlk9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0="