Add retry logic for Post, Patch and Delete to the HttpAbstractions

This commit is contained in:
Daan Boerlage 2021-11-14 01:12:32 +01:00
parent 5d6e5cf2ad
commit bcc2742e81
Signed by: daan
GPG key ID: FCE070E1E4956606

View file

@ -1,4 +1,6 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
@ -24,24 +26,29 @@ namespace Geekbot.Core
return client;
}
public static async Task<TResponse> Get<TResponse>(Uri location, HttpClient httpClient = null, bool disposeClient = true)
public static async Task<TResponse> Get<TResponse>(Uri location, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var response = await httpClient.GetAsync(location.PathAndQuery);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
HttpResponseMessage response;
try
{
response = await Execute(() => httpClient.GetAsync(location.PathAndQuery), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
var stringResponse = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TResponse>(stringResponse);
}
public static async Task<TResponse> Post<TResponse>(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true)
public static async Task<TResponse> Post<TResponse>(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
@ -51,70 +58,132 @@ namespace Geekbot.Core
Encoding.UTF8,
"application/json"
);
var response = await httpClient.PostAsync(location.PathAndQuery, content);
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
HttpResponseMessage response;
try
{
response = await Execute(() => httpClient.PostAsync(location, content), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
var stringResponse = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TResponse>(stringResponse);
}
public static async Task Post(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true)
public static async Task Post(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }),
JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }),
Encoding.UTF8,
"application/json"
);
var response = await httpClient.PostAsync(location, content);
response.EnsureSuccessStatusCode();
try
{
await Execute(() => httpClient.PostAsync(location, content), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
}
public static async Task Patch(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true)
public static async Task Patch(Uri location, object data, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }),
JsonSerializer.Serialize(data, new JsonSerializerOptions() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }),
Encoding.UTF8,
"application/json"
);
var response = await httpClient.PatchAsync(location, content);
response.EnsureSuccessStatusCode();
try
{
await Execute(() => httpClient.PatchAsync(location, content), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
}
public static async Task Delete(Uri location, HttpClient httpClient = null, bool disposeClient = true)
public static async Task Delete(Uri location, HttpClient httpClient = null, bool disposeClient = true, int maxRetries = 3)
{
httpClient ??= CreateDefaultClient();
httpClient.BaseAddress = location;
var response = await httpClient.DeleteAsync(location);
response.EnsureSuccessStatusCode();
try
{
await Execute(() => httpClient.DeleteAsync(location), maxRetries);
}
finally
{
if (disposeClient)
{
httpClient.Dispose();
}
}
}
private static async Task<HttpResponseMessage> Execute(Func<Task<HttpResponseMessage>> request, int maxRetries)
{
var attempt = 0;
while (true)
{
var response = await request();
if (!response.IsSuccessStatusCode)
{
if (attempt >= maxRetries)
{
throw new HttpRequestException($"Request failed after {attempt} attempts");
}
if (response.Headers.Contains("Retry-After"))
{
var retryAfter = response.Headers.GetValues("Retry-After").First();
if (retryAfter.Contains(':'))
{
var duration = DateTimeOffset.Parse(retryAfter).ToUniversalTime() - DateTimeOffset.Now.ToUniversalTime();
await Task.Delay(duration);
}
else
{
await Task.Delay(int.Parse(retryAfter) * 1000);
}
}
else if (response.StatusCode is HttpStatusCode.BadGateway or HttpStatusCode.ServiceUnavailable or HttpStatusCode.GatewayTimeout)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Ceiling(attempt * 1.5)));
}
else
{
response.EnsureSuccessStatusCode();
}
attempt++;
}
else
{
return response;
}
}
}
}
}