| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 |
- using System.Net.Http.Headers;
- using Microsoft.Extensions.Logging;
- namespace Infrastructure.StockData;
- /// <summary>
- /// KOSIS(kosis.kr) OpenAPI 호출 공통 — 인증은 URL query param apiKey (헤더 아님, koreaexim/OpenDART 와 동일 방식).
- /// 호출부가 apiKey 를 포함한 완성 URL 을 넘기며, 여기선 Accept json + 간단 3회 재시도(2s/4s 백오프) + 로그 시 키 마스킹만 담당.
- /// </summary>
- internal static class KosisHttp
- {
- public const string ClientName = "Kosis";
- private const int MaxAttempts = 3;
- public static async Task<string> GetStringWithRetryAsync(HttpClient client, string url, ILogger logger, CancellationToken ct)
- {
- for (var attempt = 1; ; attempt++)
- {
- try
- {
- using var request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
- using var response = await client.SendAsync(request, ct);
- response.EnsureSuccessStatusCode();
- return await response.Content.ReadAsStringAsync(ct);
- }
- catch (Exception ex) when (ex is not OperationCanceledException && attempt < MaxAttempts)
- {
- logger.LogWarning(ex, "[Kosis] HTTP 실패 — 재시도 {Attempt}/{Max} (url={Url})", attempt, MaxAttempts, Mask(url));
- await Task.Delay(TimeSpan.FromSeconds(2 * attempt), ct);
- }
- }
- }
- /// <summary>로그용 URL 에서 apiKey 값을 가린다.</summary>
- public static string Mask(string url)
- {
- var idx = url.IndexOf("apiKey=", StringComparison.OrdinalIgnoreCase);
- if (idx < 0)
- {
- return url;
- }
- var start = idx + "apiKey=".Length;
- var end = url.IndexOf('&', start);
- if (end < 0)
- {
- end = url.Length;
- }
- return string.Concat(url.AsSpan(0, start), "***", url.AsSpan(end));
- }
- }
|