diff --git a/src/ExchangeSharp/API/Common/APIRequestMaker.cs b/src/ExchangeSharp/API/Common/APIRequestMaker.cs
index baabc24c..10e70927 100644
--- a/src/ExchangeSharp/API/Common/APIRequestMaker.cs
+++ b/src/ExchangeSharp/API/Common/APIRequestMaker.cs
@@ -21,110 +21,110 @@ The above copyright notice and this permission notice shall be included in all c
namespace ExchangeSharp
{
- ///
- /// Handles all the logic for making API calls.
- ///
- ///
- public sealed class APIRequestMaker : IAPIRequestMaker
- {
- private readonly IAPIRequestHandler api;
-
- ///
- /// Proxy for http requests, reads from HTTP_PROXY environment var by default
- /// You can also set via code if you like
- ///
- private static readonly HttpClientHandler ClientHandler = new HttpClientHandler();
- public static IWebProxy? Proxy
- {
- get => ClientHandler.Proxy;
- set
- {
- ClientHandler.Proxy = value;
- }
- }
- public static readonly HttpClient Client = new HttpClient(ClientHandler);
-
- ///
- /// Static constructor
- ///
- static APIRequestMaker()
- {
- var httpProxy = Environment.GetEnvironmentVariable("http_proxy");
- httpProxy ??= Environment.GetEnvironmentVariable("HTTP_PROXY");
-
- if (!string.IsNullOrWhiteSpace(httpProxy))
- {
- var uri = new Uri(httpProxy);
- Proxy = new WebProxy(uri);
- }
-
- Client.DefaultRequestHeaders.ConnectionClose = true; // disable keep-alive
- Client.Timeout = Timeout.InfiniteTimeSpan; // we handle timeout by ourselves
- }
-
- internal class InternalHttpWebRequest : IHttpWebRequest
- {
- internal readonly HttpRequestMessage Request;
- internal HttpResponseMessage? Response;
- private string? contentType;
-
- public InternalHttpWebRequest(string method, Uri fullUri)
- {
- Request = new HttpRequestMessage(new HttpMethod(method), fullUri);
- }
-
- public void AddHeader(string header, string value)
- {
- switch (header.ToLowerInvariant())
- {
- case "content-type":
- contentType = value;
- break;
- default:
- Request.Headers.Add(header, value);
- break;
- }
- }
-
- public Uri RequestUri
- {
- get { return Request.RequestUri; }
- }
-
- public string Method
- {
- get { return Request.Method.Method; }
- set { Request.Method = new HttpMethod(value); }
- }
-
- public int Timeout { get; set; }
-
- public int ReadWriteTimeout
- {
- get => Timeout;
- set => Timeout = value;
- }
-
-
- public Task WriteAllAsync(byte[] data, int index, int length)
- {
- Request.Content = new ByteArrayContent(data, index, length);
- Request.Content.Headers.Add("content-type", contentType);
+ ///
+ /// Handles all the logic for making API calls.
+ ///
+ ///
+ public sealed class APIRequestMaker : IAPIRequestMaker
+ {
+ private readonly IAPIRequestHandler api;
+
+ ///
+ /// Proxy for http requests, reads from HTTP_PROXY environment var by default
+ /// You can also set via code if you like
+ ///
+ private static readonly HttpClientHandler ClientHandler = new HttpClientHandler();
+ public static IWebProxy? Proxy
+ {
+ get => ClientHandler.Proxy;
+ set
+ {
+ ClientHandler.Proxy = value;
+ }
+ }
+ public static readonly HttpClient Client = new HttpClient(ClientHandler);
+
+ ///
+ /// Static constructor
+ ///
+ static APIRequestMaker()
+ {
+ var httpProxy = Environment.GetEnvironmentVariable("http_proxy");
+ httpProxy ??= Environment.GetEnvironmentVariable("HTTP_PROXY");
+
+ if (!string.IsNullOrWhiteSpace(httpProxy))
+ {
+ var uri = new Uri(httpProxy);
+ Proxy = new WebProxy(uri);
+ }
+
+ Client.DefaultRequestHeaders.ConnectionClose = true; // disable keep-alive
+ Client.Timeout = Timeout.InfiniteTimeSpan; // we handle timeout by ourselves
+ }
+
+ internal class InternalHttpWebRequest : IHttpWebRequest
+ {
+ internal readonly HttpRequestMessage Request;
+ internal HttpResponseMessage? Response;
+ private string? contentType;
+
+ public InternalHttpWebRequest(string method, Uri fullUri)
+ {
+ Request = new HttpRequestMessage(new HttpMethod(method), fullUri);
+ }
+
+ public void AddHeader(string header, string value)
+ {
+ switch (header.ToLowerInvariant())
+ {
+ case "content-type":
+ contentType = value;
+ break;
+ default:
+ Request.Headers.TryAddWithoutValidation(header, value);
+ break;
+ }
+ }
+
+ public Uri RequestUri
+ {
+ get { return Request.RequestUri; }
+ }
+
+ public string Method
+ {
+ get { return Request.Method.Method; }
+ set { Request.Method = new HttpMethod(value); }
+ }
+
+ public int Timeout { get; set; }
+
+ public int ReadWriteTimeout
+ {
+ get => Timeout;
+ set => Timeout = value;
+ }
+
+
+ public Task WriteAllAsync(byte[] data, int index, int length)
+ {
+ Request.Content = new ByteArrayContent(data, index, length);
+ Request.Content.Headers.Add("content-type", contentType);
return Task.CompletedTask;
- }
- }
+ }
+ }
- internal class InternalHttpWebResponse : IHttpWebResponse
- {
- private readonly HttpResponseMessage response;
+ internal class InternalHttpWebResponse : IHttpWebResponse
+ {
+ private readonly HttpResponseMessage response;
- public InternalHttpWebResponse(HttpResponseMessage response)
- {
- this.response = response;
- }
+ public InternalHttpWebResponse(HttpResponseMessage response)
+ {
+ this.response = response;
+ }
- public IReadOnlyList GetHeader(string name)
- {
+ public IReadOnlyList GetHeader(string name)
+ {
try
{
return response.Headers.GetValues(name).ToArray(); // throws InvalidOperationException when name not exist
@@ -135,102 +135,102 @@ public IReadOnlyList GetHeader(string name)
}
}
- public Dictionary> Headers
- {
- get
- {
- return response.Headers.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value.ToArray());
- }
- }
- }
-
- ///
- /// Constructor
- ///
- /// API
- public APIRequestMaker(IAPIRequestHandler api)
- {
- this.api = api;
- }
-
- ///
- /// Make a request to a path on the API
- ///
- /// Path and query
- /// Override the base url, null for the default BaseUrl
- /// Payload, can be null. For private API end points, the payload must contain a 'nonce' key set to GenerateNonce value.
- /// The encoding of payload is API dependant but is typically json.
- /// Request method or null for default. Example: 'GET' or 'POST'.
- /// Raw response
- public async Task MakeRequestAsync(string url, string? baseUrl = null, Dictionary? payload = null, string? method = null)
- {
- await new SynchronizationContextRemover();
- await api.RateLimit.WaitToProceedAsync();
-
- if (url[0] != '/')
- {
- url = "/" + url;
- }
-
- // prepare the request
- string fullUrl = (baseUrl ?? api.BaseUrl) + url;
- method ??= api.RequestMethod;
- Uri uri = api.ProcessRequestUrl(new UriBuilder(fullUrl), payload, method);
- var request = new InternalHttpWebRequest(method, uri);
- request.AddHeader("accept-language", "en-US,en;q=0.5");
- request.AddHeader("content-type", api.RequestContentType);
- request.AddHeader("user-agent", BaseAPI.RequestUserAgent);
- request.Timeout = (int)api.RequestTimeout.TotalMilliseconds;
- await api.ProcessRequestAsync(request, payload);
-
- // send the request
- var response = request.Response;
- string responseString;
- using var cancel = new CancellationTokenSource(request.Timeout);
- try
- {
- RequestStateChanged?.Invoke(this, RequestMakerState.Begin, uri.AbsoluteUri);// when start make a request we send the uri, this helps developers to track the http requests.
- response = await Client.SendAsync(request.Request, cancel.Token);
- if (response == null)
- {
- throw new APIException("Unknown response from server");
- }
- responseString = await response.Content.ReadAsStringAsync();
-
- if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
- {
- // 404 maybe return empty responseString
- if (string.IsNullOrWhiteSpace(responseString))
- {
- throw new APIException(string.Format("{0} - {1}", response.StatusCode.ConvertInvariant(), response.StatusCode));
- }
-
- throw new APIException(responseString);
- }
+ public Dictionary> Headers
+ {
+ get
+ {
+ return response.Headers.ToDictionary(x => x.Key, x => (IReadOnlyList)x.Value.ToArray());
+ }
+ }
+ }
+
+ ///
+ /// Constructor
+ ///
+ /// API
+ public APIRequestMaker(IAPIRequestHandler api)
+ {
+ this.api = api;
+ }
+
+ ///
+ /// Make a request to a path on the API
+ ///
+ /// Path and query
+ /// Override the base url, null for the default BaseUrl
+ /// Payload, can be null. For private API end points, the payload must contain a 'nonce' key set to GenerateNonce value.
+ /// The encoding of payload is API dependant but is typically json.
+ /// Request method or null for default. Example: 'GET' or 'POST'.
+ /// Raw response
+ public async Task MakeRequestAsync(string url, string? baseUrl = null, Dictionary? payload = null, string? method = null)
+ {
+ await new SynchronizationContextRemover();
+ await api.RateLimit.WaitToProceedAsync();
+
+ if (url[0] != '/')
+ {
+ url = "/" + url;
+ }
+
+ // prepare the request
+ string fullUrl = (baseUrl ?? api.BaseUrl) + url;
+ method ??= api.RequestMethod;
+ Uri uri = api.ProcessRequestUrl(new UriBuilder(fullUrl), payload, method);
+ var request = new InternalHttpWebRequest(method, uri);
+ request.AddHeader("accept-language", "en-US,en;q=0.5");
+ request.AddHeader("content-type", api.RequestContentType);
+ request.AddHeader("user-agent", BaseAPI.RequestUserAgent);
+ request.Timeout = (int)api.RequestTimeout.TotalMilliseconds;
+ await api.ProcessRequestAsync(request, payload);
+
+ // send the request
+ var response = request.Response;
+ string responseString;
+ using var cancel = new CancellationTokenSource(request.Timeout);
+ try
+ {
+ RequestStateChanged?.Invoke(this, RequestMakerState.Begin, uri.AbsoluteUri);// when start make a request we send the uri, this helps developers to track the http requests.
+ response = await Client.SendAsync(request.Request, cancel.Token);
+ if (response == null)
+ {
+ throw new APIException("Unknown response from server");
+ }
+ responseString = await response.Content.ReadAsStringAsync();
+
+ if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
+ {
+ // 404 maybe return empty responseString
+ if (string.IsNullOrWhiteSpace(responseString))
+ {
+ throw new APIException(string.Format("{0} - {1}", response.StatusCode.ConvertInvariant(), response.StatusCode));
+ }
+
+ throw new APIException(responseString);
+ }
api.ProcessResponse(new InternalHttpWebResponse(response));
- RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString);
- }
- catch (OperationCanceledException ex) when (cancel.IsCancellationRequested)
- {
- RequestStateChanged?.Invoke(this, RequestMakerState.Error, ex);
- throw new TimeoutException("APIRequest timeout", ex);
- }
- catch (Exception ex)
- {
- RequestStateChanged?.Invoke(this, RequestMakerState.Error, ex);
- throw;
- }
- finally
- {
- response?.Dispose();
- }
- return responseString;
- }
-
- ///
- /// An action to execute when a request has been made (this request and state and object (response or exception))
- ///
- public Action? RequestStateChanged { get; set; }
- }
+ RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString);
+ }
+ catch (OperationCanceledException ex) when (cancel.IsCancellationRequested)
+ {
+ RequestStateChanged?.Invoke(this, RequestMakerState.Error, ex);
+ throw new TimeoutException("APIRequest timeout", ex);
+ }
+ catch (Exception ex)
+ {
+ RequestStateChanged?.Invoke(this, RequestMakerState.Error, ex);
+ throw;
+ }
+ finally
+ {
+ response?.Dispose();
+ }
+ return responseString;
+ }
+
+ ///
+ /// An action to execute when a request has been made (this request and state and object (response or exception))
+ ///
+ public Action? RequestStateChanged { get; set; }
+ }
}
diff --git a/src/ExchangeSharp/API/Common/BaseAPI.cs b/src/ExchangeSharp/API/Common/BaseAPI.cs
index 46b459d5..e5f376cc 100644
--- a/src/ExchangeSharp/API/Common/BaseAPI.cs
+++ b/src/ExchangeSharp/API/Common/BaseAPI.cs
@@ -510,7 +510,7 @@ public async Task MakeJsonRequestAsync(string url, string? baseUrl = null,
/// Callback for messages
/// Connect callback
/// Web socket - dispose of the wrapper to shutdown the socket
- public Task ConnectWebSocketAsync
+ public virtual Task ConnectWebSocketAsync
(
string url,
Func messageCallback,
@@ -551,7 +551,7 @@ public Task ConnectWebSocketAsync
/// Callback for messages
/// Connect callback
/// Web socket - dispose of the wrapper to shutdown the socket
- public Task ConnectPublicWebSocketAsync
+ public virtual Task ConnectPublicWebSocketAsync
(
string url,
Func messageCallback,
@@ -577,7 +577,7 @@ public Task ConnectPublicWebSocketAsync
/// Connect callback
/// Text Message callback
/// Web socket - dispose of the wrapper to shutdown the socket
- public Task ConnectPrivateWebSocketAsync
+ public virtual Task ConnectPrivateWebSocketAsync
(
string url,
Func messageCallback,