mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-09-05 15:11:42 +00:00
HttpVersion update
Added LibraryHelpers.CreateHttpClientMessageHandle to standardize HttpMessageHandler creation Added REST client option for selecting HTTP protocol version Added REST client option for HTTP client keep alive interval Added HttpVersion to WebCallResult responses Updated request logic to default to using HTTP version 2.0 for dotnet core
This commit is contained in:
parent
b215cccda4
commit
d44a11c44e
@ -60,7 +60,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
request.Setup(c => c.GetHeaders()).Returns(() => headers.ToArray());
|
||||
|
||||
var factory = Mock.Get(Api1.RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
|
||||
{
|
||||
request.Setup(a => a.Uri).Returns(uri);
|
||||
@ -69,7 +69,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
.Returns(request.Object);
|
||||
|
||||
factory = Mock.Get(Api2.RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.Callback<HttpMethod, Uri, int>((method, uri, id) =>
|
||||
{
|
||||
request.Setup(a => a.Uri).Returns(uri);
|
||||
@ -90,12 +90,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
|
||||
|
||||
var factory = Mock.Get(Api1.RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.Returns(request.Object);
|
||||
|
||||
|
||||
factory = Mock.Get(Api2.RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.Returns(request.Object);
|
||||
}
|
||||
|
||||
@ -118,12 +118,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
||||
request.Setup(c => c.GetHeaders()).Returns(headers.ToArray());
|
||||
|
||||
var factory = Mock.Get(Api1.RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
||||
.Returns(request.Object);
|
||||
|
||||
factory = Mock.Get(Api2.RequestFactory);
|
||||
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
|
||||
.Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
|
||||
.Returns(request.Object);
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ namespace CryptoExchange.Net.Clients
|
||||
options,
|
||||
apiOptions)
|
||||
{
|
||||
RequestFactory.Configure(options.Proxy, options.RequestTimeout, httpClient);
|
||||
RequestFactory.Configure(options, httpClient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -388,7 +388,7 @@ namespace CryptoExchange.Net.Clients
|
||||
queryString = $"?{queryString}";
|
||||
|
||||
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString);
|
||||
var request = RequestFactory.Create(definition.Method, uri, requestId);
|
||||
var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId);
|
||||
request.Accept = Constants.JsonContentHeader;
|
||||
|
||||
foreach (var header in requestConfiguration.Headers)
|
||||
@ -443,9 +443,6 @@ namespace CryptoExchange.Net.Clients
|
||||
{
|
||||
response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
|
||||
sw.Stop();
|
||||
var statusCode = response.StatusCode;
|
||||
var headers = response.ResponseHeaders;
|
||||
var responseLength = response.ContentLength;
|
||||
responseStream = await response.GetResponseStreamAsync().ConfigureAwait(false);
|
||||
var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData;
|
||||
|
||||
@ -475,18 +472,18 @@ namespace CryptoExchange.Net.Clients
|
||||
if (error.Code == null || error.Code == 0)
|
||||
error.Code = (int)response.StatusCode;
|
||||
|
||||
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error!);
|
||||
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error!);
|
||||
}
|
||||
|
||||
var valid = await accessor.Read(responseStream, outputOriginalData).ConfigureAwait(false);
|
||||
if (typeof(T) == typeof(object))
|
||||
// Success status code and expected empty response, assume it's correct
|
||||
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]", request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null);
|
||||
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, 0, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]", request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null);
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
// Invalid json
|
||||
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, valid.Error);
|
||||
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, valid.Error);
|
||||
}
|
||||
|
||||
// Json response received
|
||||
@ -503,33 +500,55 @@ namespace CryptoExchange.Net.Clients
|
||||
}
|
||||
|
||||
// Success status code, but TryParseError determined it was an error response
|
||||
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError);
|
||||
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError);
|
||||
}
|
||||
|
||||
var deserializeResult = accessor.Deserialize<T>();
|
||||
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult.Data, deserializeResult.Error);
|
||||
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult.Data, deserializeResult.Error);
|
||||
}
|
||||
catch (HttpRequestException requestException)
|
||||
{
|
||||
// Request exception, can't reach server for instance
|
||||
var error = new WebError(requestException.Message, requestException);
|
||||
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
}
|
||||
catch (OperationCanceledException canceledException)
|
||||
{
|
||||
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken)
|
||||
{
|
||||
// Cancellation token canceled by caller
|
||||
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError(canceledException));
|
||||
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError(canceledException));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Request timed out
|
||||
var error = new WebError($"Request timed out", exception: canceledException);
|
||||
error.ErrorType = ErrorType.Timeout;
|
||||
return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
}
|
||||
}
|
||||
catch (ArgumentException argumentException)
|
||||
{
|
||||
if (argumentException.Message.StartsWith("Only HTTP/"))
|
||||
{
|
||||
// Unsupported HTTP version error .net framework
|
||||
var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version {request.HttpVersion}: " + argumentException.Message);
|
||||
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (NotSupportedException notSupportedException)
|
||||
{
|
||||
if (notSupportedException.Message.StartsWith("Request version value must be one of"))
|
||||
{
|
||||
// Unsupported HTTP version error dotnet code
|
||||
var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version {request.HttpVersion}: " + notSupportedException.Message);
|
||||
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
accessor?.Clear();
|
||||
@ -674,21 +693,21 @@ namespace CryptoExchange.Net.Clients
|
||||
{
|
||||
base.SetOptions(options);
|
||||
|
||||
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout);
|
||||
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout, ClientOptions.HttpKeepAliveInterval);
|
||||
}
|
||||
|
||||
internal async Task<WebCallResult<bool>> SyncTimeAsync()
|
||||
{
|
||||
var timeSyncParams = GetTimeSyncInfo();
|
||||
if (timeSyncParams == null)
|
||||
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
|
||||
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, 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, null, null, ResultDataSource.Server, true, null);
|
||||
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
|
||||
}
|
||||
|
||||
var localTime = DateTime.UtcNow;
|
||||
@ -717,7 +736,7 @@ namespace CryptoExchange.Net.Clients
|
||||
timeSyncParams.TimeSyncState.Semaphore.Release();
|
||||
}
|
||||
|
||||
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
|
||||
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
|
||||
}
|
||||
|
||||
private bool ShouldCache(RequestDefinition definition)
|
||||
|
@ -28,6 +28,10 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
Uri Uri { get; }
|
||||
/// <summary>
|
||||
/// HTTP protocol version
|
||||
/// </summary>
|
||||
Version HttpVersion { get; }
|
||||
/// <summary>
|
||||
/// internal request id for tracing
|
||||
/// </summary>
|
||||
int RequestId { get; }
|
||||
|
@ -1,4 +1,5 @@
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
@ -12,25 +13,21 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// <summary>
|
||||
/// Create a request for an uri
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="uri"></param>
|
||||
/// <param name="requestId"></param>
|
||||
/// <returns></returns>
|
||||
IRequest Create(HttpMethod method, Uri uri, int requestId);
|
||||
IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId);
|
||||
|
||||
/// <summary>
|
||||
/// Configure the requests created by this factory
|
||||
/// </summary>
|
||||
/// <param name="requestTimeout">Request timeout to use</param>
|
||||
/// <param name="options">Rest client options</param>
|
||||
/// <param name="httpClient">Optional shared http client instance</param>
|
||||
/// <param name="proxy">Optional proxy to use when no http client is provided</param>
|
||||
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
|
||||
void Configure(RestExchangeOptions options, HttpClient? httpClient = null);
|
||||
|
||||
/// <summary>
|
||||
/// Update settings
|
||||
/// </summary>
|
||||
/// <param name="proxy">Proxy to use</param>
|
||||
/// <param name="requestTimeout">Request timeout to use</param>
|
||||
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
|
||||
/// <param name="httpKeepAliveInterval">Http client keep alive interval</param>
|
||||
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@ -15,6 +16,11 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
HttpStatusCode StatusCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Http protocol version
|
||||
/// </summary>
|
||||
Version HttpVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the status code indicates a success status
|
||||
/// </summary>
|
||||
|
@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
|
||||
namespace CryptoExchange.Net
|
||||
@ -43,5 +46,58 @@ namespace CryptoExchange.Net
|
||||
|
||||
return clientOrderId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new HttpMessageHandler instance
|
||||
/// </summary>
|
||||
public static HttpMessageHandler CreateHttpClientMessageHandler(ApiProxy? proxy, TimeSpan? keepAliveInterval)
|
||||
{
|
||||
#if NET5_0_OR_GREATER
|
||||
var socketHandler = new SocketsHttpHandler();
|
||||
try
|
||||
{
|
||||
if (keepAliveInterval != null && keepAliveInterval != TimeSpan.Zero)
|
||||
{
|
||||
socketHandler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always;
|
||||
socketHandler.KeepAlivePingDelay = keepAliveInterval.Value;
|
||||
socketHandler.KeepAlivePingTimeout = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
|
||||
socketHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
socketHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
||||
}
|
||||
catch (PlatformNotSupportedException) { }
|
||||
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||
|
||||
if (proxy != null)
|
||||
{
|
||||
socketHandler.Proxy = new WebProxy
|
||||
{
|
||||
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||
};
|
||||
}
|
||||
return socketHandler;
|
||||
#else
|
||||
var httpHandler = new HttpClientHandler();
|
||||
try
|
||||
{
|
||||
httpHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
httpHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
||||
}
|
||||
catch (PlatformNotSupportedException) { }
|
||||
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||
|
||||
if (proxy != null)
|
||||
{
|
||||
httpHandler.Proxy = new WebProxy
|
||||
{
|
||||
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||
};
|
||||
}
|
||||
return httpHandler;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,6 +206,11 @@ namespace CryptoExchange.Net.Objects
|
||||
/// </summary>
|
||||
public HttpMethod? RequestMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP protocol version
|
||||
/// </summary>
|
||||
public Version? HttpVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The headers sent with the request
|
||||
/// </summary>
|
||||
@ -251,6 +256,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// </summary>
|
||||
public WebCallResult(
|
||||
HttpStatusCode? code,
|
||||
Version? httpVersion,
|
||||
KeyValuePair<string, string[]>[]? responseHeaders,
|
||||
TimeSpan? responseTime,
|
||||
string? originalData,
|
||||
@ -262,6 +268,7 @@ namespace CryptoExchange.Net.Objects
|
||||
Error? error) : base(error)
|
||||
{
|
||||
ResponseStatusCode = code;
|
||||
HttpVersion = httpVersion;
|
||||
ResponseHeaders = responseHeaders;
|
||||
ResponseTime = responseTime;
|
||||
RequestId = requestId;
|
||||
@ -286,7 +293,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public WebCallResult AsError(Error error)
|
||||
{
|
||||
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
|
||||
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -297,7 +304,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public WebCallResult<K> As<K>([AllowNull] K data)
|
||||
{
|
||||
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, data, Error);
|
||||
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, data, Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -334,7 +341,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public WebCallResult<K> AsError<K>(Error error)
|
||||
{
|
||||
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, default, error);
|
||||
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, default, error);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -355,6 +362,11 @@ namespace CryptoExchange.Net.Objects
|
||||
/// </summary>
|
||||
public HttpMethod? RequestMethod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP protocol version
|
||||
/// </summary>
|
||||
public Version? HttpVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The headers sent with the request
|
||||
/// </summary>
|
||||
@ -403,21 +415,9 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <summary>
|
||||
/// Create a new result
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="responseHeaders"></param>
|
||||
/// <param name="responseTime"></param>
|
||||
/// <param name="responseLength"></param>
|
||||
/// <param name="originalData"></param>
|
||||
/// <param name="requestId"></param>
|
||||
/// <param name="requestUrl"></param>
|
||||
/// <param name="requestBody"></param>
|
||||
/// <param name="requestMethod"></param>
|
||||
/// <param name="requestHeaders"></param>
|
||||
/// <param name="dataSource"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="error"></param>
|
||||
public WebCallResult(
|
||||
HttpStatusCode? code,
|
||||
Version? httpVersion,
|
||||
KeyValuePair<string, string[]>[]? responseHeaders,
|
||||
TimeSpan? responseTime,
|
||||
long? responseLength,
|
||||
@ -431,6 +431,7 @@ namespace CryptoExchange.Net.Objects
|
||||
[AllowNull] T data,
|
||||
Error? error) : base(data, originalData, error)
|
||||
{
|
||||
HttpVersion = httpVersion;
|
||||
ResponseStatusCode = code;
|
||||
ResponseHeaders = responseHeaders;
|
||||
ResponseTime = responseTime;
|
||||
@ -450,7 +451,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public new WebCallResult AsDataless()
|
||||
{
|
||||
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error);
|
||||
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error);
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy as a dataless result
|
||||
@ -458,14 +459,14 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public new WebCallResult AsDatalessError(Error error)
|
||||
{
|
||||
return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
|
||||
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new error result
|
||||
/// </summary>
|
||||
/// <param name="error">The error</param>
|
||||
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, default, error) { }
|
||||
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, default, error) { }
|
||||
|
||||
/// <summary>
|
||||
/// Copy the WebCallResult to a new data type
|
||||
@ -475,7 +476,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public new WebCallResult<K> As<K>([AllowNull] K data)
|
||||
{
|
||||
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error);
|
||||
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -486,7 +487,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public new WebCallResult<K> AsError<K>(Error error)
|
||||
{
|
||||
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, default, error);
|
||||
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, default, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -498,7 +499,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
public new WebCallResult<K> AsErrorWithData<K>(Error error, K data)
|
||||
{
|
||||
return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, error);
|
||||
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -569,7 +570,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <returns></returns>
|
||||
internal WebCallResult<T> Cached()
|
||||
{
|
||||
return new WebCallResult<T>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Cache, Data, Error);
|
||||
return new WebCallResult<T>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Cache, Data, Error);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -1,5 +1,7 @@
|
||||
using CryptoExchange.Net.Authentication;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace CryptoExchange.Net.Objects.Options
|
||||
{
|
||||
@ -28,6 +30,20 @@ namespace CryptoExchange.Net.Objects.Options
|
||||
/// </summary>
|
||||
public TimeSpan CachingMaxAge { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP protocol version to use, typically 2.0 or 1.1
|
||||
/// </summary>
|
||||
public Version HttpVersion { get; set; }
|
||||
#if NET5_0_OR_GREATER
|
||||
= new Version(2, 0);
|
||||
#else
|
||||
= new Version(1, 1);
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Http client keep alive interval for keeping connections open
|
||||
/// </summary>
|
||||
public TimeSpan? HttpKeepAliveInterval { get; set; } = TimeSpan.FromSeconds(15);
|
||||
|
||||
/// <summary>
|
||||
/// Set the values of this options on the target options
|
||||
/// </summary>
|
||||
@ -43,6 +59,8 @@ namespace CryptoExchange.Net.Objects.Options
|
||||
item.RateLimitingBehaviour = RateLimitingBehaviour;
|
||||
item.CachingEnabled = CachingEnabled;
|
||||
item.CachingMaxAge = CachingMaxAge;
|
||||
item.HttpVersion = HttpVersion;
|
||||
item.HttpKeepAliveInterval = HttpKeepAliveInterval;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,9 @@ namespace CryptoExchange.Net.Requests
|
||||
/// <inheritdoc />
|
||||
public Uri Uri => _request.RequestUri!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version HttpVersion => _request.Version!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int RequestId { get; }
|
||||
|
||||
@ -81,7 +84,9 @@ namespace CryptoExchange.Net.Requests
|
||||
/// <inheritdoc />
|
||||
public async Task<IResponse> GetResponseAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return new Response(await _httpClient.SendAsync(_request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false));
|
||||
var response = await _httpClient.SendAsync(_request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return new Response(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Net;
|
||||
using System.Net.Http;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
|
||||
namespace CryptoExchange.Net.Requests
|
||||
{
|
||||
@ -14,54 +15,43 @@ namespace CryptoExchange.Net.Requests
|
||||
private HttpClient? _httpClient;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null)
|
||||
public void Configure(RestExchangeOptions options, HttpClient? client = null)
|
||||
{
|
||||
if (client == null)
|
||||
client = CreateClient(proxy, requestTimeout);
|
||||
client = CreateClient(options.Proxy, options.RequestTimeout, options.HttpKeepAliveInterval);
|
||||
|
||||
_httpClient = client;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IRequest Create(HttpMethod method, Uri uri, int requestId)
|
||||
public IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId)
|
||||
{
|
||||
if (_httpClient == null)
|
||||
throw new InvalidOperationException("Cant create request before configuring http client");
|
||||
|
||||
return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId);
|
||||
var requestMessage = new HttpRequestMessage(method, uri);
|
||||
requestMessage.Version = httpRequestVersion;
|
||||
#if NET5_0_OR_GREATER
|
||||
requestMessage.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
|
||||
#endif
|
||||
return new Request(requestMessage, _httpClient, requestId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout)
|
||||
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval)
|
||||
{
|
||||
_httpClient = CreateClient(proxy, requestTimeout);
|
||||
_httpClient = CreateClient(proxy, requestTimeout, httpKeepAliveInterval);
|
||||
}
|
||||
|
||||
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
|
||||
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval)
|
||||
{
|
||||
var handler = new HttpClientHandler();
|
||||
try
|
||||
{
|
||||
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
||||
}
|
||||
catch (PlatformNotSupportedException) { }
|
||||
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||
|
||||
if (proxy != null)
|
||||
{
|
||||
handler.Proxy = new WebProxy
|
||||
{
|
||||
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
|
||||
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
|
||||
};
|
||||
}
|
||||
|
||||
var handler = LibraryHelpers.CreateHttpClientMessageHandler(proxy, httpKeepAliveInterval);
|
||||
var client = new HttpClient(handler)
|
||||
{
|
||||
Timeout = requestTimeout
|
||||
};
|
||||
return client;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@ -18,6 +19,9 @@ namespace CryptoExchange.Net.Requests
|
||||
/// <inheritdoc />
|
||||
public HttpStatusCode StatusCode => _response.StatusCode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Version HttpVersion => _response.Version;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSuccessStatusCode => _response.IsSuccessStatusCode;
|
||||
|
||||
|
@ -48,6 +48,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
WebCallResult<T> result,
|
||||
INextPageToken? nextPageToken = null) :
|
||||
base(result.ResponseStatusCode,
|
||||
result.HttpVersion,
|
||||
result.ResponseHeaders,
|
||||
result.ResponseTime,
|
||||
result.ResponseLength,
|
||||
@ -75,6 +76,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
WebCallResult<T> result,
|
||||
INextPageToken? nextPageToken = null) :
|
||||
base(result.ResponseStatusCode,
|
||||
result.HttpVersion,
|
||||
result.ResponseHeaders,
|
||||
result.ResponseTime,
|
||||
result.ResponseLength,
|
||||
@ -100,6 +102,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
string exchange,
|
||||
TradingMode[]? dataTradeModes,
|
||||
HttpStatusCode? code,
|
||||
Version? httpVersion,
|
||||
KeyValuePair<string, string[]>[]? responseHeaders,
|
||||
TimeSpan? responseTime,
|
||||
long? responseLength,
|
||||
@ -114,6 +117,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
Error? error,
|
||||
INextPageToken? nextPageToken = null) : base(
|
||||
code,
|
||||
httpVersion,
|
||||
responseHeaders,
|
||||
responseTime,
|
||||
responseLength,
|
||||
@ -140,7 +144,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// <returns></returns>
|
||||
public new ExchangeWebResult<K> As<K>([AllowNull] K data)
|
||||
{
|
||||
return new ExchangeWebResult<K>(Exchange, DataTradeMode, ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageToken);
|
||||
return new ExchangeWebResult<K>(Exchange, DataTradeMode, ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -21,6 +21,8 @@ namespace CryptoExchange.Net.Testing.Implementations
|
||||
|
||||
public Uri Uri { get; set; }
|
||||
|
||||
public Version HttpVersion { get; set; }
|
||||
|
||||
public int RequestId { get; set; }
|
||||
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
@ -1,5 +1,6 @@
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using CryptoExchange.Net.Objects.Options;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
@ -14,11 +15,11 @@ namespace CryptoExchange.Net.Testing.Implementations
|
||||
_request = request;
|
||||
}
|
||||
|
||||
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null)
|
||||
public void Configure(RestExchangeOptions options, HttpClient? client)
|
||||
{
|
||||
}
|
||||
|
||||
public IRequest Create(HttpMethod method, Uri uri, int requestId)
|
||||
public IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId)
|
||||
{
|
||||
_request.Method = method;
|
||||
_request.Uri = uri;
|
||||
@ -26,6 +27,6 @@ namespace CryptoExchange.Net.Testing.Implementations
|
||||
return _request;
|
||||
}
|
||||
|
||||
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout) {}
|
||||
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval) {}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
@ -11,6 +12,7 @@ namespace CryptoExchange.Net.Testing.Implementations
|
||||
private readonly Stream _response;
|
||||
|
||||
public HttpStatusCode StatusCode { get; }
|
||||
public Version HttpVersion { get; }
|
||||
|
||||
public bool IsSuccessStatusCode { get; }
|
||||
|
||||
@ -21,6 +23,7 @@ namespace CryptoExchange.Net.Testing.Implementations
|
||||
public TestResponse(HttpStatusCode code, Stream response)
|
||||
{
|
||||
StatusCode = code;
|
||||
HttpVersion = new Version(2, 0);
|
||||
IsSuccessStatusCode = code == HttpStatusCode.OK;
|
||||
_response = response;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user