Add retry logic for Post, Patch and Delete to the HttpAbstractions
This commit is contained in:
parent
5d6e5cf2ad
commit
bcc2742e81
1 changed files with 102 additions and 33 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue