1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-20 12:53:02 +00:00

Squashed commit of the following:

commit 90f285d7f6bcd926ce9ca3d5832b1d70a5eae6ab
Author: JKorf <jankorf91@gmail.com>
Date:   Sun Jun 25 19:51:12 2023 +0200

    Docs

commit 72187035c703d1402b37bd2f4c3e066706f28d67
Author: JKorf <jankorf91@gmail.com>
Date:   Sat Jun 24 16:02:53 2023 +0200

    docs

commit 8411977292f1fb0b6e0705b1ad675b79a5311d90
Author: JKorf <jankorf91@gmail.com>
Date:   Fri Jun 23 18:25:15 2023 +0200

    wip

commit cb7d33aad5d2751104c8b8a6c6eadbf0d36b672c
Author: JKorf <jankorf91@gmail.com>
Date:   Fri Jun 2 19:26:26 2023 +0200

    wip

commit 4359a2d05ea1141cff516dab18f364a6ca854e18
Author: JKorf <jankorf91@gmail.com>
Date:   Wed May 31 20:51:36 2023 +0200

    wip

commit c6adb1b2f728d143f6bd667139c619581122a3c9
Author: JKorf <jankorf91@gmail.com>
Date:   Mon May 1 21:13:47 2023 +0200

    wip

commit 7fee733f82fa6ff574030452f0955c9e817647dd
Author: JKorf <jankorf91@gmail.com>
Date:   Thu Apr 27 13:02:56 2023 +0200

    wip

commit f8057313ffc9b0c31effcda71d35d105ea390971
Author: JKorf <jankorf91@gmail.com>
Date:   Mon Apr 17 21:37:51 2023 +0200

    wip
