using System.Net.Http.Headers;
using Microsoft.Extensions.Logging;
namespace Infrastructure.StockData;
///
/// KOSIS(kosis.kr) OpenAPI 호출 공통 — 인증은 URL query param apiKey (헤더 아님, koreaexim/OpenDART 와 동일 방식).
/// 호출부가 apiKey 를 포함한 완성 URL 을 넘기며, 여기선 Accept json + 간단 3회 재시도(2s/4s 백오프) + 로그 시 키 마스킹만 담당.
///
internal static class KosisHttp
{
public const string ClientName = "Kosis";
private const int MaxAttempts = 3;
public static async Task 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);
}
}
}
/// 로그용 URL 에서 apiKey 값을 가린다.
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));
}
}