각 ASPNET 핵심 웹 API 프로젝트 요구 사항 - 섹션 5 - Polly

이 시리즈의 네 번째 부분은 본 논문에서 Polly를 사용하여 HttpClient에 복구 능력과 순간적인 고장 처리를 추가하는 방법을 보여 드리겠습니다.
ASP를 만들면NET 핵심 웹 API 프로젝트 중 WeatherForecastController이 일기예보 목록으로 돌아왔다.나는 정적 데이터를 되돌려 주는 것이 아니라 외부 날씨 API를 사용하여 실제 일기예보를 얻을 것이다.

1단계 - 외부 API

  • ~ weatherapi.com
  • 계정 만들기
  • 에 로그인하면 대시보드에서 API 키를 찾을 수 있습니다.

  • 2단계 - 유형 HTTP 클라이언트


    프로젝트에서 HttpClient 클래스를 사용하는 방법은 다음과 같습니다.
  • 기본용법
  • 명명 고객
  • 입력된 클라이언트
  • 생성된 클라이언트
  • 대부분의 경우, 나는 유형화된 클라이언트를 사용하는 것을 더욱 좋아한다. 왜냐하면:
  • 은 키
  • 으로 문자열을 사용하지 않고도 명명된 클라이언트와 동일한 기능을 제공합니다.
  • 은 클라이언트
  • 을 사용할 때 IntelliSense 및 컴파일러 도움말을 제공합니다.
  • 은 특정 HttpClient를 구성하고 상호 작용하는 별도의 위치를 제공합니다.예를 들어, 단일 유형의 클라이언트를 사용할 수 있습니다.
  • (단일 백엔드 엔드포인트용)
  • 은 처리 단점
  • 의 모든 논리를 봉인한다
  • 은 DI와 함께 사용되며 어플리케이션
  • 에 필요할 때 주입 가능
    날씨 외부 API에 대한 유형 클라이언트를 만듭니다.
  • 폴더 HttpClients 폴더
  • 에 새 폴더 생성 Infrastructure
  • 새 클래스 WeatherHttpClient.csHttpClients 폴더
  • 에 추가
  • appsettings.json 파일을 열고 다음 키/값을 추가합니다.
  • "WeatherSettings": {
      "ApiKey": "YOURKEY"
      "BaseUrl": "https://api.weatherapi.com",
      "NoDaysForecast": 5
    } 
    
  • WeatherHttpClient.cs 파일을 열고 API 설정의 클래스를 만듭니다.
  • public class WeatherSettings
    {
        public string ApiKey { get; set; }
    
        public string BaseUrl { get; set; }
    
        public int NoDaysForecast { get; set; }
    }
    
  • 열기 Startup.cs 클래스와 내부 ConfigureServices 방법 귀속 및 날씨 설정 등록:
  • var weatherSettings = new WeatherSettings();
    Configuration.GetSection("WeatherSettings").Bind(weatherSettings);
    services.AddSingleton(weatherSettings);
    
  • 내부 WeatherHttpClient.cs 파일 생성 인터페이스:
  • public interface IWeatherHttpClient
    {
        Task<IEnumerable<WeatherForecast>> GetForecastAsync(string cityName);
    }
    
  • 은 다음과 같은 인터페이스를 제공합니다.
  • public class WeatherHttpClient : IWeatherHttpClient
    {
        private readonly HttpClient _client;
        private readonly WeatherSettings _settings;
    
        public WeatherHttpClient(HttpClient client, WeatherSettings settings)
        {
            _client = client;
            _settings = settings;
            _client.BaseAddress = new Uri(_settings.BaseUrl);
            _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }
    
        public async Task<IEnumerable<WeatherForecast>> GetForecastAsync(string cityName)
        {
            var url = $"v1/forecast.json?key={_settings.ApiKey}&q={cityName}&days={_settings.NoDaysForecast}";
            var response = await _client.GetAsync(url);
            response.EnsureSuccessStatusCode();
            var content = await response.Content.ReadAsStringAsync();
            ...
        }
    }
    
    API의 응답은 매우 상세합니다. 저는 반서열화된 JSON의 대상을 만들지 않고 익명의 대상을 사용하여 제가 필요로 하는 데이터를 추출합니다.
    public async Task<IEnumerable<WeatherForecast>> GetForecastsAsync(string cityName)
    {
        ...
        var days = JsonSerializerExtensions.DeserializeAnonymousType(content, new
        {
            forecast = new
            {
                forecastday = new[]
                {
                   new
                   {
                       date = DateTime.Now,
                       day = new { avgtemp_c = 0.0, condition = new { text = "" } }
                   }
                }
            }
        }).forecast.forecastday;
    
        return days.Select(d => new WeatherForecast
        {
            Date = d.date,
            Summary = d.day.condition.text,
            TemperatureC = (int)d.day.avgtemp_c
        });
    
        // Other way to deserialize json without creating anonymous object
        // To get more information see https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=net-5.0
        //dynamic result = JsonSerializer.Deserialize<ExpandoObject>(content);
        //var days = result.forecast.GetProperty("forecastday").EnumerateArray();
        //foreach (var day in days)
        //{
        //    var date = day.GetProperty("date").GetDateTime();
        //    var temp = day.GetProperty("day").GetProperty("avgtemp_c").GetDouble();
        //    var condition = day.GetProperty("day").GetProperty("condition").GetProperty("text").GetString();
        //}
    }
    
  • 클래스 Startup.cs 열기 및 날씨 HTTP 클라이언트 서비스 등록:
  • services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>();
    
  • WeatherForecastController으로 열고 IWeatherHttpClient을 주입합니다.
  • namespace CoolWebApi.Apis.V1.Controllers
    {
        [ApiController]
        [ApiVersion("1.0")]
        [Route("api/v{version:apiVersion}/[controller]")]
        public class WeatherForecastController : ControllerBase
        {
            private readonly IWeatherHttpClient _weatherClient;
    
            public WeatherForecastController(IWeatherHttpClient weatherClient)
            {
                _weatherClient = weatherClient;
            }
    
            [HttpGet]
            [ProducesResponseType(StatusCodes.Status200OK)]
            [ProducesResponseType(StatusCodes.Status400BadRequest)]
            public async Task<IEnumerable<WeatherForecast>> Get(string city = "London")
            {
                return await _weatherClient.GetForecastsAsync(city);
            }
    
    날씨 API를 호출할 때 오류를 모의할 때가 되었다.따라서 외부 API를 제어할 수 없으며 Fiddler Everywhere을 사용하여 HTTP 요청과 응답을 캡처하고 처리합니다.

    3. 바이올리니스트 사용하기


  • Download, 바이올리니스트 설치
  • Fiddler 실행 및 api/v1/weatherforecast API 호출

    Fiddler가 포획한 HTTP 데이터를 볼 수 있지만, 암호화되어 있기 때문에 HTTPS에서 호출된 요청/응답을 볼 수 없습니다. 그러나 Fiddler는 HTTPS 데이터를 복호화하는 메커니즘을 가지고 있습니다.
  • 메뉴에서 View->Preferences 을 클릭하거나 ctrl+, 을 클릭하여 Settings 을 엽니다.
  • 설정 창에서 HTTPS 탭을 클릭한 다음 Trust root certificate (Remove root certificate 을 클릭하여 언제든지 인증서를 삭제할 수 있음)
  • 에서 API를 재호출하여 암호 해독 요청/응답을 볼 수 있습니다.

    포획된 데이터를 필터해서 다른 요청을 제거합시다.
  • URL 열의
  • 수직점을 클릭하여 필터 창을 열고 localhost:5001api.weatherapi.com URL을 입력하고 AndOr으로 변경합니다.

    이제 api.weatherapi.com API의 응답을 변경할 때가 되었습니다.
  • 탭에서 Live Traffic 행을 마우스 오른쪽 버튼으로 클릭하고 메뉴에서 api.weatherapi.com (1)을 클릭합니다.Add new rule 탭에서 스위치 버튼을 클릭하여 활성화(2)하고 편집 아이콘(3)

  • Auto Responder 창의
  • 에서 Rule Editor 입력 및 다음 텍스트를 지우고 저장 버튼을 클릭합니다.
  • HTTP/1.1 503
    
    Service is unavailable
    

    규칙을 테스트해 봅시다.Raw 행을 다시 마우스 오른쪽 버튼으로 클릭하고 메뉴에서 api.weatherapi.com 을 클릭하면 API 가 Reply->Reissue requests 상태 코드가 아닌 503 으로 돌아갑니다.

    API를 거들먹거리며 호출해 보면 200을 받을 수 있다

    4단계 - Polly 설치

  • 설치 Internal Server Error nuget 패키지
  • 에서 Microsoft.Extensions.Http.Polly 클래스를 열고 Startup.cs 방법에서 ConfigureServices 등록을 다음과 같이 수정합니다.
  • services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(4)));
    
    이제 WeatherHttpClient을 3회 재시도하고 재시도 간격을 4초간 기다립니다.WeatherHttpClient에서 사용할 API 호출 횟수를 다시 한 번 살펴보겠습니다.

    API가 호출되는 동안 Fiddler Auto Responder를 닫으려면 다시 시도해 보겠습니다.

    이번에는 자동 응답기를 끄면 API가 상태 코드 200과 예측 결과를 반환합니다.
    마우스를 WeatherHttpClient 메서드에 놓으면 AddTransientHttpErrorPolicy에서 다음 오류 범주를 처리하는 것을 알 수 있습니다.
  • 네트워크 장애(예: System.Net.Http.Http Request Exception)
  • HTTP 5XX 상태 코드(서버 오류)
  • HTTP 408 상태 코드(요청 시간 초과)

    예를 들어, HTTP 오류 코드 400은 재시도되지 않습니다.
  • Polly는 다음과 같은 다양한 유연한 정책을 제공합니다.
  • 재시도
  • 회로 차단기
  • 제한 시간
  • 칸막이 격리
  • 캐시
  • 예비(fallback)
  • 보증서 패키지
  • 빠른 실패는 사용자/호출자를 기다리게 하는 것보다 낫다.만약 외부 서비스에 고장이 나거나 심각한 어려움이 생기면 이 시스템을 좀 쉬게 하는 것이 가장 좋다.이 경우 Polly에서 여러 정책을 연결하여 휴식을 취할 수 있습니다.차단기 정책과 다시 시도해 보겠습니다.
    services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddTransientHttpErrorPolicy(policy => 
            policy.WaitAndRetryAsync(2, _ => TimeSpan.FromSeconds(2)))
        .AddTransientHttpErrorPolicy(policy => 
            policy.CircuitBreakerAsync(2, TimeSpan.FromSeconds(5)));
    
    우리는 재시도를 2초씩 하고 4번의 재시도에 실패한 후 5초간 멈추기를 희망한다.

    추가 예:

  • 의 다른 재시도 시간:
  • services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(new[]
            {
                TimeSpan.FromSeconds(2),
                TimeSpan.FromSeconds(6),
                TimeSpan.FromSeconds(10)
            }));
    
    이 정책은 1차 재시도 전에 2초, 2차 재시도 전에 6초, 3차 재시도 전에 10초 지연됩니다.
  • 기타 상태 코드 처리
  • var policy = HttpPolicyExtensions
      .HandleTransientHttpError()
      .OrResult(response => (int)response.StatusCode == 417) // RetryAfter
      .WaitAndRetryAsync(...);
    
    앞에서 언급한 바와 같이 기본적으로 PolicyBuilderHandleTransientHttpError, HttpRequestException5XX 오류를 처리한다.상술한 정책도 429 상태 코드를 처리할 수 있다.
  • 사용자 정의 오류 처리 논리:
  • services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddTransientHttpErrorPolicy(policy => policy.RetryAsync(3, onRetry: (exception, retryCount) =>
        {
            //Add logic to be executed before each retry
        }));
    
  • 영원히 재시도
  • services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddTransientHttpErrorPolicy(policy => policy.RetryForeverAsync());
    
  • 고급 회로 차단기
  • services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddTransientHttpErrorPolicy(policy => policy.AdvancedCircuitBreakerAsync(
            failureThreshold: 0.5,
            samplingDuration: TimeSpan.FromSeconds(10),
            minimumThroughput: 8,
            durationOfBreak: TimeSpan.FromSeconds(30)
        ));
    
    수신 요청의 50% 이상이 실패하거나 10초 동안 최소 8번의 장애가 발생하면 30초 동안 회로를 끊습니다.회로는 30초 후에 복원/폐합된다.
  • 철수
  • services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddTransientHttpErrorPolicy(policy => 
            policy.FallbackAsync(new HttpResponseMessage(HttpStatusCode.RequestTimeout)));
    
    리트랙트 기술은 고장이 계속 발생할 때 리트랙트 값을 되돌려 주는 데 도움이 되며, 이상을 다시 던지는 것이 아니라 리트랙트 값을 되돌려 주는 데 도움이 된다.이는 반환 값이 감지될 때 시스템이 우아하게 시스템 안정을 유지하도록 하는 데 도움이 된다.
    시간 초과

  • var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);
    services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddPolicyHandler(timeoutPolicy);
    
    시간 초과 정책은 응답 요청에 필요한 시간을 지정할 수 있습니다. 지정한 시간 내에 응답이 없으면 요청이 취소됩니다.
  • 동적 선택 전략
  • var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);
    var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>();
    services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddPolicyHandler(request => request.Method == HttpMethod.Get 
            ? retryPolicy 
            : noOpPolicy
    );
    
    다른 HTTP 용어에는 적용되지 않고 408 요청에만 적용되는 정책을 정의해야 할 수도 있습니다.위의 예에서 시간 초과 정책은 GET개의 요청에만 사용됩니다.
  • 정책 레지스트리
  • var registry = services.AddPolicyRegistry();
    registry.Add("DefaultRetryStrategy", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(...));
    registry.Add("DefaultCircuitBreaker", HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(...));
    
    // Or
    
    var registry = new PolicyRegistry()
    {
        { "DefaultRetryStrategy", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(...) },
        { "DefaultCircuitBreaker", HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(...) }
    };
    services.AddSingleton<IReadOnlyPolicyRegistry<string>>(registry);
    
    services.AddHttpClient<IWeatherHttpClient, WeatherHttpClient>()
        .AddPolicyHandlerFromRegistry("DefaultCircuitBreaker")
        .AddPolicyHandlerFromRegistry("DefaultCircuitBreaker");
    
    Polly는 strategy storage center와 같은 정책 레지스트리를 제공합니다. 등록된 정책은 응용 프로그램의 여러 위치에서 다시 사용할 수 있도록 합니다.
    당신은 Github에서 이 연습의 원본 코드를 찾을 수 있습니다.

    좋은 웹페이지 즐겨찾기