using System.Net.Http; using System; using System.Collections.Generic; using System.Threading.Tasks; using System.Net; namespace SpotifyAPI.Web.Http { public class APIConnector : IAPIConnector { private readonly Uri _baseAddress; private readonly IAuthenticator? _authenticator; private readonly IJSONSerializer _jsonSerializer; private readonly IHTTPClient _httpClient; private readonly IRetryHandler? _retryHandler; private readonly IHTTPLogger? _httpLogger; public event EventHandler? ResponseReceived; public APIConnector(Uri baseAddress, IAuthenticator authenticator) : this(baseAddress, authenticator, new NewtonsoftJSONSerializer(), new NetHttpClient(), null, null) { } public APIConnector( Uri baseAddress, IAuthenticator? authenticator, IJSONSerializer jsonSerializer, IHTTPClient httpClient, IRetryHandler? retryHandler, IHTTPLogger? httpLogger) { _baseAddress = baseAddress; _authenticator = authenticator; _jsonSerializer = jsonSerializer; _httpClient = httpClient; _retryHandler = retryHandler; _httpLogger = httpLogger; } public Task Delete(Uri uri) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Delete); } public Task Delete(Uri uri, IDictionary? parameters) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Delete, parameters); } public Task Delete(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Delete, parameters, body); } public async Task Delete(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); var response = await SendAPIRequestDetailed(uri, HttpMethod.Delete, parameters, body).ConfigureAwait(false); return response.StatusCode; } public Task Get(Uri uri) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Get); } public Task Get(Uri uri, IDictionary? parameters) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Get, parameters); } public async Task Get(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); var response = await SendAPIRequestDetailed(uri, HttpMethod.Get, parameters, body).ConfigureAwait(false); return response.StatusCode; } public Task Post(Uri uri) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Post); } public Task Post(Uri uri, IDictionary? parameters) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Post, parameters); } public Task Post(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Post, parameters, body); } public Task Post(Uri uri, IDictionary? parameters, object? body, Dictionary? headers) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Post, parameters, body, headers); } public async Task Post(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); var response = await SendAPIRequestDetailed(uri, HttpMethod.Post, parameters, body).ConfigureAwait(false); return response.StatusCode; } public Task Put(Uri uri) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Put); } public Task Put(Uri uri, IDictionary? parameters) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Put, parameters); } public Task Put(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); return SendAPIRequest(uri, HttpMethod.Put, parameters, body); } public async Task Put(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); var response = await SendAPIRequestDetailed(uri, HttpMethod.Put, parameters, body).ConfigureAwait(false); return response.StatusCode; } public async Task PutRaw(Uri uri, IDictionary? parameters, object? body) { Ensure.ArgumentNotNull(uri, nameof(uri)); var response = await SendRawRequest(uri, HttpMethod.Put, parameters, body).ConfigureAwait(false); return response.StatusCode; } public void SetRequestTimeout(TimeSpan timeout) { _httpClient.SetRequestTimeout(timeout); } private IRequest CreateRequest( Uri uri, HttpMethod method, IDictionary? parameters, object? body, IDictionary? headers ) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(method, nameof(method)); return new Request( _baseAddress, uri, method, headers ?? new Dictionary(), parameters ?? new Dictionary()) { Body = body }; } private async Task> DoSerializedRequest(IRequest request) { _jsonSerializer.SerializeRequest(request); var response = await DoRequest(request).ConfigureAwait(false); return _jsonSerializer.DeserializeResponse(response); } private async Task DoRequest(IRequest request) { await ApplyAuthenticator(request).ConfigureAwait(false); _httpLogger?.OnRequest(request); IResponse response = await _httpClient.DoRequest(request).ConfigureAwait(false); _httpLogger?.OnResponse(response); ResponseReceived?.Invoke(this, response); if (_retryHandler != null) { response = await _retryHandler.HandleRetry(request, response, async (newRequest) => { await ApplyAuthenticator(request).ConfigureAwait(false); var newResponse = await _httpClient.DoRequest(request).ConfigureAwait(false); _httpLogger?.OnResponse(newResponse); ResponseReceived?.Invoke(this, response); return newResponse; }).ConfigureAwait(false); } ProcessErrors(response); return response; } private async Task ApplyAuthenticator(IRequest request) { #if NETSTANDARD2_0 if (_authenticator != null && !request.Endpoint.IsAbsoluteUri || request.Endpoint.AbsoluteUri.Contains("https://api.spotify.com")) #else if (_authenticator != null && !request.Endpoint.IsAbsoluteUri || request.Endpoint.AbsoluteUri.Contains("https://api.spotify.com", StringComparison.InvariantCulture)) #endif { await _authenticator!.Apply(request, this).ConfigureAwait(false); } } public Task SendRawRequest( Uri uri, HttpMethod method, IDictionary? parameters = null, object? body = null, IDictionary? headers = null ) { var request = CreateRequest(uri, method, parameters, body, headers); return DoRequest(request); } public async Task SendAPIRequest( Uri uri, HttpMethod method, IDictionary? parameters = null, object? body = null, IDictionary? headers = null ) { var request = CreateRequest(uri, method, parameters, body, headers); IAPIResponse apiResponse = await DoSerializedRequest(request).ConfigureAwait(false); return apiResponse.Body!; } public async Task SendAPIRequestDetailed( Uri uri, HttpMethod method, IDictionary? parameters = null, object? body = null, IDictionary? headers = null ) { var request = CreateRequest(uri, method, parameters, body, headers); var response = await DoSerializedRequest(request).ConfigureAwait(false); return response.Response; } private static void ProcessErrors(IResponse response) { Ensure.ArgumentNotNull(response, nameof(response)); if ((int)response.StatusCode >= 200 && (int)response.StatusCode < 400) { return; } switch (response.StatusCode) { case HttpStatusCode.Unauthorized: throw new APIUnauthorizedException(response); case (HttpStatusCode)429: // TODO: Remove hack once .netstandard 2.0 is not supported throw new APITooManyRequestsException(response); default: throw new APIException(response); } } } }