using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using sNetHttp = System.Net.Http;
using sNetHttpHeaders = System.Net.Http.Headers;
using Microsoft.Extensions.Logging;
namespace NucuCar.Common
{
///
/// A simple HttpClient wrapper designed to make it easier to work with web requests with media type application/json.
/// It implements a simple retry mechanism.
///
public class HttpClient : IDisposable
{
#region Fields
public ILogger Logger;
// ReSharper disable InconsistentNaming
protected int maxRetries;
protected int timeout;
// ReSharper restore InconsistentNaming
private readonly sNetHttp.HttpClient _httpClient;
public int MaxRetries
{
get => maxRetries;
set
{
if (value < 0 || value > 10)
{
throw new ArgumentOutOfRangeException($"Maximum retries allowed value is between 0 and 10!");
}
maxRetries = value;
}
}
public int Timeout
{
get => timeout;
set
{
if (value < 0 || value > 10000)
{
throw new ArgumentOutOfRangeException($"Timeout allowed value is between 0 and 10000!");
}
timeout = value;
}
}
#endregion
#region Constructors
protected HttpClient()
{
_httpClient = new sNetHttp.HttpClient();
maxRetries = 3;
timeout = 10000;
Logger = null;
}
protected HttpClient(string baseAddress) : this()
{
_httpClient.BaseAddress = new Uri(baseAddress);
// TODO: Setup logging.
}
protected HttpClient(string baseAddress, int maxRetries) : this(baseAddress)
{
MaxRetries = maxRetries;
}
#endregion
#region Public Methods
public void Authorization(string scheme, string token)
{
_httpClient.DefaultRequestHeaders.Authorization =
new sNetHttpHeaders.AuthenticationHeaderValue(scheme, token);
}
public void Authorization(string token)
{
Authorization("Bearer", token);
}
public async Task GetAsync(string path)
{
var request = _makeRequest(sNetHttp.HttpMethod.Get, path);
return await SendAsync(request);
}
public async Task PostAsync(string path, Dictionary data)
{
var request = _makeRequest(sNetHttp.HttpMethod.Post, path);
request.Content = _makeContent(data);
return await SendAsync(request);
}
public async Task PutAsync(string path, Dictionary data)
{
var request = _makeRequest(sNetHttp.HttpMethod.Put, path);
request.Content = _makeContent(data);
return await SendAsync(request);
}
public async Task DeleteAsync(string path, Dictionary data)
{
var request = _makeRequest(sNetHttp.HttpMethod.Delete, path);
request.Content = _makeContent(data);
return await SendAsync(request);
}
///
/// Makes a request with timeout and retry support.
///
/// The request to make.
///
public virtual async Task SendAsync(sNetHttp.HttpRequestMessage requestMessage)
{
var currentRetry = 0;
sNetHttp.HttpResponseMessage responseMessage = null;
while (currentRetry < maxRetries)
{
try
{
responseMessage = await _sendAsync(requestMessage);
break;
}
catch (TaskCanceledException)
{
Logger?.LogError($"Request timeout for {requestMessage.RequestUri}!");
}
catch (sNetHttp.HttpRequestException e)
{
// The request failed due to an underlying issue such as network connectivity, DNS failure,
// server certificate validation or timeout.
Logger?.LogError($"HttpRequestException timeout for {requestMessage.RequestUri}!");
Logger?.LogError($"{e.Message}");
}
finally
{
currentRetry += 1;
}
}
return responseMessage;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region NonPublic Methods
///
/// Creates a StringContent with media type of application.json and encodes it with UTF8.
///
/// A dictionary representing JSON data.
///
private sNetHttp.StringContent _makeContent(Dictionary data)
{
return new sNetHttp.StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json");
}
///
/// Creates a HttpRequestMessage, applies the auth header and constructs the uri.
///
/// The HttpMethod to use
/// The path, whether it is relative to the base or a new one.
///
private sNetHttp.HttpRequestMessage _makeRequest(sNetHttp.HttpMethod method, string path)
{
var requestUri = path.ToLowerInvariant().StartsWith("http://")
? new Uri(path)
: new Uri(_httpClient.BaseAddress, path);
var requestMessage = new sNetHttp.HttpRequestMessage
{
Method = method,
RequestUri = requestUri
};
requestMessage.Headers.Authorization = _httpClient.DefaultRequestHeaders.Authorization;
return requestMessage;
}
///
/// Makes a request which gets cancelled after Timeout.
///
///
///
private async Task _sendAsync(sNetHttp.HttpRequestMessage requestMessage)
{
var cts = new CancellationTokenSource();
sNetHttp.HttpResponseMessage response;
// Make sure we cancel after a certain timeout.
cts.CancelAfter(timeout);
try
{
response = await _httpClient.SendAsync(requestMessage,
sNetHttp.HttpCompletionOption.ResponseContentRead,
cts.Token);
}
finally
{
cts.Dispose();
}
return response;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_httpClient.Dispose();
}
}
#endregion
}
}