#nullable enable using Swan.Formatters; using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Headers; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Swan.Net { /// /// Represents a HttpClient with extended methods to use with JSON payloads /// and bearer tokens authentication. /// public static class JsonClient { private const String JsonMimeType = "application/json"; private const String FormType = "application/x-www-form-urlencoded"; private static readonly HttpClient HttpClient = new HttpClient(); /// /// Post a object as JSON with optional authorization token. /// /// The type of response object. /// The request URI. /// The payload. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested type. /// public static async Task Post(Uri requestUri, Object payload, String? authorization = null, CancellationToken cancellationToken = default) where T : notnull { String jsonString = await PostString(requestUri, payload, authorization, cancellationToken).ConfigureAwait(false); return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize(jsonString) : default; } /// /// Posts the specified URL. /// /// The request URI. /// The payload. /// The authorization. /// The cancellation token. /// /// A task with a result as a collection of key/value pairs. /// public static async Task?> Post(Uri requestUri, Object payload, String? authorization = null, CancellationToken cancellationToken = default) { String jsonString = await PostString(requestUri, payload, authorization, cancellationToken).ConfigureAwait(false); return String.IsNullOrWhiteSpace(jsonString) ? default : Json.Deserialize(jsonString) as IDictionary; } /// /// Posts the specified URL. /// /// The request URI. /// The payload. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested string. /// /// url. /// Error POST JSON. public static Task PostString(Uri requestUri, Object payload, String? authorization = null, CancellationToken cancellationToken = default) => SendAsync(HttpMethod.Post, requestUri, payload, authorization, cancellationToken); /// /// Puts the specified URL. /// /// The type of response object. /// The request URI. /// The payload. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested type. /// public static async Task Put(Uri requestUri, Object payload, String? authorization = null, CancellationToken ct = default) where T : notnull { String jsonString = await PutString(requestUri, payload, authorization, ct).ConfigureAwait(false); return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize(jsonString) : default; } /// /// Puts the specified URL. /// /// The request URI. /// The payload. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested collection of key/value pairs. /// public static async Task?> Put(Uri requestUri, Object payload, String? authorization = null, CancellationToken cancellationToken = default) { Object response = await Put(requestUri, payload, authorization, cancellationToken).ConfigureAwait(false); return response as IDictionary; } /// /// Puts as string. /// /// The request URI. /// The payload. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested string. /// /// url. /// Error PUT JSON. public static Task PutString(Uri requestUri, Object payload, String? authorization = null, CancellationToken ct = default) => SendAsync(HttpMethod.Put, requestUri, payload, authorization, ct); /// /// Gets as string. /// /// The request URI. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested string. /// /// url. /// Error GET JSON. public static Task GetString(Uri requestUri, String? authorization = null, CancellationToken ct = default) => GetString(requestUri, null, authorization, ct); /// /// Gets the string. /// /// The URI. /// The headers. /// The authorization. /// The ct. /// /// A task with a result of the requested string. /// public static async Task GetString(Uri uri, IDictionary>? headers, String? authorization = null, CancellationToken ct = default) { HttpContent response = await GetHttpContent(uri, ct, authorization, headers).ConfigureAwait(false); return await response.ReadAsStringAsync().ConfigureAwait(false); } /// /// Gets the specified URL and return the JSON data as object /// with optional authorization token. /// /// The response type. /// The request URI. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested type. /// public static async Task Get(Uri requestUri, String? authorization = null, CancellationToken ct = default) where T : notnull { String jsonString = await GetString(requestUri, authorization, ct).ConfigureAwait(false); return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize(jsonString) : default; } /// /// Gets the specified URL and return the JSON data as object /// with optional authorization token. /// /// The response type. /// The request URI. /// The headers. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested type. /// public static async Task Get(Uri requestUri, IDictionary>? headers, String? authorization = null, CancellationToken ct = default) where T : notnull { String jsonString = await GetString(requestUri, headers, authorization, ct).ConfigureAwait(false); return !String.IsNullOrEmpty(jsonString) ? Json.Deserialize(jsonString) : default; } /// /// Gets the binary. /// /// The request URI. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested byte array. /// /// url. /// Error GET Binary. public static async Task GetBinary(Uri requestUri, String? authorization = null, CancellationToken ct = default) { HttpContent response = await GetHttpContent(requestUri, ct, authorization).ConfigureAwait(false); return await response.ReadAsByteArrayAsync().ConfigureAwait(false); } /// /// Authenticate against a web server using Bearer Token. /// /// The request URI. /// The username. /// The password. /// The cancellation token. /// /// A task with a Dictionary with authentication data. /// /// url /// or /// username. /// Error Authenticating. public static async Task?> Authenticate(Uri requestUri, String username, String password, CancellationToken ct = default) { if(String.IsNullOrWhiteSpace(username)) { throw new ArgumentNullException(nameof(username)); } // ignore empty password for now String content = $"grant_type=password&username={username}&password={password}"; using StringContent requestContent = new StringContent(content, Encoding.UTF8, FormType); HttpResponseMessage response = await HttpClient.PostAsync(requestUri, requestContent, ct).ConfigureAwait(false); if(!response.IsSuccessStatusCode) { throw new SecurityException($"Error Authenticating. Status code: {response.StatusCode}."); } String jsonPayload = await response.Content.ReadAsStringAsync().ConfigureAwait(false); return Json.Deserialize(jsonPayload) as IDictionary; } /// /// Posts the file. /// /// The request URI. /// The buffer. /// Name of the file. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested string. /// public static Task PostFileString(Uri requestUri, Byte[] buffer, String fileName, String? authorization = null, CancellationToken ct = default) => PostString(requestUri, new { Filename = fileName, Data = buffer }, authorization, ct); /// /// Posts the file. /// /// The response type. /// The request URI. /// The buffer. /// Name of the file. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested string. /// public static Task PostFile(Uri requestUri, Byte[] buffer, String fileName, String? authorization = null, CancellationToken ct = default) where T : notnull => Post(requestUri, new { Filename = fileName, Data = buffer }, authorization, ct); /// /// Sends the asynchronous request. /// /// The method. /// The request URI. /// The payload. /// The authorization. /// The cancellation token. /// /// A task with a result of the requested string. /// /// requestUri. /// Error {method} JSON. public static async Task SendAsync(HttpMethod method, Uri requestUri, Object payload, String? authorization = null, CancellationToken ct = default) { using HttpResponseMessage response = await GetResponse(requestUri, authorization, null, payload, method, ct).ConfigureAwait(false); if(!response.IsSuccessStatusCode) { throw new JsonRequestException( $"Error {method} JSON", (Int32)response.StatusCode, await response.Content.ReadAsStringAsync().ConfigureAwait(false)); } return await response.Content.ReadAsStringAsync().ConfigureAwait(false); } private static async Task GetHttpContent(Uri uri, CancellationToken ct, String? authorization = null, IDictionary>? headers = null) { HttpResponseMessage response = await GetResponse(uri, authorization, headers, ct: ct).ConfigureAwait(false); return response.IsSuccessStatusCode ? response.Content : throw new JsonRequestException("Error GET", (Int32)response.StatusCode); } private static async Task GetResponse(Uri uri, String? authorization, IDictionary>? headers, Object? payload = null, HttpMethod? method = default, CancellationToken ct = default) { if(uri == null) { throw new ArgumentNullException(nameof(uri)); } using HttpRequestMessage requestMessage = new HttpRequestMessage(method ?? HttpMethod.Get, uri); if(!String.IsNullOrWhiteSpace(authorization)) { requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authorization); } if(headers != null) { foreach(KeyValuePair> header in headers) { requestMessage.Headers.Add(header.Key, header.Value); } } if(payload != null && requestMessage.Method != HttpMethod.Get) { requestMessage.Content = new StringContent(Json.Serialize(payload), Encoding.UTF8, JsonMimeType); } return await HttpClient.SendAsync(requestMessage, ct).ConfigureAwait(false); } } }