2020-05-01 19:05:28 +01:00
|
|
|
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
|
|
|
|
{
|
2020-05-02 12:04:26 +01:00
|
|
|
private readonly Uri _baseAddress;
|
2020-05-25 17:00:38 +01:00
|
|
|
private readonly IAuthenticator? _authenticator;
|
2020-05-02 12:04:26 +01:00
|
|
|
private readonly IJSONSerializer _jsonSerializer;
|
|
|
|
private readonly IHTTPClient _httpClient;
|
2020-05-25 17:00:38 +01:00
|
|
|
private readonly IRetryHandler? _retryHandler;
|
|
|
|
private readonly IHTTPLogger? _httpLogger;
|
2020-05-01 19:05:28 +01:00
|
|
|
|
2020-05-30 22:20:42 +01:00
|
|
|
public event EventHandler<IResponse>? ResponseReceived;
|
|
|
|
|
2020-05-01 19:05:28 +01:00
|
|
|
public APIConnector(Uri baseAddress, IAuthenticator authenticator) :
|
2020-05-03 12:00:50 +01:00
|
|
|
this(baseAddress, authenticator, new NewtonsoftJSONSerializer(), new NetHttpClient(), null, null)
|
2020-05-01 19:05:28 +01:00
|
|
|
{ }
|
2020-05-02 18:57:31 +01:00
|
|
|
public APIConnector(
|
|
|
|
Uri baseAddress,
|
2020-05-25 17:00:38 +01:00
|
|
|
IAuthenticator? authenticator,
|
2020-05-02 18:57:31 +01:00
|
|
|
IJSONSerializer jsonSerializer,
|
|
|
|
IHTTPClient httpClient,
|
2020-05-25 17:00:38 +01:00
|
|
|
IRetryHandler? retryHandler,
|
|
|
|
IHTTPLogger? httpLogger)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
_baseAddress = baseAddress;
|
|
|
|
_authenticator = authenticator;
|
|
|
|
_jsonSerializer = jsonSerializer;
|
|
|
|
_httpClient = httpClient;
|
2020-05-02 18:57:31 +01:00
|
|
|
_retryHandler = retryHandler;
|
2020-05-03 12:00:50 +01:00
|
|
|
_httpLogger = httpLogger;
|
2020-05-01 19:05:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public Task<T> Delete<T>(Uri uri)
|
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Delete);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Delete<T>(Uri uri, IDictionary<string, string>? parameters)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Delete, parameters);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Delete<T>(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Delete, parameters, body);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public async Task<HttpStatusCode> Delete(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-04 22:02:53 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
2020-05-05 04:26:37 +01:00
|
|
|
var response = await SendAPIRequestDetailed(uri, HttpMethod.Delete, parameters, body).ConfigureAwait(false);
|
2020-05-04 22:02:53 +01:00
|
|
|
return response.StatusCode;
|
|
|
|
}
|
|
|
|
|
2020-05-01 19:05:28 +01:00
|
|
|
public Task<T> Get<T>(Uri uri)
|
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Get);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Get<T>(Uri uri, IDictionary<string, string>? parameters)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Get, parameters);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public async Task<HttpStatusCode> Get(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-07 17:03:20 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
var response = await SendAPIRequestDetailed(uri, HttpMethod.Get, parameters, body).ConfigureAwait(false);
|
|
|
|
return response.StatusCode;
|
|
|
|
}
|
|
|
|
|
2020-05-01 19:05:28 +01:00
|
|
|
public Task<T> Post<T>(Uri uri)
|
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Post);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Post<T>(Uri uri, IDictionary<string, string>? parameters)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Post<T>(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
2020-05-03 00:00:35 +01:00
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters, body);
|
2020-05-01 19:05:28 +01:00
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Post<T>(Uri uri, IDictionary<string, string>? parameters, object? body, Dictionary<string, string>? headers)
|
2020-05-13 22:49:54 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters, body, headers);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public async Task<HttpStatusCode> Post(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-04 22:02:53 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
2020-05-05 04:26:37 +01:00
|
|
|
var response = await SendAPIRequestDetailed(uri, HttpMethod.Post, parameters, body).ConfigureAwait(false);
|
2020-05-04 22:02:53 +01:00
|
|
|
return response.StatusCode;
|
|
|
|
}
|
|
|
|
|
2020-05-01 19:05:28 +01:00
|
|
|
public Task<T> Put<T>(Uri uri)
|
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Put);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Put<T>(Uri uri, IDictionary<string, string>? parameters)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Put, parameters);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public Task<T> Put<T>(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
|
|
|
return SendAPIRequest<T>(uri, HttpMethod.Put, parameters, body);
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public async Task<HttpStatusCode> Put(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-03 08:06:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
2020-05-05 04:26:37 +01:00
|
|
|
var response = await SendAPIRequestDetailed(uri, HttpMethod.Put, parameters, body).ConfigureAwait(false);
|
2020-05-03 08:06:28 +01:00
|
|
|
return response.StatusCode;
|
|
|
|
}
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
public async Task<HttpStatusCode> PutRaw(Uri uri, IDictionary<string, string>? parameters, object? body)
|
2020-05-03 07:10:41 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
|
2020-05-05 04:26:37 +01:00
|
|
|
var response = await SendRawRequest(uri, HttpMethod.Put, parameters, body).ConfigureAwait(false);
|
2020-05-03 07:10:41 +01:00
|
|
|
return response.StatusCode;
|
|
|
|
}
|
|
|
|
|
2020-05-01 19:05:28 +01:00
|
|
|
public void SetRequestTimeout(TimeSpan timeout)
|
|
|
|
{
|
|
|
|
_httpClient.SetRequestTimeout(timeout);
|
|
|
|
}
|
|
|
|
|
2020-05-03 07:10:41 +01:00
|
|
|
private IRequest CreateRequest(
|
2020-05-01 19:05:28 +01:00
|
|
|
Uri uri,
|
|
|
|
HttpMethod method,
|
2020-05-25 17:00:38 +01:00
|
|
|
IDictionary<string, string>? parameters,
|
|
|
|
object? body,
|
|
|
|
IDictionary<string, string>? headers
|
2020-05-01 19:05:28 +01:00
|
|
|
)
|
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(uri, nameof(uri));
|
|
|
|
Ensure.ArgumentNotNull(method, nameof(method));
|
|
|
|
|
2020-05-25 17:00:38 +01:00
|
|
|
return new Request(
|
|
|
|
_baseAddress,
|
|
|
|
uri,
|
|
|
|
method,
|
|
|
|
headers ?? new Dictionary<string, string>(),
|
|
|
|
parameters ?? new Dictionary<string, string>())
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
2020-05-02 12:04:26 +01:00
|
|
|
Body = body
|
2020-05-01 19:05:28 +01:00
|
|
|
};
|
2020-05-03 07:10:41 +01:00
|
|
|
}
|
2020-05-01 19:05:28 +01:00
|
|
|
|
2020-05-03 08:06:28 +01:00
|
|
|
private async Task<IAPIResponse<T>> DoSerializedRequest<T>(IRequest request)
|
2020-05-03 07:10:41 +01:00
|
|
|
{
|
2020-05-01 19:05:28 +01:00
|
|
|
_jsonSerializer.SerializeRequest(request);
|
2020-05-05 04:26:37 +01:00
|
|
|
var response = await DoRequest(request).ConfigureAwait(false);
|
2020-05-03 07:10:41 +01:00
|
|
|
return _jsonSerializer.DeserializeResponse<T>(response);
|
|
|
|
}
|
|
|
|
|
2020-05-03 08:06:28 +01:00
|
|
|
private async Task<IResponse> DoRequest(IRequest request)
|
2020-05-03 07:10:41 +01:00
|
|
|
{
|
2020-05-14 22:26:40 +01:00
|
|
|
await ApplyAuthenticator(request).ConfigureAwait(false);
|
2020-05-03 12:00:50 +01:00
|
|
|
_httpLogger?.OnRequest(request);
|
2020-05-01 19:05:28 +01:00
|
|
|
IResponse response = await _httpClient.DoRequest(request).ConfigureAwait(false);
|
2020-05-03 12:00:50 +01:00
|
|
|
_httpLogger?.OnResponse(response);
|
2020-05-30 22:20:42 +01:00
|
|
|
ResponseReceived?.Invoke(this, response);
|
2020-05-02 21:48:21 +01:00
|
|
|
if (_retryHandler != null)
|
2020-05-02 18:57:31 +01:00
|
|
|
{
|
2020-05-05 14:30:00 +01:00
|
|
|
response = await _retryHandler.HandleRetry(request, response, async (newRequest) =>
|
2020-05-02 21:48:21 +01:00
|
|
|
{
|
2020-05-14 22:26:40 +01:00
|
|
|
await ApplyAuthenticator(request).ConfigureAwait(false);
|
2020-05-03 12:00:50 +01:00
|
|
|
var newResponse = await _httpClient.DoRequest(request).ConfigureAwait(false);
|
|
|
|
_httpLogger?.OnResponse(newResponse);
|
2020-05-30 22:20:42 +01:00
|
|
|
ResponseReceived?.Invoke(this, response);
|
2020-05-03 12:00:50 +01:00
|
|
|
return newResponse;
|
2020-05-05 14:30:00 +01:00
|
|
|
}).ConfigureAwait(false);
|
2020-05-02 21:48:21 +01:00
|
|
|
}
|
2020-05-01 19:05:28 +01:00
|
|
|
ProcessErrors(response);
|
2020-05-03 07:10:41 +01:00
|
|
|
return response;
|
|
|
|
}
|
2020-05-01 19:05:28 +01:00
|
|
|
|
2020-05-14 22:26:40 +01:00
|
|
|
private async Task ApplyAuthenticator(IRequest request)
|
|
|
|
{
|
2020-05-15 19:06:24 +01:00
|
|
|
if (_authenticator != null
|
|
|
|
&& !request.Endpoint.IsAbsoluteUri
|
|
|
|
|| request.Endpoint.AbsoluteUri.Contains("https://api.spotify.com", StringComparison.InvariantCulture))
|
2020-05-14 22:26:40 +01:00
|
|
|
{
|
2020-05-25 17:00:38 +01:00
|
|
|
await _authenticator!.Apply(request, this).ConfigureAwait(false);
|
2020-05-14 22:26:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-03 07:10:41 +01:00
|
|
|
public Task<IResponse> SendRawRequest(
|
|
|
|
Uri uri,
|
|
|
|
HttpMethod method,
|
2020-05-25 17:00:38 +01:00
|
|
|
IDictionary<string, string>? parameters = null,
|
|
|
|
object? body = null,
|
|
|
|
IDictionary<string, string>? headers = null
|
2020-05-03 07:10:41 +01:00
|
|
|
)
|
|
|
|
{
|
2020-05-13 22:49:54 +01:00
|
|
|
var request = CreateRequest(uri, method, parameters, body, headers);
|
2020-05-03 08:06:28 +01:00
|
|
|
return DoRequest(request);
|
2020-05-03 07:10:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public async Task<T> SendAPIRequest<T>(
|
|
|
|
Uri uri,
|
|
|
|
HttpMethod method,
|
2020-05-25 17:00:38 +01:00
|
|
|
IDictionary<string, string>? parameters = null,
|
|
|
|
object? body = null,
|
|
|
|
IDictionary<string, string>? headers = null
|
2020-05-03 07:10:41 +01:00
|
|
|
)
|
|
|
|
{
|
2020-05-13 22:49:54 +01:00
|
|
|
var request = CreateRequest(uri, method, parameters, body, headers);
|
2020-05-05 14:30:00 +01:00
|
|
|
IAPIResponse<T> apiResponse = await DoSerializedRequest<T>(request).ConfigureAwait(false);
|
2020-11-13 13:43:00 +00:00
|
|
|
return apiResponse.Body!;
|
2020-05-01 19:05:28 +01:00
|
|
|
}
|
|
|
|
|
2020-05-03 08:06:28 +01:00
|
|
|
public async Task<IResponse> SendAPIRequestDetailed(
|
|
|
|
Uri uri,
|
|
|
|
HttpMethod method,
|
2020-05-25 17:00:38 +01:00
|
|
|
IDictionary<string, string>? parameters = null,
|
|
|
|
object? body = null,
|
|
|
|
IDictionary<string, string>? headers = null
|
2020-05-03 08:06:28 +01:00
|
|
|
)
|
|
|
|
{
|
2020-05-13 22:49:54 +01:00
|
|
|
var request = CreateRequest(uri, method, parameters, body, headers);
|
2020-05-05 14:30:00 +01:00
|
|
|
var response = await DoSerializedRequest<object>(request).ConfigureAwait(false);
|
2020-05-03 08:06:28 +01:00
|
|
|
return response.Response;
|
|
|
|
}
|
|
|
|
|
2020-05-05 14:30:00 +01:00
|
|
|
private static void ProcessErrors(IResponse response)
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
|
|
|
Ensure.ArgumentNotNull(response, nameof(response));
|
|
|
|
|
|
|
|
if ((int)response.StatusCode >= 200 && (int)response.StatusCode < 400)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-26 17:28:44 +00:00
|
|
|
throw response.StatusCode switch
|
2020-05-01 19:05:28 +01:00
|
|
|
{
|
2020-12-26 17:28:44 +00:00
|
|
|
HttpStatusCode.Unauthorized => new APIUnauthorizedException(response),
|
|
|
|
// TODO: Remove hack once .netstandard 2.0 is not supported
|
|
|
|
(HttpStatusCode)429 => new APITooManyRequestsException(response),
|
|
|
|
_ => new APIException(response),
|
|
|
|
};
|
2020-05-01 19:05:28 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|