Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an Observable sequence that will resend HTTP requests after a timeout?

I'm new to Rx and I'm trying to create an Observable sequence that will allow me to do the following:

  1. Send a HTTP POST request to a URI using System.Net.Http.HttpClient.SendAsync(request, cancelToken)
  2. Wait for a configurable time period for a response to be returned or for the request to timeout.
  3. If the request times out, then repeat the request.
  4. Continue repeating the request until either a response is received (doesn't have to be 200 OK) or a max. number of retries is reached.
  5. If the max. number of retries is reached then I have to know about it so I can log an error.

I've been playing around with:

HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "Some URI");
...
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;

...

try
{
    var res = Observable.FromAsync(() => _client.SendAsync(request, token))
                                                .Timeout(TimeSpan.FromSeconds(5));
    try
    {
        HttpResponseMessage response = res.Wait();

        // Process response
        ...
    }
    catch (TaskCancelledException)
    {
        ....
    }
}
catch (TimeoutException)
{
    ....
}

but I'm not sure of the best way to kick off the request again after a timeout occurs, and also how I should check that I have reached the max. number of retries.

Also, I'm not sure whether I should be putting a timeout policy on the observable sequence or whether I should be setting the Timeout property on the HttpClient object itself.

[Edited on 11 Dec 2014]

Based on comments below, I have updated the code from @TheZenCoder so that it uses an await

var attempts = 0;
HttpResponseMessage response = null;

try
{
    response = await Observable
        .FromAsync((ct) =>
        {
            attempts++;
            return SendRequest(token, ct);
        })
        .Retry(5)
        .LastAsync();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

I've only done limited testing so far but it seems to work ok.

like image 632
Martin Avatar asked Dec 10 '14 12:12

Martin


1 Answers

For sending the request, setting a request timeout and using a cancellation token you can wrap that into a method like:

private static Task<HttpResponseMessage> SendRequest(CancellationToken token)
{
    var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };

    var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.google.ba"));

    return client.SendAsync(request, token);
}

I think setting the timeout on the HTTP client is a cleaner option instead of using the Observable timeout.

Then you can use the IObservable Retry method to retry this operation multiple times. If you are not afraid of opensource there are also some more flexible retry methods in the RXX Extensions as noted by @Dave Sexton in the comments.

var attempts = 0;

try
{
    var response = Observable
        .FromAsync(() =>
        {
            attempts++;
            return SendRequest(token);
        })
        .Retry(5)
        .Wait();
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

If an HTTP timeout or another error occurs in the SendRequest method the retries will continue. An exception will be thrown after the last retry containing information about the error. The number of retries is set to the attempts variable.

Have in mind that using the Wait method effectively blocks the calling thread execution until a result is available, and that is not something you ever want to do when using Async code. Maybe you have some specific scenario on mind, and that is why i left it there in my example.

like image 117
Faris Zacina Avatar answered Nov 15 '22 04:11

Faris Zacina