HttpClientFactory с политикой тайм-аута Polly, похоже, не работает

Я пытаюсь реализовать политику тайм-аута Polly с помощью нового .NET Core 2.1 HttpClientFactory; однако я не могу получить тайм-аут.

My ConfigureServices:

// Configure polly policies
TimeoutPolicy<HttpResponseMessage> timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(5, TimeoutStrategy.Pessimistic);

// Configure platform service clients
services.AddHttpClient<IDiscoveryClient, DiscoveryClient>()
    .AddPolicyHandler(timeoutPolicy);

Мой метод POST в DiscoveryClient:

public async Task<TResponse> PostXMLAsync<TResponse, TPostData>(string url, TPostData postData)
    where TResponse : ClientResponse
    where TPostData : ClientPostData
{
    HttpResponseMessage response = await httpClient.PostAsXmlAsync(url, postData);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsAsync<TResponse>();
}

К сожалению, время ожидания вызова истекает через 100 секунд по умолчанию, а не через 5 секунд, определенных в политике Polly.

Есть мысли о том, что я делаю не так?


person wdspider    schedule 13.07.2018    source источник
comment
Где ты видишь таймаут? В await httpClient.PostAsXmlAsync(url, postData) или в await response.Content.ReadAsAsync<TResponse>()? Как ты видишь тайм-аут? В журналах, или вы видите тайм-аут, выданный как исключение из любой из этих двух строк кода?   -  person mountain traveller    schedule 14.07.2018
comment
По сути, таймаута 5 секунд не происходит. Вместо этого await httpClient.PostAsXmlAsync(url, postData) требуется 100 секунд для завершения, а затем я получаю HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error). от response.EnsureSuccessStatusCode();. Известно, что служба, которую я вызываю, время от времени делает это, поэтому я хотел реализовать политику тайм-аута Polly.   -  person wdspider    schedule 16.07.2018
comment
Вы можете подтвердить, что происходит с TimeoutStrategy.Optimistic? HttpClientFactory   -  person mountain traveller    schedule 16.07.2018
comment
TimeoutStrategy.Optimistic, похоже, не имеет значения. Я создал демонстрацию здесь: gist.github.com/wdspider/a1cf8328db54e06cd то, что я звоню, не является публичным; поэтому я просто добавил 20-секундную задержку в ClientLoggingHandler, что должно быть приличной симуляцией.   -  person wdspider    schedule 16.07.2018
comment
Тайм-аут в https://gist.github.com/wdspider/a1cf8328dbcf6cd42ea0a889f5427f0b таков, что await Task.Delay(...) выполняется до (является «снаружи», с точки зрения вложенности) TimeoutPolicy; т.е. политика тайм-аута не регулирует задержку. Измените его на services....AddPolicyHandler(timeoutPolicy).AddHttpMessageHandler<ClientLoggingHandler>();, и тайм-аут сработает. Помогает ли эта информация оригинальному делу?   -  person mountain traveller    schedule 17.07.2018
comment
Такое переупорядочение действительно работает, как я и ожидал. Из документов Polly я не понял, что на самом деле имеет значение порядок политик с обработчиками сообщений; только то, что политический порядок между политиками имел значение. Спасибо за вашу помощь. Если вы хотите переписать свой пред. комментарий как ответ, отмечу.   -  person wdspider    schedule 17.07.2018
comment
Спасибо @wdspider. Добавлено в документацию Polly с этой целью.   -  person mountain traveller    schedule 17.07.2018
comment
Я вижу эту проблему с этим фрагментом кода: services.AddHttpClient(typeof(T).Name).AddPolicyHandler(GetPollyPolicies(retryCount, timeoutSeconds)); где, когда я комбинирую это waitAndRetryPolicy.WrapAsync(timeoutPerTryPolicy); Когда я получаю httpClient от CreateClient, я смотрю на тайм-аут, который по умолчанию установлен на 100 секунды. @mountaintraveller   -  person vsarunov    schedule 06.08.2020


Ответы (1)


Сначала давайте определим фиктивный сервер, который отвечает 500 через 100 секунд:

const string address = "http://localhost:9000";
var delay = TimeSpan.FromSeconds(100);
var server = WireMockServer.Start(new WireMockServerSettings { Urls = new[] { address } });
server
    .Given(Request.Create().WithPath("/").UsingPost())
    .RespondWith(Response.Create().WithDelay(delay).WithStatusCode(500));

Для этого я использовал WireMock.Net.

Теперь посмотрим на IDiscoveryClient и DiscoveryClient:

interface IDiscoveryClient
{
    Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data);
}
class DiscoveryClient : IDiscoveryClient
{
    private readonly HttpClient httpClient;

    public DiscoveryClient(HttpClient httpClient) => this.httpClient = httpClient;

    public async Task<TResponse> SendRequest<TResponse, TPostData>(string url, TPostData data)
    {
        var content = new StringContent(JsonConvert.SerializeObject(data), Encoding.UTF8);
        var response = await httpClient.PostAsync(url, content);
        response.EnsureSuccessStatusCode();
        var rawData = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<TResponse>(rawData);
    }
}
class TestRequest { public string Content { get; set; } }
class TestResponse { public string Data { get; set; } }

Я использовал json вместо xml, но с точки зрения вопроса это неважно.

И, наконец, подключим DI и сделаем запрос:

AsyncTimeoutPolicy<HttpResponseMessage> timeoutPolicy =
    Policy.TimeoutAsync<HttpResponseMessage>(5, TimeoutStrategy.Pessimistic);

IServiceCollection services = new ServiceCollection();
services.AddHttpClient<IDiscoveryClient, DiscoveryClient>()
    .AddPolicyHandler(timeoutPolicy);

ServiceProvider serviceProvider = services.BuildServiceProvider();
var client = serviceProvider.GetService<IDiscoveryClient>();

Stopwatch sw = Stopwatch.StartNew();
try
{
    TestResponse res = await client.SendRequest<TestResponse, TestRequest>(address, new TestRequest { Content =  "Test"});
}
catch (TimeoutRejectedException ex)
{
    sw.Stop();
    Console.WriteLine(sw.Elapsed);
}

Напечатанный результат будет примерно таким:

00:00:05.0296804

Хорошо то, что он также работает со стратегиями Optimistic и Pessimistic.

person Peter Csala    schedule 15.01.2021