This commit is contained in:
JKorf
2023-06-25 19:58:46 +02:00
parent 19cc020852
commit 690f2a63e5
74 changed files with 1946 additions and 1826 deletions
+45 -39
View File
@@ -8,8 +8,8 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Requests;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -24,6 +24,7 @@ namespace CryptoExchange.Net
{
/// <inheritdoc />
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
/// <inheritdoc />
public abstract TimeSyncInfo? GetTimeSyncInfo();
@@ -38,36 +39,39 @@ namespace CryptoExchange.Net
/// </summary>
protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
/// <summary>
/// Options for this client
/// </summary>
public new RestApiClientOptions Options => (RestApiClientOptions)base.Options;
/// <summary>
/// List of rate limiters
/// </summary>
internal IEnumerable<IRateLimiter> RateLimiters { get; }
/// <summary>
/// Options
/// </summary>
internal ClientOptions ClientOptions { get; set; }
/// <inheritdoc />
public new RestExchangeOptions ClientOptions => (RestExchangeOptions)base.ClientOptions;
/// <inheritdoc />
public new RestApiOptions ApiOptions => (RestApiOptions)base.ApiOptions;
/// <summary>
/// ctor
/// </summary>
/// <param name="log">Logger</param>
/// <param name="logger">Logger</param>
/// <param name="httpClient">HttpClient to use</param>
/// <param name="baseAddress">Base address for this API client</param>
/// <param name="options">The base client options</param>
/// <param name="apiOptions">The Api client options</param>
public RestApiClient(Log log, ClientOptions options, RestApiClientOptions apiOptions) : base(log, options, apiOptions)
public RestApiClient(ILogger logger, HttpClient? httpClient, string baseAddress, RestExchangeOptions options, RestApiOptions apiOptions)
: base(logger,
apiOptions.OutputOriginalData ?? options.OutputOriginalData,
apiOptions.ApiCredentials ?? options.ApiCredentials,
baseAddress,
options,
apiOptions)
{
var rateLimiters = new List<IRateLimiter>();
foreach (var rateLimiter in apiOptions.RateLimiters)
rateLimiters.Add(rateLimiter);
RateLimiters = rateLimiters;
ClientOptions = options;
RequestFactory.Configure(apiOptions.RequestTimeout, options.Proxy, apiOptions.HttpClient);
RequestFactory.Configure(options.RequestTimeout, httpClient);
}
/// <summary>
@@ -203,7 +207,7 @@ namespace CryptoExchange.Net
var syncTimeResult = await syncTask.ConfigureAwait(false);
if (!syncTimeResult)
{
_log.Write(LogLevel.Debug, $"[{requestId}] Failed to sync time, aborting request: " + syncTimeResult.Error);
_logger.Log(LogLevel.Debug, $"[{requestId}] Failed to sync time, aborting request: " + syncTimeResult.Error);
return syncTimeResult.As<IRequest>(default);
}
}
@@ -213,7 +217,7 @@ namespace CryptoExchange.Net
{
foreach (var limiter in RateLimiters)
{
var limitResult = await limiter.LimitRequestAsync(_log, uri.AbsolutePath, method, signed, Options.ApiCredentials?.Key ?? ClientOptions.ApiCredentials?.Key, Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
var limitResult = await limiter.LimitRequestAsync(_logger, uri.AbsolutePath, method, signed, ApiOptions.ApiCredentials?.Key ?? ClientOptions.ApiCredentials?.Key, ApiOptions.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
if (!limitResult.Success)
return new CallResult<IRequest>(limitResult.Error!);
}
@@ -221,11 +225,11 @@ namespace CryptoExchange.Net
if (signed && AuthenticationProvider == null)
{
_log.Write(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
_logger.Log(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
return new CallResult<IRequest>(new NoApiCredentialsError());
}
_log.Write(LogLevel.Information, $"[{requestId}] Creating request for " + uri);
_logger.Log(LogLevel.Information, $"[{requestId}] Creating request for " + uri);
var paramsPosition = parameterPosition ?? ParameterPositions[method];
var request = ConstructRequest(uri, method, parameters?.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value), signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders);
@@ -238,7 +242,7 @@ namespace CryptoExchange.Net
paramString += " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"));
TotalRequestsMade++;
_log.Write(LogLevel.Trace, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(ClientOptions.Proxy == null ? "" : $" via proxy {ClientOptions.Proxy.Host}")}");
_logger.Log(LogLevel.Trace, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}");
return new CallResult<IRequest>(request);
}
@@ -263,6 +267,7 @@ namespace CryptoExchange.Net
sw.Stop();
var statusCode = response.StatusCode;
var headers = response.ResponseHeaders;
var responseLength = response.ContentLength;
var responseStream = await response.GetResponseStreamAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
@@ -272,25 +277,26 @@ namespace CryptoExchange.Net
{
using var reader = new StreamReader(responseStream);
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
responseLength ??= data.Length;
responseStream.Close();
response.Close();
_log.Write(LogLevel.Debug, $"[{request.RequestId}] Response received in {sw.ElapsedMilliseconds}ms{(_log.Level == LogLevel.Trace ? (": " + data) : "")}");
_logger.Log(LogLevel.Debug, $"[{request.RequestId}] Response received in {sw.ElapsedMilliseconds}ms{(OutputOriginalData ? (": " + data) : "")}");
if (!expectedEmptyResponse)
{
// Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example
var parseResult = ValidateJson(data);
if (!parseResult.Success)
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, Options.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
// Let the library implementation see if it is an error response, and if so parse the error
var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false);
if (error != null)
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, Options.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
// Not an error, so continue deserializing
var deserializeResult = Deserialize<T>(parseResult.Data, deserializer, request.RequestId);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, Options.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), deserializeResult.Data, deserializeResult.Error);
}
else
{
@@ -299,16 +305,16 @@ namespace CryptoExchange.Net
var parseResult = ValidateJson(data);
if (!parseResult.Success)
// Not empty, and not json
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, Options.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false);
if (error != null)
// Error response
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, Options.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
}
// Empty success response; okay
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, Options.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default);
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default);
}
}
else
@@ -319,7 +325,7 @@ namespace CryptoExchange.Net
responseStream.Close();
response.Close();
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null);
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null);
}
// Success status code, and we don't have to check for errors. Continue deserializing directly from the stream
@@ -327,7 +333,7 @@ namespace CryptoExchange.Net
responseStream.Close();
response.Close();
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, Options.OutputOriginalData ? desResult.OriginalData : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error);
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, responseLength, OutputOriginalData ? desResult.OriginalData : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), desResult.Data, desResult.Error);
}
}
else
@@ -335,36 +341,36 @@ namespace CryptoExchange.Net
// Http status code indicates error
using var reader = new StreamReader(responseStream);
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
_log.Write(LogLevel.Warning, $"[{request.RequestId}] Error received in {sw.ElapsedMilliseconds}ms: {data}");
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Error received in {sw.ElapsedMilliseconds}ms: {data}");
responseStream.Close();
response.Close();
var parseResult = ValidateJson(data);
var error = parseResult.Success ? ParseErrorResponse(parseResult.Data) : new ServerError(data)!;
if (error.Code == null || error.Code == 0)
error.Code = (int)response.StatusCode;
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, data, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error);
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, data.Length, data, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error);
}
}
catch (HttpRequestException requestException)
{
// Request exception, can't reach server for instance
var exceptionInfo = requestException.ToLogString();
_log.Write(LogLevel.Warning, $"[{request.RequestId}] Request exception: " + exceptionInfo);
return new WebCallResult<T>(null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo));
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request exception: " + exceptionInfo);
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError(exceptionInfo));
}
catch (OperationCanceledException canceledException)
{
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken)
{
// Cancellation token canceled by caller
_log.Write(LogLevel.Warning, $"[{request.RequestId}] Request canceled by cancellation token");
return new WebCallResult<T>(null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError());
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request canceled by cancellation token");
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new CancellationRequestedError());
}
else
{
// Request timed out
_log.Write(LogLevel.Warning, $"[{request.RequestId}] Request timed out: " + canceledException.ToLogString());
return new WebCallResult<T>(null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"[{request.RequestId}] Request timed out"));
_logger.Log(LogLevel.Warning, $"[{request.RequestId}] Request timed out: " + canceledException.ToLogString());
return new WebCallResult<T>(null, null, null, null, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, new WebError($"[{request.RequestId}] Request timed out"));
}
}
}
@@ -542,14 +548,14 @@ namespace CryptoExchange.Net
{
var timeSyncParams = GetTimeSyncInfo();
if (timeSyncParams == null)
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, true, null);
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null);
if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
{
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval))
{
timeSyncParams.TimeSyncState.Semaphore.Release();
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, true, null);
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null);
}
var localTime = DateTime.UtcNow;
@@ -578,7 +584,7 @@ namespace CryptoExchange.Net
timeSyncParams.TimeSyncState.Semaphore.Release();
}
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, true, null);
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, true, null);
}
}
}