mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-07-26 19:26:47 +00:00
Wip, support for time syncing, refactoring authentication
This commit is contained in:
parent
8b479547ab
commit
c2105fe690
@ -1,6 +1,9 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace CryptoExchange.Net.Authentication
|
||||
{
|
||||
@ -14,45 +17,151 @@ namespace CryptoExchange.Net.Authentication
|
||||
/// </summary>
|
||||
public ApiCredentials Credentials { get; }
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
protected byte[] _sBytes;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="credentials"></param>
|
||||
protected AuthenticationProvider(ApiCredentials credentials)
|
||||
{
|
||||
if (credentials.Secret == null)
|
||||
throw new ArgumentException("ApiKey/Secret needed");
|
||||
|
||||
Credentials = credentials;
|
||||
_sBytes = Encoding.UTF8.GetBytes(credentials.Secret.GetString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add authentication to the parameter list based on the provided credentials
|
||||
/// Authenticate a request where the parameters need to be in the Uri
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri the request is for</param>
|
||||
/// <param name="method">The HTTP method of the request</param>
|
||||
/// <param name="parameters">The provided parameters for the request</param>
|
||||
/// <param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param>
|
||||
/// <param name="parameterPosition">Where parameters are placed, in the URI or in the request body</param>
|
||||
/// <param name="arraySerialization">How array parameters are serialized</param>
|
||||
/// <returns>Should return the original parameter list including any authentication parameters needed</returns>
|
||||
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
|
||||
HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization)
|
||||
/// <param name="apiClient">The Api client sending the request</param>
|
||||
/// <param name="uri">The uri for the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="parameters">The request parameters</param>
|
||||
/// <param name="headers">The request headers</param>
|
||||
/// <param name="auth">If the requests should be authenticated</param>
|
||||
/// <param name="arraySerialization">Array serialization type</param>
|
||||
/// <returns></returns>
|
||||
public abstract void AuthenticateUriRequest(
|
||||
RestApiClient apiClient,
|
||||
ref Uri uri,
|
||||
HttpMethod method,
|
||||
SortedDictionary<string, object> parameters,
|
||||
Dictionary<string, string> headers,
|
||||
bool auth,
|
||||
ArrayParametersSerialization arraySerialization);
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate a request where the parameters need to be in the request body
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The Api client sending the request</param>
|
||||
/// <param name="uri">The uri for the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="parameters">The request parameters</param>
|
||||
/// <param name="headers">The request headers</param>
|
||||
/// <param name="auth">If the requests should be authenticated</param>
|
||||
/// <param name="arraySerialization">Array serialization type</param>
|
||||
public abstract void AuthenticateBodyRequest(
|
||||
RestApiClient apiClient,
|
||||
Uri uri,
|
||||
HttpMethod method,
|
||||
SortedDictionary<string, object> parameters,
|
||||
Dictionary<string, string> headers,
|
||||
bool auth,
|
||||
ArrayParametersSerialization arraySerialization);
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 sign the data and return the hash
|
||||
/// </summary>
|
||||
/// <param name="data">Data to sign</param>
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
protected static string SignSHA256(string data, SignOutputType? outputType = null)
|
||||
{
|
||||
return parameters;
|
||||
using var encryptor = SHA256.Create();
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes): BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add authentication to the header dictionary based on the provided credentials
|
||||
/// SHA384 sign the data and return the hash
|
||||
/// </summary>
|
||||
/// <param name="uri">The uri the request is for</param>
|
||||
/// <param name="method">The HTTP method of the request</param>
|
||||
/// <param name="parameters">The provided parameters for the request</param>
|
||||
/// <param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param>
|
||||
/// <param name="parameterPosition">Where post parameters are placed, in the URI or in the request body</param>
|
||||
/// <param name="arraySerialization">How array parameters are serialized</param>
|
||||
/// <returns>Should return a dictionary containing any header key/value pairs needed for authenticating the request</returns>
|
||||
public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
|
||||
HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization)
|
||||
/// <param name="data">Data to sign</param>
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
protected static string SignSHA384(string data, SignOutputType? outputType = null)
|
||||
{
|
||||
return new Dictionary<string, string>();
|
||||
using var encryptor = SHA384.Create();
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SHA512 sign the data and return the hash
|
||||
/// </summary>
|
||||
/// <param name="data">Data to sign</param>
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
protected static string SignSHA512(string data, SignOutputType? outputType = null)
|
||||
{
|
||||
using var encryptor = SHA512.Create();
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MD5 sign the data and return the hash
|
||||
/// </summary>
|
||||
/// <param name="data">Data to sign</param>
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
protected static string SignMD5(string data, SignOutputType? outputType = null)
|
||||
{
|
||||
using var encryptor = MD5.Create();
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HMACSHA256 sign the data and return the hash
|
||||
/// </summary>
|
||||
/// <param name="data">Data to sign</param>
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
protected string SignHMACSHA256(string data, SignOutputType? outputType = null)
|
||||
{
|
||||
using var encryptor = new HMACSHA256(_sBytes);
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HMACSHA384 sign the data and return the hash
|
||||
/// </summary>
|
||||
/// <param name="data">Data to sign</param>
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
protected string SignHMACSHA384(string data, SignOutputType? outputType = null)
|
||||
{
|
||||
using var encryptor = new HMACSHA384(_sBytes);
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HMACSHA512 sign the data and return the hash
|
||||
/// </summary>
|
||||
/// <param name="data">Data to sign</param>
|
||||
/// <param name="outputType">String type</param>
|
||||
/// <returns></returns>
|
||||
protected string SignHMACSHA512(string data, SignOutputType? outputType = null)
|
||||
{
|
||||
using var encryptor = new HMACSHA512(_sBytes);
|
||||
var resultBytes = encryptor.ComputeHash(Encoding.UTF8.GetBytes(data));
|
||||
return outputType == SignOutputType.Base64 ? BytesToBase64String(resultBytes) : BytesToHexString(resultBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -76,16 +185,26 @@ namespace CryptoExchange.Net.Authentication
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert byte array to hex
|
||||
/// Convert byte array to hex string
|
||||
/// </summary>
|
||||
/// <param name="buff"></param>
|
||||
/// <returns></returns>
|
||||
protected static string ByteToString(byte[] buff)
|
||||
protected static string BytesToHexString(byte[] buff)
|
||||
{
|
||||
var result = string.Empty;
|
||||
foreach (var t in buff)
|
||||
result += t.ToString("X2"); /* hex format */
|
||||
result += t.ToString("X2");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert byte array to base64 string
|
||||
/// </summary>
|
||||
/// <param name="buff"></param>
|
||||
/// <returns></returns>
|
||||
protected static string BytesToBase64String(byte[] buff)
|
||||
{
|
||||
return Convert.ToBase64String(buff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
CryptoExchange.Net/Authentication/SignOutputType.cs
Normal file
17
CryptoExchange.Net/Authentication/SignOutputType.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace CryptoExchange.Net.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Output string type
|
||||
/// </summary>
|
||||
public enum SignOutputType
|
||||
{
|
||||
/// <summary>
|
||||
/// Hex string
|
||||
/// </summary>
|
||||
Hex,
|
||||
/// <summary>
|
||||
/// Base64 string
|
||||
/// </summary>
|
||||
Base64
|
||||
}
|
||||
}
|
@ -83,9 +83,9 @@ namespace CryptoExchange.Net
|
||||
|
||||
ClientOptions = exchangeOptions;
|
||||
RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Execute a request to the uri and deserialize the response into the provided type parameter
|
||||
/// </summary>
|
||||
@ -118,6 +118,11 @@ namespace CryptoExchange.Net
|
||||
) where T : class
|
||||
{
|
||||
var requestId = NextId();
|
||||
|
||||
var syncTimeResult = await apiClient.SyncTimeAsync().ConfigureAwait(false);
|
||||
if (!syncTimeResult)
|
||||
return syncTimeResult.As<T>(default);
|
||||
|
||||
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
|
||||
if (signed && apiClient.AuthenticationProvider == null)
|
||||
{
|
||||
@ -145,6 +150,9 @@ namespace CryptoExchange.Net
|
||||
paramString += " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"));
|
||||
}
|
||||
|
||||
apiClient.TotalRequestsMade++;
|
||||
TotalRequestsMade++;
|
||||
|
||||
log.Write(LogLevel.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(ClientOptions.Proxy == null ? "" : $" via proxy {ClientOptions.Proxy.Host}")}");
|
||||
return await GetResponseAsync<T>(request, deserializer, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@ -160,7 +168,6 @@ namespace CryptoExchange.Net
|
||||
{
|
||||
try
|
||||
{
|
||||
TotalRequestsMade++;
|
||||
var sw = Stopwatch.StartNew();
|
||||
var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
|
||||
sw.Stop();
|
||||
@ -278,22 +285,43 @@ namespace CryptoExchange.Net
|
||||
int requestId,
|
||||
Dictionary<string, string>? additionalHeaders)
|
||||
{
|
||||
parameters ??= new Dictionary<string, object>();
|
||||
SortedDictionary<string, object> sortedParameters = new SortedDictionary<string, object>(GetParameterComparer());
|
||||
if (parameters != null)
|
||||
sortedParameters = new SortedDictionary<string, object>(parameters, GetParameterComparer());
|
||||
|
||||
var uriString = uri.ToString();
|
||||
if (apiClient.AuthenticationProvider != null)
|
||||
parameters = apiClient.AuthenticationProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization);
|
||||
|
||||
if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true)
|
||||
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
|
||||
|
||||
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
var request = RequestFactory.Create(method, uriString, requestId);
|
||||
request.Accept = Constants.JsonContentHeader;
|
||||
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
||||
{
|
||||
foreach (var parameter in sortedParameters)
|
||||
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
|
||||
}
|
||||
|
||||
var length = sortedParameters.Count;
|
||||
var headers = new Dictionary<string, string>();
|
||||
if (apiClient.AuthenticationProvider != null)
|
||||
headers = apiClient.AuthenticationProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization);
|
||||
{
|
||||
if(parameterPosition == HttpMethodParameterPosition.InUri)
|
||||
apiClient.AuthenticationProvider.AuthenticateUriRequest(apiClient, ref uri, method, sortedParameters, headers, signed, arraySerialization);
|
||||
else
|
||||
apiClient.AuthenticationProvider.AuthenticateBodyRequest(apiClient, uri, method, sortedParameters, headers, signed, arraySerialization);
|
||||
}
|
||||
|
||||
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
||||
{
|
||||
// Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
|
||||
if (sortedParameters.Count != length)
|
||||
{
|
||||
var uriBuilder = new UriBuilder();
|
||||
uriBuilder.Scheme = uri.Scheme;
|
||||
uriBuilder.Host = uri.Host;
|
||||
uriBuilder.Path = uri.AbsolutePath;
|
||||
uri = uriBuilder.Uri;
|
||||
foreach(var parameter in sortedParameters)
|
||||
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
var request = RequestFactory.Create(method, uri, requestId);
|
||||
request.Accept = Constants.JsonContentHeader;
|
||||
|
||||
foreach (var header in headers)
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
@ -304,55 +332,88 @@ namespace CryptoExchange.Net
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
if(StandardRequestHeaders != null)
|
||||
if (StandardRequestHeaders != null)
|
||||
{
|
||||
foreach (var header in StandardRequestHeaders)
|
||||
// Only add it if it isn't overwritten
|
||||
if(additionalHeaders?.ContainsKey(header.Key) != true)
|
||||
if (additionalHeaders?.ContainsKey(header.Key) != true)
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||
{
|
||||
if (parameters?.Any() == true)
|
||||
WriteParamBody(request, parameters, contentType);
|
||||
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
if (sortedParameters?.Any() == true)
|
||||
WriteParamBody(request, sortedParameters, contentType);
|
||||
else
|
||||
request.SetContent(requestBodyEmptyContent, contentType);
|
||||
}
|
||||
|
||||
return request;
|
||||
|
||||
//var uriString = uri.ToString();
|
||||
//if (apiClient.AuthenticationProvider != null)
|
||||
// parameters = apiClient.AuthenticationProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization);
|
||||
|
||||
//if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true)
|
||||
// uriString += "?" + parameters.CreateParamString(true, arraySerialization);
|
||||
|
||||
//var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
//var request = RequestFactory.Create(method, uriString, requestId);
|
||||
//request.Accept = Constants.JsonContentHeader;
|
||||
|
||||
//var headers = new Dictionary<string, string>();
|
||||
//if (apiClient.AuthenticationProvider != null)
|
||||
// headers = apiClient.AuthenticationProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization);
|
||||
|
||||
//foreach (var header in headers)
|
||||
// request.AddHeader(header.Key, header.Value);
|
||||
|
||||
//if (additionalHeaders != null)
|
||||
//{
|
||||
// foreach (var header in additionalHeaders)
|
||||
// request.AddHeader(header.Key, header.Value);
|
||||
//}
|
||||
|
||||
//if(StandardRequestHeaders != null)
|
||||
//{
|
||||
// foreach (var header in StandardRequestHeaders)
|
||||
// // Only add it if it isn't overwritten
|
||||
// if(additionalHeaders?.ContainsKey(header.Key) != true)
|
||||
// request.AddHeader(header.Key, header.Value);
|
||||
//}
|
||||
|
||||
//if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||
//{
|
||||
// if (parameters?.Any() == true)
|
||||
// WriteParamBody(request, parameters, contentType);
|
||||
// else
|
||||
// request.SetContent(requestBodyEmptyContent, contentType);
|
||||
//}
|
||||
|
||||
//return request;
|
||||
}
|
||||
|
||||
protected virtual IComparer<string> GetParameterComparer() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Writes the parameters of the request to the request object body
|
||||
/// </summary>
|
||||
/// <param name="request">The request to set the parameters on</param>
|
||||
/// <param name="parameters">The parameters to set</param>
|
||||
/// <param name="contentType">The content type of the data</param>
|
||||
protected virtual void WriteParamBody(IRequest request, Dictionary<string, object> parameters, string contentType)
|
||||
protected virtual void WriteParamBody(IRequest request, SortedDictionary<string, object> parameters, string contentType)
|
||||
{
|
||||
if (requestBodyFormat == RequestBodyFormat.Json)
|
||||
{
|
||||
// Write the parameters as json in the body
|
||||
var stringData = JsonConvert.SerializeObject(parameters.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value));
|
||||
var stringData = JsonConvert.SerializeObject(parameters);
|
||||
request.SetContent(stringData, contentType);
|
||||
}
|
||||
else if (requestBodyFormat == RequestBodyFormat.FormData)
|
||||
{
|
||||
// Write the parameters as form data in the body
|
||||
var formData = HttpUtility.ParseQueryString(string.Empty);
|
||||
foreach (var kvp in parameters.OrderBy(p => p.Key))
|
||||
{
|
||||
if (kvp.Value.GetType().IsArray)
|
||||
{
|
||||
var array = (Array)kvp.Value;
|
||||
foreach (var value in array)
|
||||
formData.Add(kvp.Key, value.ToString());
|
||||
}
|
||||
else
|
||||
formData.Add(kvp.Key, kvp.Value.ToString());
|
||||
}
|
||||
var stringData = formData.ToString();
|
||||
var stringData = parameters.ToFormData();
|
||||
request.SetContent(stringData, contentType);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Logging;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CryptoExchange.Net
|
||||
{
|
||||
@ -9,16 +14,27 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
public abstract class RestApiClient: BaseApiClient
|
||||
{
|
||||
protected abstract TimeSyncModel GetTimeSyncParameters();
|
||||
protected abstract void UpdateTimeOffset(TimeSpan offset);
|
||||
public abstract TimeSpan GetTimeOffset();
|
||||
|
||||
/// <summary>
|
||||
/// Total amount of requests made with this API client
|
||||
/// </summary>
|
||||
public int TotalRequestsMade { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Options for this client
|
||||
/// </summary>
|
||||
internal RestApiClientOptions Options { get; }
|
||||
public RestApiClientOptions Options { get; }
|
||||
|
||||
/// <summary>
|
||||
/// List of rate limiters
|
||||
/// </summary>
|
||||
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
||||
|
||||
private Log _log;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
@ -34,5 +50,59 @@ namespace CryptoExchange.Net
|
||||
RateLimiters = rateLimiters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the server time for the purpose of syncing time between client and server to prevent authentication issues
|
||||
/// </summary>
|
||||
/// <returns>Server time</returns>
|
||||
protected abstract Task<WebCallResult<DateTime>> GetServerTimestampAsync();
|
||||
|
||||
internal async Task<WebCallResult<bool>> SyncTimeAsync()
|
||||
{
|
||||
var timeSyncParams = GetTimeSyncParameters();
|
||||
if (await timeSyncParams.Semaphore.WaitAsync(0).ConfigureAwait(false))
|
||||
{
|
||||
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.LastSyncTime < TimeSpan.FromHours(1)))
|
||||
{
|
||||
timeSyncParams.Semaphore.Release();
|
||||
return new WebCallResult<bool>(null, null, true, null);
|
||||
}
|
||||
|
||||
var localTime = DateTime.UtcNow;
|
||||
var result = await GetServerTimestampAsync().ConfigureAwait(false);
|
||||
if (!result)
|
||||
{
|
||||
timeSyncParams.Semaphore.Release();
|
||||
return result.As(false);
|
||||
}
|
||||
|
||||
if (TotalRequestsMade == 1)
|
||||
{
|
||||
// If this was the first request make another one to calculate the offset since the first one can be slower
|
||||
localTime = DateTime.UtcNow;
|
||||
result = await GetServerTimestampAsync().ConfigureAwait(false);
|
||||
if (!result)
|
||||
{
|
||||
timeSyncParams.Semaphore.Release();
|
||||
return result.As(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate time offset between local and server
|
||||
var offset = result.Data - localTime;
|
||||
if (offset.TotalMilliseconds >= 0 && offset.TotalMilliseconds < 500)
|
||||
{
|
||||
// Small offset, probably mainly due to ping. Don't adjust time
|
||||
UpdateTimeOffset(offset);
|
||||
timeSyncParams.Semaphore.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateTimeOffset(offset);
|
||||
timeSyncParams.Semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
return new WebCallResult<bool>(null, null, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using CryptoExchange.Net.Logging;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -142,6 +143,49 @@ namespace CryptoExchange.Net
|
||||
return uriString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a dictionary to formdata string
|
||||
/// </summary>
|
||||
/// <param name="parameters"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToFormData(this SortedDictionary<string, object> parameters)
|
||||
{
|
||||
var formData = HttpUtility.ParseQueryString(string.Empty);
|
||||
foreach (var kvp in parameters.OrderBy(p => p.Key))
|
||||
{
|
||||
if (kvp.Value.GetType().IsArray)
|
||||
{
|
||||
var array = (Array)kvp.Value;
|
||||
foreach (var value in array)
|
||||
formData.Add(kvp.Key, value.ToString());
|
||||
}
|
||||
else
|
||||
formData.Add(kvp.Key, kvp.Value.ToString());
|
||||
}
|
||||
return formData.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add parameter to URI
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static Uri AddQueryParmeter(this Uri uri, string name, string value)
|
||||
{
|
||||
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
|
||||
|
||||
httpValueCollection.Remove(name);
|
||||
httpValueCollection.Add(name, value);
|
||||
|
||||
var ub = new UriBuilder(uri);
|
||||
ub.Query = httpValueCollection.ToString();
|
||||
|
||||
return ub.Uri;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the string the secure string is representing
|
||||
/// </summary>
|
||||
|
@ -16,7 +16,7 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// <param name="uri"></param>
|
||||
/// <param name="requestId"></param>
|
||||
/// <returns></returns>
|
||||
IRequest Create(HttpMethod method, string uri, int requestId);
|
||||
IRequest Create(HttpMethod method, Uri uri, int requestId);
|
||||
|
||||
/// <summary>
|
||||
/// Configure the requests created by this factory
|
||||
|
@ -275,6 +275,11 @@ namespace CryptoExchange.Net.Objects
|
||||
/// </summary>
|
||||
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically sync the local time with the server time
|
||||
/// </summary>
|
||||
public bool AutoTimestamp { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
@ -312,12 +317,13 @@ namespace CryptoExchange.Net.Objects
|
||||
if(def.RateLimiters != null)
|
||||
input.RateLimiters = def.RateLimiters.ToList();
|
||||
input.RateLimitingBehaviour = def.RateLimitingBehaviour;
|
||||
input.AutoTimestamp = def.AutoTimestamp;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}";
|
||||
return $"{base.ToString()}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, AutoTimestamp: {AutoTimestamp}";
|
||||
}
|
||||
}
|
||||
|
||||
|
21
CryptoExchange.Net/Objects/TimeSyncModel.cs
Normal file
21
CryptoExchange.Net/Objects/TimeSyncModel.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace CryptoExchange.Net.Objects
|
||||
{
|
||||
public class TimeSyncModel
|
||||
{
|
||||
public bool SyncTime { get; set; }
|
||||
public SemaphoreSlim Semaphore { get; set; }
|
||||
public DateTime LastSyncTime { get; set; }
|
||||
|
||||
public TimeSyncModel(bool syncTime, SemaphoreSlim semaphore, DateTime lastSyncTime)
|
||||
{
|
||||
SyncTime = syncTime;
|
||||
Semaphore = semaphore;
|
||||
LastSyncTime = lastSyncTime;
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ namespace CryptoExchange.Net.Requests
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequest Create(HttpMethod method, string uri, int requestId)
|
||||
public IRequest Create(HttpMethod method, Uri uri, int requestId)
|
||||
{
|
||||
if (httpClient == null)
|
||||
throw new InvalidOperationException("Cant create request before configuring http client");
|
||||
|
Loading…
x
Reference in New Issue
Block a user