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;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
@ -24,24 +26,29 @@ namespace Geekbot.Core
|
||||||
return client;
|
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 ??= CreateDefaultClient();
|
||||||
httpClient.BaseAddress = location;
|
httpClient.BaseAddress = location;
|
||||||
|
|
||||||
var response = await httpClient.GetAsync(location.PathAndQuery);
|
HttpResponseMessage response;
|
||||||
response.EnsureSuccessStatusCode();
|
try
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (disposeClient)
|
|
||||||
{
|
{
|
||||||
httpClient.Dispose();
|
response = await Execute(() => httpClient.GetAsync(location.PathAndQuery), maxRetries);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeClient)
|
||||||
|
{
|
||||||
|
httpClient.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
return JsonSerializer.Deserialize<TResponse>(stringResponse);
|
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 ??= CreateDefaultClient();
|
||||||
httpClient.BaseAddress = location;
|
httpClient.BaseAddress = location;
|
||||||
|
@ -51,69 +58,131 @@ namespace Geekbot.Core
|
||||||
Encoding.UTF8,
|
Encoding.UTF8,
|
||||||
"application/json"
|
"application/json"
|
||||||
);
|
);
|
||||||
var response = await httpClient.PostAsync(location.PathAndQuery, content);
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var stringResponse = await response.Content.ReadAsStringAsync();
|
|
||||||
|
|
||||||
if (disposeClient)
|
HttpResponseMessage response;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
httpClient.Dispose();
|
response = await Execute(() => httpClient.PostAsync(location, content), maxRetries);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (disposeClient)
|
||||||
|
{
|
||||||
|
httpClient.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var stringResponse = await response.Content.ReadAsStringAsync();
|
||||||
return JsonSerializer.Deserialize<TResponse>(stringResponse);
|
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 ??= CreateDefaultClient();
|
||||||
httpClient.BaseAddress = location;
|
httpClient.BaseAddress = location;
|
||||||
|
|
||||||
var content = new StringContent(
|
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,
|
Encoding.UTF8,
|
||||||
"application/json"
|
"application/json"
|
||||||
);
|
);
|
||||||
|
|
||||||
var response = await httpClient.PostAsync(location, content);
|
try
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
if (disposeClient)
|
|
||||||
{
|
{
|
||||||
httpClient.Dispose();
|
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 ??= CreateDefaultClient();
|
||||||
httpClient.BaseAddress = location;
|
httpClient.BaseAddress = location;
|
||||||
|
|
||||||
var content = new StringContent(
|
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,
|
Encoding.UTF8,
|
||||||
"application/json"
|
"application/json"
|
||||||
);
|
);
|
||||||
|
|
||||||
var response = await httpClient.PatchAsync(location, content);
|
try
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
if (disposeClient)
|
|
||||||
{
|
{
|
||||||
httpClient.Dispose();
|
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 ??= CreateDefaultClient();
|
||||||
httpClient.BaseAddress = location;
|
httpClient.BaseAddress = location;
|
||||||
|
|
||||||
var response = await httpClient.DeleteAsync(location);
|
try
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
|
|
||||||
if (disposeClient)
|
|
||||||
{
|
{
|
||||||
httpClient.Dispose();
|
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