diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml
index ed94e55..0f06bb5 100644
--- a/CryptoExchange.Net/CryptoExchange.Net.xml
+++ b/CryptoExchange.Net/CryptoExchange.Net.xml
@@ -633,13 +633,13 @@
-
+
Configure the requests created by this factory
Request timeout to use
- Proxy settings to use
- Should generate unique id for requests
+ Proxy settings to use
+ Optional shared http client instance
@@ -1622,12 +1622,24 @@
The time the server has to respond to a request before timing out
+
+
+ http client
+
+
ctor
+
+
+ ctor
+
+
+ Shared http client
+
Create a copy of the options
@@ -2030,13 +2042,12 @@
Request object
-
+
Create request object for web request
-
- if true, should assign unique id for request
+
@@ -2070,7 +2081,7 @@
WebRequest factory
-
+
@@ -2958,5 +2969,148 @@
+
+
+ Specifies that is allowed as an input even if the
+ corresponding type disallows it.
+
+
+
+
+ Initializes a new instance of the class.
+
+
+
+
+ Specifies that is disallowed as an input even if the
+ corresponding type allows it.
+
+
+
+
+ Initializes a new instance of the class.
+
+
+
+
+ Specifies that a method that will never return under any circumstance.
+
+
+
+
+ Initializes a new instance of the class.
+
+
+
+
+ Specifies that the method will not return if the associated
+ parameter is passed the specified value.
+
+
+
+
+ Gets the condition parameter value.
+ Code after the method is considered unreachable by diagnostics if the argument
+ to the associated parameter matches this value.
+
+
+
+
+ Initializes a new instance of the
+ class with the specified parameter value.
+
+
+ The condition parameter value.
+ Code after the method is considered unreachable by diagnostics if the argument
+ to the associated parameter matches this value.
+
+
+
+
+ Specifies that an output may be even if the
+ corresponding type disallows it.
+
+
+
+
+ Initializes a new instance of the class.
+
+
+
+
+ Specifies that when a method returns ,
+ the parameter may be even if the corresponding type disallows it.
+
+
+
+
+ Gets the return value condition.
+ If the method returns this value, the associated parameter may be .
+
+
+
+
+ Initializes the attribute with the specified return value condition.
+
+
+ The return value condition.
+ If the method returns this value, the associated parameter may be .
+
+
+
+
+ Specifies that an output is not even if the
+ corresponding type allows it.
+
+
+
+
+ Initializes a new instance of the class.
+
+
+
+
+ Specifies that the output will be non- if the
+ named parameter is non-.
+
+
+
+
+ Gets the associated parameter name.
+ The output will be non- if the argument to the
+ parameter specified is non-.
+
+
+
+
+ Initializes the attribute with the associated parameter name.
+
+
+ The associated parameter name.
+ The output will be non- if the argument to the
+ parameter specified is non-.
+
+
+
+
+ Specifies that when a method returns ,
+ the parameter will not be even if the corresponding type allows it.
+
+
+
+
+ Gets the return value condition.
+ If the method returns this value, the associated parameter will not be .
+
+
+
+
+ Initializes the attribute with the specified return value condition.
+
+
+ The return value condition.
+ If the method returns this value, the associated parameter will not be .
+
+
diff --git a/CryptoExchange.Net/Interfaces/IRequestFactory.cs b/CryptoExchange.Net/Interfaces/IRequestFactory.cs
index 59b1bb8..e05b371 100644
--- a/CryptoExchange.Net/Interfaces/IRequestFactory.cs
+++ b/CryptoExchange.Net/Interfaces/IRequestFactory.cs
@@ -22,6 +22,7 @@ namespace CryptoExchange.Net.Interfaces
///
/// Request timeout to use
/// Proxy settings to use
- void Configure(TimeSpan requestTimeout, ApiProxy? proxy);
+ /// Optional shared http client instance
+ void Configure(TimeSpan requestTimeout, ApiProxy? proxy, HttpClient? httpClient=null);
}
}
diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs
index 577de1e..bb09671 100644
--- a/CryptoExchange.Net/Objects/Options.cs
+++ b/CryptoExchange.Net/Objects/Options.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Net.Http;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging;
@@ -126,7 +127,10 @@ namespace CryptoExchange.Net.Objects
/// The time the server has to respond to a request before timing out
///
public TimeSpan RequestTimeout { get; set; } = TimeSpan.FromSeconds(30);
-
+ ///
+ /// http client
+ ///
+ public HttpClient? HttpClient;
///
/// ctor
///
@@ -134,7 +138,15 @@ namespace CryptoExchange.Net.Objects
public RestClientOptions(string baseAddress): base(baseAddress)
{
}
-
+ ///
+ /// ctor
+ ///
+ ///
+ /// Shared http client
+ public RestClientOptions(HttpClient httpClient, string baseAddress) : base(baseAddress)
+ {
+ HttpClient = httpClient;
+ }
///
/// Create a copy of the options
///
@@ -150,7 +162,8 @@ namespace CryptoExchange.Net.Objects
LogWriters = LogWriters,
RateLimiters = RateLimiters,
RateLimitingBehaviour = RateLimitingBehaviour,
- RequestTimeout = RequestTimeout
+ RequestTimeout = RequestTimeout,
+ HttpClient = HttpClient
};
if (ApiCredentials != null)
diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs
index 54cd8a2..3502c61 100644
--- a/CryptoExchange.Net/Requests/RequestFactory.cs
+++ b/CryptoExchange.Net/Requests/RequestFactory.cs
@@ -11,21 +11,28 @@ namespace CryptoExchange.Net.Requests
///
public class RequestFactory : IRequestFactory
{
- private HttpClient? httpClient;
- private bool isTracingEnabled;
- ///
- public void Configure(TimeSpan requestTimeout, ApiProxy? proxy)
- {
- HttpMessageHandler handler = new HttpClientHandler()
- {
- Proxy = proxy == null ? null : new WebProxy
- {
- Address = new Uri($"{proxy.Host}:{proxy.Port}"),
- Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
- }
- };
+ private HttpClient? httpClient;
- httpClient = new HttpClient(handler) { Timeout = requestTimeout };
+ ///
+ public void Configure(TimeSpan requestTimeout, ApiProxy? proxy, HttpClient? client = null)
+ {
+ if (client == null)
+ {
+ HttpMessageHandler handler = new HttpClientHandler()
+ {
+ Proxy = proxy == null ? null : new WebProxy
+ {
+ Address = new Uri($"{proxy.Host}:{proxy.Port}"),
+ Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
+ }
+ };
+
+ httpClient = new HttpClient(handler) { Timeout = requestTimeout };
+ }
+ else
+ {
+ httpClient = client;
+ }
}
///
diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs
index e9932d2..945ff79 100644
--- a/CryptoExchange.Net/RestClient.cs
+++ b/CryptoExchange.Net/RestClient.cs
@@ -24,13 +24,13 @@ namespace CryptoExchange.Net
///
/// Base rest client
///
- public abstract class RestClient: BaseClient, IRestClient
+ public abstract class RestClient : BaseClient, IRestClient
{
///
/// The factory for creating requests. Used for unit testing
///
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
-
+
///
/// Where to place post parameters
///
@@ -77,13 +77,13 @@ namespace CryptoExchange.Net
///
///
///
- protected RestClient(RestClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider): base(exchangeOptions, authenticationProvider)
+ protected RestClient(RestClientOptions exchangeOptions, AuthenticationProvider? authenticationProvider) : base(exchangeOptions, authenticationProvider)
{
if (exchangeOptions == null)
throw new ArgumentNullException(nameof(exchangeOptions));
RequestTimeout = exchangeOptions.RequestTimeout;
- RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy);
+ RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour;
var rateLimiters = new List();
foreach (var rateLimiter in exchangeOptions.RateLimiters)
@@ -134,10 +134,10 @@ namespace CryptoExchange.Net
{
reply = await ping.SendPingAsync(uri.Host).ConfigureAwait(false);
}
- catch(PingException e)
+ catch (PingException e)
{
if (e.InnerException == null)
- return new CallResult(0, new CantConnectError {Message = "Ping failed: " + e.Message});
+ return new CallResult(0, new CantConnectError { Message = "Ping failed: " + e.Message });
if (e.InnerException is SocketException exception)
return new CallResult(0, new CantConnectError { Message = "Ping failed: " + exception.SocketErrorCode });
@@ -149,7 +149,7 @@ namespace CryptoExchange.Net
ping.Dispose();
}
- if(ct.IsCancellationRequested)
+ if (ct.IsCancellationRequested)
return new CallResult(0, new CancellationRequestedError());
return reply.Status == IPStatus.Success ? new CallResult(reply.RoundtripTime, null) : new CallResult(0, new CantConnectError { Message = "Ping failed: " + reply.Status });
@@ -174,7 +174,7 @@ namespace CryptoExchange.Net
{
log.Write(LogVerbosity.Debug, "Creating request for " + uri);
if (signed && authProvider == null)
- {
+ {
log.Write(LogVerbosity.Warning, $"Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
return new WebCallResult(null, null, null, new NoApiCredentialsError());
}
@@ -185,19 +185,19 @@ namespace CryptoExchange.Net
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour);
if (!limitResult.Success)
{
- log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} failed because of rate limit");
+ log.Write(LogVerbosity.Debug, $"Request {request.RequestId} {uri.AbsolutePath} failed because of rate limit");
return new WebCallResult(null, null, null, limitResult.Error);
}
if (limitResult.Data > 0)
- log.Write(LogVerbosity.Debug, $"Request {request.RequestId} {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}");
+ log.Write(LogVerbosity.Debug, $"Request {request.RequestId} {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}");
}
- string? paramString = null;
- if (method == HttpMethod.Post)
+ string? paramString = null;
+ if (method == HttpMethod.Post)
paramString = " with request body " + request.Content;
- log.Write(LogVerbosity.Debug, $"Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null? "": $" via proxy {apiProxy.Host}")} with id {request.RequestId}");
+ log.Write(LogVerbosity.Debug, $"Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null ? "" : $" via proxy {apiProxy.Host}")} with id {request.RequestId}");
return await GetResponse(request, cancellationToken).ConfigureAwait(false);
}
@@ -230,7 +230,7 @@ namespace CryptoExchange.Net
if (!parseResult.Success)
return WebCallResult.CreateErrorResult(response.StatusCode, response.ResponseHeaders, new ServerError(data));
var error = await TryParseError(parseResult.Data);
- if(error != null)
+ if (error != null)
return WebCallResult.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error);
var deserializeResult = Deserialize(parseResult.Data);
@@ -253,7 +253,7 @@ namespace CryptoExchange.Net
responseStream.Close();
response.Close();
var parseResult = ValidateJson(data);
- return new WebCallResult(statusCode, headers, default, parseResult.Success ? ParseErrorResponse(parseResult.Data) :new ServerError(data));
+ return new WebCallResult(statusCode, headers, default, parseResult.Success ? ParseErrorResponse(parseResult.Data) : new ServerError(data));
}
}
catch (HttpRequestException requestException)
@@ -263,7 +263,7 @@ namespace CryptoExchange.Net
}
catch (TaskCanceledException canceledException)
{
- if(canceledException.CancellationToken == cancellationToken)
+ if (canceledException.CancellationToken == cancellationToken)
{
// Cancellation token cancelled
log.Write(LogVerbosity.Warning, $"Request {request.RequestId} cancel requested");
@@ -305,10 +305,10 @@ namespace CryptoExchange.Net
parameters = new Dictionary();
var uriString = uri.ToString();
- if(authProvider != null)
+ if (authProvider != null)
parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, postPosition, arraySerialization);
- if((method == HttpMethod.Get || method == HttpMethod.Delete || postPosition == PostParameters.InUri) && parameters?.Any() == true)
+ if ((method == HttpMethod.Get || method == HttpMethod.Delete || postPosition == PostParameters.InUri) && parameters?.Any() == true)
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
@@ -324,7 +324,7 @@ namespace CryptoExchange.Net
if ((method == HttpMethod.Post || method == HttpMethod.Put) && postPosition != PostParameters.InUri)
{
- if(parameters?.Any() == true)
+ if (parameters?.Any() == true)
WriteParamBody(request, parameters, contentType);
else
request.SetContent(requestBodyEmptyContent, contentType);
@@ -346,7 +346,7 @@ namespace CryptoExchange.Net
var stringData = JsonConvert.SerializeObject(parameters.OrderBy(p => p.Key).ToDictionary(p => p.Key, p => p.Value));
request.SetContent(stringData, contentType);
}
- else if(requestBodyFormat == RequestBodyFormat.FormData)
+ else if (requestBodyFormat == RequestBodyFormat.FormData)
{
var formData = HttpUtility.ParseQueryString(string.Empty);
foreach (var kvp in parameters.OrderBy(p => p.Key))
@@ -354,7 +354,7 @@ namespace CryptoExchange.Net
if (kvp.Value.GetType().IsArray)
{
var array = (Array)kvp.Value;
- foreach(var value in array)
+ foreach (var value in array)
formData.Add(kvp.Key, value.ToString());
}
else
@@ -363,7 +363,7 @@ namespace CryptoExchange.Net
var stringData = formData.ToString();
request.SetContent(stringData, contentType);
}
- }
+ }
///
/// Parse an error response from the server. Only used when server returns a status other than Success(200)