KosisHttp.cs 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. using System.Net.Http.Headers;
  2. using Microsoft.Extensions.Logging;
  3. namespace Infrastructure.StockData;
  4. /// <summary>
  5. /// KOSIS(kosis.kr) OpenAPI 호출 공통 — 인증은 URL query param apiKey (헤더 아님, koreaexim/OpenDART 와 동일 방식).
  6. /// 호출부가 apiKey 를 포함한 완성 URL 을 넘기며, 여기선 Accept json + 간단 3회 재시도(2s/4s 백오프) + 로그 시 키 마스킹만 담당.
  7. /// </summary>
  8. internal static class KosisHttp
  9. {
  10. public const string ClientName = "Kosis";
  11. private const int MaxAttempts = 3;
  12. public static async Task<string> GetStringWithRetryAsync(HttpClient client, string url, ILogger logger, CancellationToken ct)
  13. {
  14. for (var attempt = 1; ; attempt++)
  15. {
  16. try
  17. {
  18. using var request = new HttpRequestMessage(HttpMethod.Get, url);
  19. request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  20. using var response = await client.SendAsync(request, ct);
  21. response.EnsureSuccessStatusCode();
  22. return await response.Content.ReadAsStringAsync(ct);
  23. }
  24. catch (Exception ex) when (ex is not OperationCanceledException && attempt < MaxAttempts)
  25. {
  26. logger.LogWarning(ex, "[Kosis] HTTP 실패 — 재시도 {Attempt}/{Max} (url={Url})", attempt, MaxAttempts, Mask(url));
  27. await Task.Delay(TimeSpan.FromSeconds(2 * attempt), ct);
  28. }
  29. }
  30. }
  31. /// <summary>로그용 URL 에서 apiKey 값을 가린다.</summary>
  32. public static string Mask(string url)
  33. {
  34. var idx = url.IndexOf("apiKey=", StringComparison.OrdinalIgnoreCase);
  35. if (idx < 0)
  36. {
  37. return url;
  38. }
  39. var start = idx + "apiKey=".Length;
  40. var end = url.IndexOf('&', start);
  41. if (end < 0)
  42. {
  43. end = url.Length;
  44. }
  45. return string.Concat(url.AsSpan(0, start), "***", url.AsSpan(end));
  46. }
  47. }