Implemented Core client
This commit is contained in:
324
Cloudflare/CloudflareClient.cs
Normal file
324
Cloudflare/CloudflareClient.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AMWD.Net.Api.Cloudflare.Auth;
|
||||
|
||||
namespace AMWD.Net.Api.Cloudflare
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the Core of the Cloudflare API client.
|
||||
/// </summary>
|
||||
public partial class CloudflareClient : ICloudflareClient, IDisposable
|
||||
{
|
||||
private static readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
|
||||
{
|
||||
Culture = CultureInfo.InvariantCulture,
|
||||
Formatting = Formatting.None,
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
private readonly ClientOptions _clientOptions;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
private bool _isDisposed = false;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="emailAddress">The email address of the Cloudflare account.</param>
|
||||
/// <param name="apiKey">The API key of the Cloudflare account.</param>
|
||||
/// <param name="clientOptions">The client options (optional).</param>
|
||||
public CloudflareClient(string emailAddress, string apiKey, ClientOptions clientOptions = null)
|
||||
: this(new ApiKeyAuthentication(emailAddress, apiKey), clientOptions)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="apiToken">The API token.</param>
|
||||
/// <param name="clientOptions">The client options (optional).</param>
|
||||
public CloudflareClient(string apiToken, ClientOptions clientOptions = null)
|
||||
: this(new ApiTokenAuthentication(apiToken), clientOptions)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CloudflareClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="authentication">The authentication information.</param>
|
||||
/// <param name="clientOptions">The client options (optional).</param>
|
||||
public CloudflareClient(IAuthentication authentication, ClientOptions clientOptions = null)
|
||||
{
|
||||
if (authentication == null)
|
||||
throw new ArgumentNullException(nameof(authentication));
|
||||
|
||||
_clientOptions = clientOptions ?? new ClientOptions();
|
||||
ValidateClientOptions();
|
||||
|
||||
_httpClient = CreateHttpClient();
|
||||
authentication.AddHeader(_httpClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the resources used by the <see cref="CloudflareClient"/> object.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
_httpClient.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> GetAsync<TResponse>(string requestPath, IQueryParameterFilter queryFilter = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath, queryFilter);
|
||||
|
||||
var response = await _httpClient.GetAsync(requestUrl, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> PostAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
HttpContent httpRequestContent;
|
||||
if (request is HttpContent httpContent)
|
||||
{
|
||||
httpRequestContent = httpContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
|
||||
httpRequestContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.PostAsync(requestUrl, httpRequestContent, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> PutAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
HttpContent httpRequestContent;
|
||||
if (request is HttpContent httpContent)
|
||||
{
|
||||
httpRequestContent = httpContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
|
||||
httpRequestContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
var response = await _httpClient.PutAsync(requestUrl, httpRequestContent, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> DeleteAsync<TResponse>(string requestPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
var response = await _httpClient.DeleteAsync(requestUrl, cancellationToken).ConfigureAwait(false);
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<CloudflareResponse<TResponse>> PatchAsync<TResponse, TRequest>(string requestPath, TRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
ValidateRequestPath(requestPath);
|
||||
|
||||
string requestUrl = BuildRequestUrl(requestPath);
|
||||
|
||||
HttpContent httpRequestContent;
|
||||
if (request is HttpContent httpContent)
|
||||
{
|
||||
httpRequestContent = httpContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(request, _jsonSerializerSettings);
|
||||
httpRequestContent = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
var response = await _httpClient.PatchAsync(requestUrl, httpRequestContent, cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
var httpRequestMessage = new HttpRequestMessage
|
||||
{
|
||||
Version = HttpVersion.Version11,
|
||||
Method = new HttpMethod("PATCH"),
|
||||
RequestUri = new Uri(requestUrl),
|
||||
Content = httpRequestContent,
|
||||
};
|
||||
var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
|
||||
#endif
|
||||
|
||||
return await GetCloudflareResponse<TResponse>(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException(GetType().FullName);
|
||||
}
|
||||
|
||||
private void ValidateClientOptions()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_clientOptions.BaseUrl))
|
||||
throw new ArgumentNullException(nameof(_clientOptions.BaseUrl));
|
||||
|
||||
if (_clientOptions.Timeout <= TimeSpan.Zero)
|
||||
throw new ArgumentOutOfRangeException(nameof(_clientOptions.Timeout), "Timeout must be positive.");
|
||||
|
||||
if (_clientOptions.MaxRetries < 0 || 10 < _clientOptions.MaxRetries)
|
||||
throw new ArgumentOutOfRangeException(nameof(_clientOptions.MaxRetries), "MaxRetries should be between 0 and 10.");
|
||||
|
||||
if (_clientOptions.UseProxy && _clientOptions.Proxy == null)
|
||||
throw new ArgumentNullException(nameof(_clientOptions.Proxy));
|
||||
}
|
||||
|
||||
private void ValidateRequestPath(string requestPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requestPath))
|
||||
throw new ArgumentNullException(nameof(requestPath));
|
||||
|
||||
if (requestPath.Contains("?"))
|
||||
throw new ArgumentException("Query parameters are not allowed", nameof(requestPath));
|
||||
}
|
||||
|
||||
private HttpClient CreateHttpClient()
|
||||
{
|
||||
string version = typeof(CloudflareClient).Assembly
|
||||
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
|
||||
.InformationalVersion;
|
||||
|
||||
HttpMessageHandler handler;
|
||||
try
|
||||
{
|
||||
handler = new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = _clientOptions.AllowRedirects,
|
||||
UseProxy = _clientOptions.UseProxy,
|
||||
Proxy = _clientOptions.Proxy,
|
||||
};
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
handler = new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = _clientOptions.AllowRedirects,
|
||||
};
|
||||
}
|
||||
|
||||
var client = new HttpClient(handler, true)
|
||||
{
|
||||
BaseAddress = new Uri(_clientOptions.BaseUrl),
|
||||
Timeout = _clientOptions.Timeout,
|
||||
};
|
||||
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("AMWD.CloudflareClient", version));
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
if (_clientOptions.DefaultHeaders.Count > 0)
|
||||
{
|
||||
foreach (var headerKvp in _clientOptions.DefaultHeaders)
|
||||
client.DefaultRequestHeaders.Add(headerKvp.Key, headerKvp.Value);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private async Task<CloudflareResponse<TRes>> GetCloudflareResponse<TRes>(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
string content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
#else
|
||||
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
#endif
|
||||
switch (response.StatusCode)
|
||||
{
|
||||
case HttpStatusCode.Forbidden:
|
||||
case HttpStatusCode.Unauthorized:
|
||||
var errorResponse = JsonConvert.DeserializeObject<CloudflareResponse<object>>(content);
|
||||
throw new AuthenticationException(string.Join(Environment.NewLine, errorResponse.Errors.Select(e => $"{e.Code}: {e.Message}")));
|
||||
|
||||
default:
|
||||
try
|
||||
{
|
||||
return JsonConvert.DeserializeObject<CloudflareResponse<TRes>>(content);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (typeof(TRes) == typeof(string))
|
||||
{
|
||||
object cObj = content.Replace("\\n", Environment.NewLine);
|
||||
return new CloudflareResponse<TRes>
|
||||
{
|
||||
Success = true,
|
||||
ResultInfo = new ResultInfo(),
|
||||
Result = (TRes)cObj,
|
||||
};
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildRequestUrl(string requestPath, IQueryParameterFilter queryFilter = null)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
|
||||
if (_clientOptions.DefaultQueryParams.Count > 0)
|
||||
{
|
||||
foreach (var paramKvp in _clientOptions.DefaultQueryParams)
|
||||
dict[paramKvp.Key] = paramKvp.Value;
|
||||
}
|
||||
|
||||
var queryParams = queryFilter?.GetQueryParameters();
|
||||
if (queryParams?.Count > 0)
|
||||
{
|
||||
foreach (var kvp in queryParams)
|
||||
dict[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
if (dict.Count == 0)
|
||||
return requestPath;
|
||||
|
||||
string[] param = dict.Select(kvp => $"{kvp.Key}={WebUtility.UrlEncode(kvp.Value)}").ToArray();
|
||||
string query = string.Join("&", param);
|
||||
|
||||
return $"{requestPath}?{query}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user