Spotify.NET/SpotifyAPI.Web/Http/APIConnector.cs

294 lines
10 KiB
C#
Raw Normal View History

2020-05-01 19:05:28 +01:00
using System;
using System.Collections.Generic;
using System.Net;
2022-11-27 12:29:35 +00:00
using System.Net.Http;
using System.Threading;
2022-11-27 12:29:35 +00:00
using System.Threading.Tasks;
2020-05-01 19:05:28 +01:00
namespace SpotifyAPI.Web.Http
{
public class APIConnector : IAPIConnector
{
private readonly Uri _baseAddress;
2020-05-25 17:00:38 +01:00
private readonly IAuthenticator? _authenticator;
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
public event EventHandler<IResponse>? ResponseReceived;
2020-05-01 19:05:28 +01:00
public APIConnector(Uri baseAddress, IAuthenticator authenticator) :
this(baseAddress, authenticator, new NewtonsoftJSONSerializer(), new NetHttpClient(), null, null)
2020-05-01 19:05:28 +01:00
{ }
public APIConnector(
Uri baseAddress,
2020-05-25 17:00:38 +01:00
IAuthenticator? authenticator,
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;
_retryHandler = retryHandler;
_httpLogger = httpLogger;
2020-05-01 19:05:28 +01:00
}
public Task<T> Delete<T>(Uri uri, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Delete, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Delete<T>(Uri uri, IDictionary<string, string>? parameters, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Delete, parameters, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Delete<T>(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Delete, parameters, body, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public async Task<HttpStatusCode> Delete(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-04 22:02:53 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await SendAPIRequestDetailed(uri, HttpMethod.Delete, parameters, body, cancel: cancel).ConfigureAwait(false);
2020-05-04 22:02:53 +01:00
return response.StatusCode;
}
public Task<T> Get<T>(Uri uri, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Get, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Get<T>(Uri uri, IDictionary<string, string>? parameters, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Get, parameters, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public async Task<HttpStatusCode> Get(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-07 17:03:20 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await SendAPIRequestDetailed(uri, HttpMethod.Get, parameters, body, cancel: cancel).ConfigureAwait(false);
2020-05-07 17:03:20 +01:00
return response.StatusCode;
}
public Task<T> Post<T>(Uri uri, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Post, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Post<T>(Uri uri, IDictionary<string, string>? parameters, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Post<T>(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters, body, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Post<T>(Uri uri, IDictionary<string, string>? parameters, object? body, Dictionary<string, string>? headers, CancellationToken cancel)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters, body, headers, cancel: cancel);
}
public async Task<HttpStatusCode> Post(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-04 22:02:53 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await SendAPIRequestDetailed(uri, HttpMethod.Post, parameters, body, cancel: cancel).ConfigureAwait(false);
2020-05-04 22:02:53 +01:00
return response.StatusCode;
}
public Task<T> Put<T>(Uri uri, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Put, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Put<T>(Uri uri, IDictionary<string, string>? parameters, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Put, parameters, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public Task<T> Put<T>(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-01 19:05:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Put, parameters, body, cancel: cancel);
2020-05-01 19:05:28 +01:00
}
public async Task<HttpStatusCode> Put(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-03 08:06:28 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await SendAPIRequestDetailed(uri, HttpMethod.Put, parameters, body, cancel: cancel).ConfigureAwait(false);
2020-05-03 08:06:28 +01:00
return response.StatusCode;
}
public async Task<HttpStatusCode> PutRaw(Uri uri, IDictionary<string, string>? parameters, object? body, CancellationToken cancel)
2020-05-03 07:10:41 +01:00
{
Ensure.ArgumentNotNull(uri, nameof(uri));
var response = await SendRawRequest(uri, HttpMethod.Put, parameters, body, cancel: cancel).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
{
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
private async Task<IAPIResponse<T>> DoSerializedRequest<T>(IRequest request, CancellationToken cancel)
2020-05-03 07:10:41 +01:00
{
2020-05-01 19:05:28 +01:00
_jsonSerializer.SerializeRequest(request);
var response = await DoRequest(request, cancel).ConfigureAwait(false);
2020-05-03 07:10:41 +01:00
return _jsonSerializer.DeserializeResponse<T>(response);
}
private async Task<IResponse> DoRequest(IRequest request, CancellationToken cancel)
2020-05-03 07:10:41 +01:00
{
2020-05-14 22:26:40 +01:00
await ApplyAuthenticator(request).ConfigureAwait(false);
_httpLogger?.OnRequest(request);
IResponse response = await _httpClient.DoRequest(request, cancel).ConfigureAwait(false);
_httpLogger?.OnResponse(response);
ResponseReceived?.Invoke(this, response);
2020-05-02 21:48:21 +01:00
if (_retryHandler != null)
{
response = await _retryHandler.HandleRetry(request, response, async (newRequest, ct) =>
2020-05-02 21:48:21 +01:00
{
2020-05-14 22:26:40 +01:00
await ApplyAuthenticator(request).ConfigureAwait(false);
var newResponse = await _httpClient.DoRequest(request, ct).ConfigureAwait(false);
_httpLogger?.OnResponse(newResponse);
ResponseReceived?.Invoke(this, response);
return newResponse;
}, cancel).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
2023-11-03 22:23:54 +00:00
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
2020-05-15 19:06:24 +01:00
|| request.Endpoint.AbsoluteUri.Contains("https://api.spotify.com", StringComparison.InvariantCulture))
2023-11-03 22:23:54 +00:00
#else
|| request.Endpoint.AbsoluteUri.Contains("https://api.spotify.com"))
#endif
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,
CancellationToken cancel = default
2020-05-03 07:10:41 +01:00
)
{
var request = CreateRequest(uri, method, parameters, body, headers);
return DoRequest(request, cancel);
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,
CancellationToken cancel = default
2020-05-03 07:10:41 +01:00
)
{
var request = CreateRequest(uri, method, parameters, body, headers);
IAPIResponse<T> apiResponse = await DoSerializedRequest<T>(request, cancel).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,
CancellationToken cancel = default
2020-05-03 08:06:28 +01:00
)
{
var request = CreateRequest(uri, method, parameters, body, headers);
var response = await DoSerializedRequest<object>(request, cancel).ConfigureAwait(false);
2020-05-03 08:06:28 +01:00
return response.Response;
}
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;
}
throw response.StatusCode switch
2020-05-01 19:05:28 +01: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
}
}
}