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,