-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added support for cancellation tokens #178
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
using System.Net.Http; | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Notify.Client | ||
|
@@ -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 | ||
Comment on lines
+64
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing cancellation tokens when reading content is only supported in .NET standard 2.1 and up. This project currently targets .net standard 2.0 and .net 6, so this conditional compilation should work. |
||
|
||
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 | ||
Comment on lines
+86
to
+92
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, as before this conditional compilation is required due to the target framework. |
||
|
||
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); | ||
|
||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's possible for |
||
} | ||
throw x; | ||
}); | ||
|
@@ -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 | ||
{ | ||
|
@@ -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(" ")) | ||
Comment on lines
+151
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the original code the |
||
#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"); | ||
} | ||
|
@@ -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() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,75 @@ | ||
using Notify.Interfaces; | ||
using System; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Threading.Tasks; | ||
|
||
namespace Notify.Client | ||
{ | ||
public class HttpClientWrapper : IHttpClient | ||
{ | ||
private readonly HttpClient _client; | ||
private Uri _baseAddress; | ||
|
||
public Uri BaseAddress | ||
{ | ||
get => _baseAddress; | ||
set | ||
{ | ||
_baseAddress = value; | ||
SetClientBaseAddress(); | ||
} | ||
} | ||
|
||
public HttpClientWrapper(HttpClient client) | ||
{ | ||
_client = client; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_client.Dispose(); | ||
} | ||
|
||
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request) | ||
{ | ||
return await _client.SendAsync(request).ConfigureAwait(false); | ||
} | ||
|
||
public void SetClientBaseAddress() | ||
{ | ||
_client.BaseAddress = _baseAddress; | ||
} | ||
|
||
public void AddContentHeader(string header) | ||
{ | ||
_client.DefaultRequestHeaders.Accept | ||
.Add(new MediaTypeWithQualityHeaderValue(header)); | ||
} | ||
|
||
public void AddUserAgent(string userAgent) | ||
{ | ||
_client.DefaultRequestHeaders.Add("User-Agent", userAgent); | ||
} | ||
} | ||
} | ||
using Notify.Interfaces; | ||
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; | ||
set | ||
{ | ||
_baseAddress = value; | ||
SetClientBaseAddress(); | ||
} | ||
} | ||
|
||
public HttpClientWrapper(HttpClient client) | ||
{ | ||
_client = client; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
Dispose(true); | ||
} | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
if (_disposed) | ||
{ | ||
return; | ||
} | ||
|
||
if (disposing) | ||
{ | ||
_client.Dispose(); | ||
} | ||
|
||
_disposed = true; | ||
} | ||
|
||
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken = default) | ||
{ | ||
return await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) | ||
.ConfigureAwait(false); | ||
} | ||
|
||
public void SetClientBaseAddress() | ||
{ | ||
_client.BaseAddress = _baseAddress; | ||
} | ||
|
||
public void AddContentHeader(string header) | ||
{ | ||
_client.DefaultRequestHeaders.Accept | ||
.Add(new MediaTypeWithQualityHeaderValue(header)); | ||
} | ||
|
||
public void AddUserAgent(string userAgent) | ||
{ | ||
_client.DefaultRequestHeaders.Add("User-Agent", userAgent); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change is required as there was the possibility of a
NullReferenceException
in the original code - theVersion
attribute may be null.If nullable types were enabled for the project it would likely help catch other similar problems.