diff --git a/NucuCar.Domain/Http/HttpClient.cs b/NucuCar.Domain/Http/MinimalHttpClient.cs similarity index 70% rename from NucuCar.Domain/Http/HttpClient.cs rename to NucuCar.Domain/Http/MinimalHttpClient.cs index 96004e6..ebabe82 100644 --- a/NucuCar.Domain/Http/HttpClient.cs +++ b/NucuCar.Domain/Http/MinimalHttpClient.cs @@ -5,8 +5,8 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using sNetHttp = System.Net.Http; -using sNetHttpHeaders = System.Net.Http.Headers; +using System.Net.Http; +using System.Net.Http.Headers; namespace NucuCar.Domain.Http { @@ -14,7 +14,7 @@ namespace NucuCar.Domain.Http /// 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 + public class MinimalHttpClient : IDisposable { #region Fields @@ -54,26 +54,26 @@ namespace NucuCar.Domain.Http protected int timeout; // ReSharper restore InconsistentNaming - private readonly sNetHttp.HttpClient _httpClient; + private readonly HttpClient _httpClient; #endregion #region Constructors - public HttpClient() + public MinimalHttpClient() { - _httpClient = new sNetHttp.HttpClient(); + _httpClient = new HttpClient(); maxRetries = 3; timeout = 10000; Logger = null; } - public HttpClient(string baseAddress) : this() + public MinimalHttpClient(string baseAddress) : this() { _httpClient.BaseAddress = new Uri(baseAddress); } - public HttpClient(string baseAddress, int maxRetries) : this(baseAddress) + public MinimalHttpClient(string baseAddress, int maxRetries) : this(baseAddress) { MaxRetries = maxRetries; } @@ -82,10 +82,15 @@ namespace NucuCar.Domain.Http #region Public Methods + public void ClearAuthorizationHeader() + { + _httpClient.DefaultRequestHeaders.Authorization = null; + } + public void Authorization(string scheme, string token) { _httpClient.DefaultRequestHeaders.Authorization = - new sNetHttpHeaders.AuthenticationHeaderValue(scheme, token); + new AuthenticationHeaderValue(scheme, token); } public void Authorization(string token) @@ -93,29 +98,29 @@ namespace NucuCar.Domain.Http Authorization("Bearer", token); } - public async Task GetAsync(string path) + public async Task GetAsync(string path) { - var request = _makeRequest(sNetHttp.HttpMethod.Get, path); + var request = _makeRequest(HttpMethod.Get, path); return await SendAsync(request); } - public async Task PostAsync(string path, Dictionary data) + public async Task PostAsync(string path, Dictionary data) { - var request = _makeRequest(sNetHttp.HttpMethod.Post, path); + var request = _makeRequest(HttpMethod.Post, path); request.Content = _makeContent(data); return await SendAsync(request); } - public async Task PutAsync(string path, Dictionary data) + public async Task PutAsync(string path, Dictionary data) { - var request = _makeRequest(sNetHttp.HttpMethod.Put, path); + var request = _makeRequest(HttpMethod.Put, path); request.Content = _makeContent(data); return await SendAsync(request); } - public async Task DeleteAsync(string path, Dictionary data) + public async Task DeleteAsync(string path, Dictionary data) { - var request = _makeRequest(sNetHttp.HttpMethod.Delete, path); + var request = _makeRequest(HttpMethod.Delete, path); request.Content = _makeContent(data); return await SendAsync(request); } @@ -125,23 +130,28 @@ namespace NucuCar.Domain.Http /// /// The request to make. /// - public virtual async Task SendAsync(sNetHttp.HttpRequestMessage requestMessage) + public virtual async Task SendAsync(HttpRequestMessage requestMessage) { var currentRetry = 0; - sNetHttp.HttpResponseMessage responseMessage = null; + HttpResponseMessage responseMessage = null; while (currentRetry < maxRetries) { try { - responseMessage = await _sendAsync(requestMessage); + // We need a request copy because we can't send the same request multiple times. + var requestCopy = new HttpRequestMessage(requestMessage.Method, requestMessage.RequestUri); + requestCopy.Headers.Authorization = requestMessage.Headers.Authorization; + requestCopy.Content = requestMessage.Content; + + responseMessage = await _sendAsync(requestCopy); break; } catch (TaskCanceledException) { Logger?.LogError($"Request timeout for {requestMessage.RequestUri}!"); } - catch (sNetHttp.HttpRequestException e) + catch (HttpRequestException e) { // The request failed due to an underlying issue such as network connectivity, DNS failure, // server certificate validation or timeout. @@ -172,9 +182,9 @@ namespace NucuCar.Domain.Http /// /// A dictionary representing JSON data. /// - private sNetHttp.StringContent _makeContent(Dictionary data) + private StringContent _makeContent(Dictionary data) { - return new sNetHttp.StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json"); + return new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json"); } /// @@ -183,11 +193,11 @@ namespace NucuCar.Domain.Http /// 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) + private HttpRequestMessage _makeRequest(HttpMethod method, string path) { var uri = _httpClient.BaseAddress == null ? new Uri(path) : new Uri(_httpClient.BaseAddress, path); - - var requestMessage = new sNetHttp.HttpRequestMessage + + var requestMessage = new HttpRequestMessage { Method = method, RequestUri = uri @@ -202,17 +212,17 @@ namespace NucuCar.Domain.Http /// /// /// - private async Task _sendAsync(sNetHttp.HttpRequestMessage requestMessage) + private async Task _sendAsync(HttpRequestMessage requestMessage) { var cts = new CancellationTokenSource(); - sNetHttp.HttpResponseMessage response; + HttpResponseMessage response; // Make sure we cancel after a certain timeout. cts.CancelAfter(timeout); try { response = await _httpClient.SendAsync(requestMessage, - sNetHttp.HttpCompletionOption.ResponseContentRead, + HttpCompletionOption.ResponseContentRead, cts.Token); } finally @@ -242,9 +252,9 @@ namespace NucuCar.Domain.Http /// /// Extension used to deserialize the body of a HttpResponseMessage into Json. /// - /// The HttpResponseMessage message. + /// The HttpResponseMessage message. /// A JsonElement. - public static async Task GetJson(this sNetHttp.HttpResponseMessage responseMessage) + public static async Task GetJson(this HttpResponseMessage responseMessage) { return JsonSerializer.Deserialize(await responseMessage.Content.ReadAsStringAsync()); } diff --git a/NucuCar.Domain/Http/MockHttpClient.cs b/NucuCar.Domain/Http/MockMinimalHttpClient.cs similarity index 85% rename from NucuCar.Domain/Http/MockHttpClient.cs rename to NucuCar.Domain/Http/MockMinimalHttpClient.cs index e0fe7a5..58f8a09 100644 --- a/NucuCar.Domain/Http/MockHttpClient.cs +++ b/NucuCar.Domain/Http/MockMinimalHttpClient.cs @@ -4,14 +4,14 @@ using System.Threading.Tasks; namespace NucuCar.Domain.Http { - public class MockHttpClient : Domain.Http.HttpClient + public class MockMinimalHttpClient : MinimalHttpClient { public List SendAsyncArgCalls; public List SendAsyncResponses; private int _sendAsyncCallCounter; - public MockHttpClient(string baseAddress) : base(baseAddress) + public MockMinimalHttpClient(string baseAddress) : base(baseAddress) { _sendAsyncCallCounter = 0; SendAsyncArgCalls = new List(); diff --git a/NucuCar.Telemetry/Publishers/TelemetryPublisherFirestore.cs b/NucuCar.Telemetry/Publishers/TelemetryPublisherFirestore.cs index b64ff40..e256e04 100644 --- a/NucuCar.Telemetry/Publishers/TelemetryPublisherFirestore.cs +++ b/NucuCar.Telemetry/Publishers/TelemetryPublisherFirestore.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging; using NucuCar.Domain.Http; using NucuCar.Domain.Utilities; using NucuCar.Telemetry.Abstractions; -using HttpClient = NucuCar.Domain.Http.HttpClient; namespace NucuCar.Telemetry.Publishers { @@ -29,7 +28,7 @@ namespace NucuCar.Telemetry.Publishers /// public class TelemetryPublisherFirestore : TelemetryPublisher { - protected HttpClient HttpClient; + protected MinimalHttpClient HttpClient; private string _idToken; private DateTime _authorizationExpiryTime; @@ -67,13 +66,15 @@ namespace NucuCar.Telemetry.Publishers // Setup HttpClient var requestUrl = $"https://firestore.googleapis.com/v1/projects/{firestoreProjectId}/" + $"databases/(default)/documents/{firestoreCollection}/"; - HttpClient = new HttpClient(requestUrl) {Timeout = timeout, Logger = Logger}; + HttpClient = new MinimalHttpClient(requestUrl) {Timeout = timeout, Logger = Logger}; Logger?.LogInformation($"Initialized {nameof(TelemetryPublisherFirestore)}"); Logger?.LogInformation($"ProjectId: {firestoreProjectId}; CollectionName: {firestoreCollection}."); } private async Task SetupAuthorization() { + HttpClient.ClearAuthorizationHeader(); + // https://cloud.google.com/identity-platform/docs/use-rest-api#section-sign-in-email-password var requestUrl = $"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={_webApiKey}"; var data = new Dictionary() @@ -82,7 +83,7 @@ namespace NucuCar.Telemetry.Publishers ["password"] = _webPassword, ["returnSecureToken"] = true }; - + var response = await HttpClient.PostAsync(requestUrl, data); if (response?.StatusCode == HttpStatusCode.OK) diff --git a/NucuCar.Telemetry/TelemetryPublisherFactory.cs b/NucuCar.Telemetry/TelemetryPublisherFactory.cs index 946cc78..cd09594 100644 --- a/NucuCar.Telemetry/TelemetryPublisherFactory.cs +++ b/NucuCar.Telemetry/TelemetryPublisherFactory.cs @@ -15,7 +15,7 @@ namespace NucuCar.Telemetry /// Creates an instance of . See /// /// The type of the publisher. - /// Device connection string for Microsoft Azure IoT hub device. + /// Device connection string for the telemetry publisher. /// String that is used to identify the source of the telemetry data. /// An logger instance. /// A instance. @@ -25,7 +25,7 @@ namespace NucuCar.Telemetry Guard.ArgumentNotNullOrWhiteSpace(nameof(connectionString), connectionString); Guard.ArgumentNotNullOrWhiteSpace(nameof(telemetrySource), telemetrySource); Guard.ArgumentNotNull(nameof(logger), logger); - var opts = new TelemetryPublisherOptions() + var opts = new TelemetryPublisherOptions {ConnectionString = connectionString, TelemetrySource = telemetrySource, Logger = logger}; return SpawnPublisher(type, opts); } @@ -40,7 +40,7 @@ namespace NucuCar.Telemetry { Guard.ArgumentNotNullOrWhiteSpace(nameof(connectionString), connectionString); var opts = new TelemetryPublisherOptions() - {ConnectionString = connectionString, TelemetrySource = "TelemetryPublisherAzure"}; + {ConnectionString = connectionString, TelemetrySource = "NucuCar.Sensors"}; return SpawnPublisher(type, opts); } diff --git a/NucuCar.UnitTests/NucuCar.Domain.Telemetry.Tests/TelemetryPublisherFirestoreTest.cs b/NucuCar.UnitTests/NucuCar.Domain.Telemetry.Tests/TelemetryPublisherFirestoreTest.cs index 52dc095..a832816 100644 --- a/NucuCar.UnitTests/NucuCar.Domain.Telemetry.Tests/TelemetryPublisherFirestoreTest.cs +++ b/NucuCar.UnitTests/NucuCar.Domain.Telemetry.Tests/TelemetryPublisherFirestoreTest.cs @@ -9,7 +9,6 @@ using NucuCar.Domain.Http; using NucuCar.Telemetry; using NucuCar.Telemetry.Publishers; using Xunit; -using HttpClient = NucuCar.Domain.Http.HttpClient; namespace NucuCar.UnitTests.NucuCar.Domain.Telemetry.Tests { @@ -25,7 +24,7 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Telemetry.Tests _mockData = new Dictionary(); } - public void SetHttpClient(HttpClient client) + public void SetHttpClient(MinimalHttpClient client) { HttpClient = client; } @@ -57,7 +56,7 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Telemetry.Tests } [Fact] - private void Test_Construct_BadCollectiontName() + private void Test_Construct_BadCollectionName() { // Setup var opts = new TelemetryPublisherOptions() @@ -78,7 +77,7 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Telemetry.Tests ConnectionString = "ProjectId=test;CollectionName=test;WebApiKey=TAPIKEY;WebApiEmail=t@emai.com;WebApiPassword=tpass" }; var publisher = new MockTelemetryPublisherFirestore(opts); - var mockHttpClient = new MockHttpClient("http://testing.com"); + var mockHttpClient = new MockMinimalHttpClient("http://testing.com"); var authResponse = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("{\"idToken\": \"1\",\"expiresIn\": \"3600\"}")}; mockHttpClient.SendAsyncResponses.Add(authResponse); @@ -106,7 +105,7 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Telemetry.Tests ConnectionString = "ProjectId=test;CollectionName=test;WebApiKey=TAPIKEY;WebApiEmail=t@emai.com;WebApiPassword=tpass" }; var publisher = new MockTelemetryPublisherFirestore(opts); - var mockHttpClient = new MockHttpClient("http://testing.com"); + var mockHttpClient = new MockMinimalHttpClient("http://testing.com"); var authResponse = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("{\"idToken\": \"1\",\"expiresIn\": \"3600\"}")}; mockHttpClient.SendAsyncResponses.Add(authResponse); @@ -130,7 +129,7 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Telemetry.Tests ConnectionString = "ProjectId=test;CollectionName=test" }; var publisher = new MockTelemetryPublisherFirestore(opts); - var mockHttpClient = new MockHttpClient("http://testing.com"); + var mockHttpClient = new MockMinimalHttpClient("http://testing.com"); mockHttpClient.SendAsyncResponses.Add(new HttpResponseMessage(HttpStatusCode.OK)); publisher.SetHttpClient(mockHttpClient); @@ -155,7 +154,7 @@ namespace NucuCar.UnitTests.NucuCar.Domain.Telemetry.Tests "ProjectId=test;CollectionName=test;WebApiKey=TAPIKEY;WebApiEmail=t@emai.com;WebApiPassword=tpass" }; var publisher = new MockTelemetryPublisherFirestore(opts); - var mockHttpClient = new MockHttpClient("http://testing.com"); + var mockHttpClient = new MockMinimalHttpClient("http://testing.com"); mockHttpClient.SendAsyncResponses.Add(new HttpResponseMessage(HttpStatusCode.OK) {Content = new StringContent("{\"idToken\": \"1\",\"expiresIn\": \"0\"}")});