mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-07-27 03:36:52 +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 CryptoExchange.Net.Objects;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Authentication
|
namespace CryptoExchange.Net.Authentication
|
||||||
{
|
{
|
||||||
@ -14,45 +17,151 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ApiCredentials Credentials { get; }
|
public ApiCredentials Credentials { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// </summary>
|
||||||
|
protected byte[] _sBytes;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="credentials"></param>
|
/// <param name="credentials"></param>
|
||||||
protected AuthenticationProvider(ApiCredentials credentials)
|
protected AuthenticationProvider(ApiCredentials credentials)
|
||||||
{
|
{
|
||||||
|
if (credentials.Secret == null)
|
||||||
|
throw new ArgumentException("ApiKey/Secret needed");
|
||||||
|
|
||||||
Credentials = credentials;
|
Credentials = credentials;
|
||||||
|
_sBytes = Encoding.UTF8.GetBytes(credentials.Secret.GetString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="uri">The uri the request is for</param>
|
/// <param name="apiClient">The Api client sending the request</param>
|
||||||
/// <param name="method">The HTTP method of the request</param>
|
/// <param name="uri">The uri for the request</param>
|
||||||
/// <param name="parameters">The provided parameters for the request</param>
|
/// <param name="method">The method of 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="parameters">The request parameters</param>
|
||||||
/// <param name="parameterPosition">Where parameters are placed, in the URI or in the request body</param>
|
/// <param name="headers">The request headers</param>
|
||||||
/// <param name="arraySerialization">How array parameters are serialized</param>
|
/// <param name="auth">If the requests should be authenticated</param>
|
||||||
/// <returns>Should return the original parameter list including any authentication parameters needed</returns>
|
/// <param name="arraySerialization">Array serialization type</param>
|
||||||
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
|
/// <returns></returns>
|
||||||
HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization)
|
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>
|
/// <summary>
|
||||||
/// Add authentication to the header dictionary based on the provided credentials
|
/// SHA384 sign the data and return the hash
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uri">The uri the request is for</param>
|
/// <param name="data">Data to sign</param>
|
||||||
/// <param name="method">The HTTP method of the request</param>
|
/// <param name="outputType">String type</param>
|
||||||
/// <param name="parameters">The provided parameters for the request</param>
|
/// <returns></returns>
|
||||||
/// <param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param>
|
protected static string SignSHA384(string data, SignOutputType? outputType = null)
|
||||||
/// <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)
|
|
||||||
{
|
{
|
||||||
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>
|
/// <summary>
|
||||||
@ -76,16 +185,26 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert byte array to hex
|
/// Convert byte array to hex string
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buff"></param>
|
/// <param name="buff"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected static string ByteToString(byte[] buff)
|
protected static string BytesToHexString(byte[] buff)
|
||||||
{
|
{
|
||||||
var result = string.Empty;
|
var result = string.Empty;
|
||||||
foreach (var t in buff)
|
foreach (var t in buff)
|
||||||
result += t.ToString("X2"); /* hex format */
|
result += t.ToString("X2");
|
||||||
return result;
|
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;
|
ClientOptions = exchangeOptions;
|
||||||
RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
|
RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Execute a request to the uri and deserialize the response into the provided type parameter
|
/// Execute a request to the uri and deserialize the response into the provided type parameter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -118,6 +118,11 @@ namespace CryptoExchange.Net
|
|||||||
) where T : class
|
) where T : class
|
||||||
{
|
{
|
||||||
var requestId = NextId();
|
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);
|
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
|
||||||
if (signed && apiClient.AuthenticationProvider == null)
|
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)}]"));
|
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}")}");
|
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);
|
return await GetResponseAsync<T>(request, deserializer, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -160,7 +168,6 @@ namespace CryptoExchange.Net
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
TotalRequestsMade++;
|
|
||||||
var sw = Stopwatch.StartNew();
|
var sw = Stopwatch.StartNew();
|
||||||
var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
|
var response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
@ -278,22 +285,43 @@ namespace CryptoExchange.Net
|
|||||||
int requestId,
|
int requestId,
|
||||||
Dictionary<string, string>? additionalHeaders)
|
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 (parameterPosition == HttpMethodParameterPosition.InUri)
|
||||||
if (apiClient.AuthenticationProvider != null)
|
{
|
||||||
parameters = apiClient.AuthenticationProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization);
|
foreach (var parameter in sortedParameters)
|
||||||
|
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
|
||||||
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 length = sortedParameters.Count;
|
||||||
var headers = new Dictionary<string, string>();
|
var headers = new Dictionary<string, string>();
|
||||||
if (apiClient.AuthenticationProvider != null)
|
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)
|
foreach (var header in headers)
|
||||||
request.AddHeader(header.Key, header.Value);
|
request.AddHeader(header.Key, header.Value);
|
||||||
@ -304,55 +332,88 @@ namespace CryptoExchange.Net
|
|||||||
request.AddHeader(header.Key, header.Value);
|
request.AddHeader(header.Key, header.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(StandardRequestHeaders != null)
|
if (StandardRequestHeaders != null)
|
||||||
{
|
{
|
||||||
foreach (var header in StandardRequestHeaders)
|
foreach (var header in StandardRequestHeaders)
|
||||||
// Only add it if it isn't overwritten
|
// 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);
|
request.AddHeader(header.Key, header.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||||
{
|
{
|
||||||
if (parameters?.Any() == true)
|
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||||
WriteParamBody(request, parameters, contentType);
|
if (sortedParameters?.Any() == true)
|
||||||
|
WriteParamBody(request, sortedParameters, contentType);
|
||||||
else
|
else
|
||||||
request.SetContent(requestBodyEmptyContent, contentType);
|
request.SetContent(requestBodyEmptyContent, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return request;
|
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>
|
/// <summary>
|
||||||
/// Writes the parameters of the request to the request object body
|
/// Writes the parameters of the request to the request object body
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request to set the parameters on</param>
|
/// <param name="request">The request to set the parameters on</param>
|
||||||
/// <param name="parameters">The parameters to set</param>
|
/// <param name="parameters">The parameters to set</param>
|
||||||
/// <param name="contentType">The content type of the data</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)
|
if (requestBodyFormat == RequestBodyFormat.Json)
|
||||||
{
|
{
|
||||||
// Write the parameters as json in the body
|
// 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);
|
request.SetContent(stringData, contentType);
|
||||||
}
|
}
|
||||||
else if (requestBodyFormat == RequestBodyFormat.FormData)
|
else if (requestBodyFormat == RequestBodyFormat.FormData)
|
||||||
{
|
{
|
||||||
// Write the parameters as form data in the body
|
// Write the parameters as form data in the body
|
||||||
var formData = HttpUtility.ParseQueryString(string.Empty);
|
var stringData = parameters.ToFormData();
|
||||||
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();
|
|
||||||
request.SetContent(stringData, contentType);
|
request.SetContent(stringData, contentType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
using CryptoExchange.Net.Logging;
|
||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace CryptoExchange.Net
|
namespace CryptoExchange.Net
|
||||||
{
|
{
|
||||||
@ -9,16 +14,27 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class RestApiClient: BaseApiClient
|
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>
|
/// <summary>
|
||||||
/// Options for this client
|
/// Options for this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal RestApiClientOptions Options { get; }
|
public RestApiClientOptions Options { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of rate limiters
|
/// List of rate limiters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
internal IEnumerable<IRateLimiter> RateLimiters { get; }
|
||||||
|
|
||||||
|
private Log _log;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -34,5 +50,59 @@ namespace CryptoExchange.Net
|
|||||||
RateLimiters = rateLimiters;
|
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.Runtime.InteropServices;
|
||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Web;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -142,6 +143,49 @@ namespace CryptoExchange.Net
|
|||||||
return uriString;
|
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>
|
/// <summary>
|
||||||
/// Get the string the secure string is representing
|
/// Get the string the secure string is representing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -16,7 +16,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// <param name="uri"></param>
|
/// <param name="uri"></param>
|
||||||
/// <param name="requestId"></param>
|
/// <param name="requestId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
IRequest Create(HttpMethod method, string uri, int requestId);
|
IRequest Create(HttpMethod method, Uri uri, int requestId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configure the requests created by this factory
|
/// Configure the requests created by this factory
|
||||||
|
@ -275,6 +275,11 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait;
|
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>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -312,12 +317,13 @@ namespace CryptoExchange.Net.Objects
|
|||||||
if(def.RateLimiters != null)
|
if(def.RateLimiters != null)
|
||||||
input.RateLimiters = def.RateLimiters.ToList();
|
input.RateLimiters = def.RateLimiters.ToList();
|
||||||
input.RateLimitingBehaviour = def.RateLimitingBehaviour;
|
input.RateLimitingBehaviour = def.RateLimitingBehaviour;
|
||||||
|
input.AutoTimestamp = def.AutoTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
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 />
|
/// <inheritdoc />
|
||||||
public IRequest Create(HttpMethod method, string uri, int requestId)
|
public IRequest Create(HttpMethod method, Uri uri, int requestId)
|
||||||
{
|
{
|
||||||
if (httpClient == null)
|
if (httpClient == null)
|
||||||
throw new InvalidOperationException("Cant create request before configuring http client");
|
throw new InvalidOperationException("Cant create request before configuring http client");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user