diff --git a/NucuCar.Common/HttpClient.cs b/NucuCar.Common/HttpClient.cs
new file mode 100644
index 0000000..dcb0c6f
--- /dev/null
+++ b/NucuCar.Common/HttpClient.cs
@@ -0,0 +1,241 @@
+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
+ }
+}
\ No newline at end of file
diff --git a/NucuCar.Common/NucuCar.Common.csproj b/NucuCar.Common/NucuCar.Common.csproj
index 42cf6aa..7c20e9d 100644
--- a/NucuCar.Common/NucuCar.Common.csproj
+++ b/NucuCar.Common/NucuCar.Common.csproj
@@ -4,4 +4,8 @@
netcoreapp3.0;netcoreapp3.1
+
+
+
+
diff --git a/NucuCar.Telemetry/TelemetryPublisherFirestore.cs b/NucuCar.Telemetry/TelemetryPublisherFirestore.cs
index a86d778..38949f1 100644
--- a/NucuCar.Telemetry/TelemetryPublisherFirestore.cs
+++ b/NucuCar.Telemetry/TelemetryPublisherFirestore.cs
@@ -64,8 +64,7 @@ namespace NucuCar.Telemetry
var requestUrl = $"https://firestore.googleapis.com/v1/projects/{firestoreProjectId}/" +
$"databases/(default)/documents/{firestoreCollection}/";
- _httpClient = new HttpClient();
- _httpClient.BaseAddress = new Uri(requestUrl);
+ _httpClient = new HttpClient {BaseAddress = new Uri(requestUrl)};
Logger?.LogInformation($"Initialized {nameof(TelemetryPublisherFirestore)}");
Logger?.LogInformation($"ProjectId: {firestoreProjectId}; CollectionName: {firestoreCollection}.");
}