Skip to content

Commit

Permalink
Ref alphagov#176 - Added support for cancellation tokens. Fixed alpha…
Browse files Browse the repository at this point in the history
…gov#130 - await rather than accessing task result.
  • Loading branch information
euantorano committed Sep 27, 2023
1 parent 67ff623 commit 03c3acb
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 81 deletions.
12 changes: 6 additions & 6 deletions src/GovukNotify.Tests/GovukNotify.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
<PackageReference Include="Moq" Version="4.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
<PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
<PackageReference Include="NUnit" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="Moq" Version="4.20.69" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
</ItemGroup>

<ItemGroup>
Expand Down
76 changes: 48 additions & 28 deletions src/GovukNotify/Client/BaseClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Notify.Client
Expand Down Expand Up @@ -34,55 +35,71 @@ public BaseClient(IHttpClient client, string apiKey, string baseUrl = "https://a
this.client.BaseAddress = ValidateBaseUri(BaseUrl);
this.client.AddContentHeader("application/json");

var productVersion = typeof(BaseClient).GetTypeInfo().Assembly.GetName().Version.ToString();
var productVersion = typeof(BaseClient).GetTypeInfo().Assembly.GetName().Version?.ToString();
this.client.AddUserAgent(NOTIFY_CLIENT_NAME + productVersion);
}

public async Task<string> GET(string url)
public async Task<string> GET(string url, CancellationToken cancellationToken = default)
{
return await MakeRequest(url, HttpMethod.Get).ConfigureAwait(false);
return await MakeRequest(url, HttpMethod.Get, cancellationToken: cancellationToken)
.ConfigureAwait(false);
}

public async Task<string> POST(string url, string json)
public async Task<string> POST(string url, string json, CancellationToken cancellationToken = default)
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
return await MakeRequest(url, HttpMethod.Post, content).ConfigureAwait(false);
using var content = new StringContent(json, Encoding.UTF8, "application/json");
return await MakeRequest(url, HttpMethod.Post, content, cancellationToken: cancellationToken)
.ConfigureAwait(false);
}

public async Task<byte[]> GETBytes(string url)
public async Task<byte[]> GETBytes(string url, CancellationToken cancellationToken = default)
{
return await MakeRequestBytes(url, HttpMethod.Get).ConfigureAwait(false);
return await MakeRequestBytes(url, HttpMethod.Get, cancellationToken: cancellationToken)
.ConfigureAwait(false);
}

public async Task<byte[]> MakeRequestBytes(string url, HttpMethod method, HttpContent content = null) {
var response = SendRequest(url, method, content).Result;

var responseContent = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);

public async Task<byte[]> MakeRequestBytes(string url, HttpMethod method, HttpContent content = null, CancellationToken cancellationToken = default) {
var response = await SendRequest(url, method, content, cancellationToken);

#if NET6_0_OR_GREATER
var responseContent = await response.Content.ReadAsByteArrayAsync(cancellationToken)
.ConfigureAwait(false);
#else
var responseContent = await response.Content.ReadAsByteArrayAsync()
.ConfigureAwait(false);
#endif

if (!response.IsSuccessStatusCode)
{
// if there was an error, rather than a binary pdf, the http body will be a json error message, so
// encode the bytes as UTF8
HandleHTTPErrors(response, Encoding.UTF8.GetString(responseContent));
BaseClient.HandleHTTPErrors(response, Encoding.UTF8.GetString(responseContent));
}
return responseContent;

return responseContent;
}

public async Task<string> MakeRequest(string url, HttpMethod method, HttpContent content = null)
public async Task<string> MakeRequest(string url, HttpMethod method, HttpContent content = null, CancellationToken cancellationToken = default)
{
var response = SendRequest(url, method, content).Result;

var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

var response = await SendRequest(url, method, content, cancellationToken);

#if NET6_0_OR_GREATER
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken)
.ConfigureAwait(false);
#else
var responseContent = await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
#endif

if (!response.IsSuccessStatusCode)
{
HandleHTTPErrors(response, responseContent);
}

return responseContent;
}

private async Task<HttpResponseMessage> SendRequest(string url, HttpMethod method, HttpContent content)
private async Task<HttpResponseMessage> SendRequest(string url, HttpMethod method, HttpContent content, CancellationToken cancellationToken = default)
{
var request = new HttpRequestMessage(method, url);

Expand All @@ -98,15 +115,15 @@ private async Task<HttpResponseMessage> SendRequest(string url, HttpMethod metho

try
{
response = await this.client.SendAsync(request).ConfigureAwait(false);
response = await this.client.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (AggregateException ae)
{
ae.Handle(x =>
{
if (x is HttpRequestException)
{
throw new NotifyClientException(x.InnerException.Message);
throw new NotifyClientException(x.InnerException?.Message ?? x.Message);
}
throw x;
});
Expand All @@ -115,7 +132,7 @@ private async Task<HttpResponseMessage> SendRequest(string url, HttpMethod metho
return response;
}

private void HandleHTTPErrors(HttpResponseMessage response, string errorResponseContent)
private static void HandleHTTPErrors(HttpResponseMessage response, string errorResponseContent)
{
try
{
Expand All @@ -129,8 +146,12 @@ private void HandleHTTPErrors(HttpResponseMessage response, string errorResponse
}

public Tuple<string, string> ExtractServiceIdAndApiKey(string fromApiKey)
{
if (fromApiKey.Length < 74 || string.IsNullOrWhiteSpace(fromApiKey) || fromApiKey.Contains(" "))
{
#if NET6_0_OR_GREATER
if (string.IsNullOrWhiteSpace(fromApiKey) || fromApiKey.Length < 74 || fromApiKey.Contains(' '))
#else
if (string.IsNullOrWhiteSpace(fromApiKey) || fromApiKey.Length < 74 || fromApiKey.Contains(" "))
#endif
{
throw new NotifyAuthException("The API Key provided is invalid. Please ensure you are using a v2 API Key that is not empty or null");
}
Expand All @@ -144,15 +165,14 @@ public Tuple<string, string> ExtractServiceIdAndApiKey(string fromApiKey)
public Uri ValidateBaseUri(string baseUrl)
{
var result = Uri.TryCreate(baseUrl, UriKind.Absolute, out var uriResult)
&& (uriResult.Scheme == "http" || uriResult.Scheme == "https");
&& uriResult.Scheme is "http" or "https";

if (!result)
{
throw new NotifyAuthException("Invalid URL provided");
}

return uriResult;

}

public string GetUserAgent()
Expand Down
27 changes: 24 additions & 3 deletions src/GovukNotify/Client/HttpClientWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Notify.Client
{
public class HttpClientWrapper : IHttpClient
{
private readonly HttpClient _client;

private Uri _baseAddress;

private bool _disposed;

public Uri BaseAddress
{
get => _baseAddress;
Expand All @@ -28,12 +32,29 @@ public HttpClientWrapper(HttpClient client)

public void Dispose()
{
_client.Dispose();
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

if (disposing)
{
_client.Dispose();
}

_disposed = true;
}

public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request)
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
{
return await _client.SendAsync(request).ConfigureAwait(false);
return await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
.ConfigureAwait(false);
}

public void SetClientBaseAddress()
Expand Down
Loading

0 comments on commit 03c3acb

Please sign in to comment.