From 9b8950747ffa0f70be087ed559c4f2d6907a6322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC=20=D0=9A=D1=83=D1=80=D1=8C?= =?UTF-8?q?=D1=8F=D0=BD=D0=BE=D0=B2?= Date: Fri, 14 Aug 2020 14:15:13 +0000 Subject: [PATCH 1/5] added optional request tracing id for much better logs reading --- CryptoExchange.Net/CryptoExchange.Net.xml | 163 ++---------------- CryptoExchange.Net/Interfaces/IRequest.cs | 4 + .../Interfaces/IRequestFactory.cs | 3 +- CryptoExchange.Net/Objects/Options.cs | 2 +- CryptoExchange.Net/Requests/Request.cs | 10 +- CryptoExchange.Net/Requests/RequestFactory.cs | 9 +- CryptoExchange.Net/RestClient.cs | 18 +- 7 files changed, 44 insertions(+), 165 deletions(-) diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index 5415b61..ff62654 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -588,6 +588,11 @@ Uri + + + internal request id for tracing + + Set byte content @@ -628,12 +633,13 @@ - + Configure the requests created by this factory Request timeout to use Proxy settings to use + Should generate unique id for requests @@ -1772,7 +1778,7 @@ - Event when order book was updated, containing the changed bids and asks. Be careful! It can generate a lot of events at high-liquidity markets + Event when order book was updated, containing the changed bids and asks. Be careful! It can generate a lot of events at high-liquidity markets @@ -2025,12 +2031,13 @@ Request object - + Create request object for web request + if true, should assign unique id for request @@ -2044,6 +2051,9 @@ + + + @@ -2061,7 +2071,7 @@ WebRequest factory - + @@ -3094,148 +3104,3 @@ -System.Diagnostics.CodeAnalysis.AllowNullAttribute"> - - 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/IRequest.cs b/CryptoExchange.Net/Interfaces/IRequest.cs index 9e819cb..9d02651 100644 --- a/CryptoExchange.Net/Interfaces/IRequest.cs +++ b/CryptoExchange.Net/Interfaces/IRequest.cs @@ -27,6 +27,10 @@ namespace CryptoExchange.Net.Interfaces /// Uri Uri { get; } /// + /// internal request id for tracing + /// + string? RequestId { get; } + /// /// Set byte content /// /// diff --git a/CryptoExchange.Net/Interfaces/IRequestFactory.cs b/CryptoExchange.Net/Interfaces/IRequestFactory.cs index ceb7f8c..d519fe9 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); + /// Should generate unique id for requests + void Configure(TimeSpan requestTimeout, ApiProxy? proxy, bool isTracingEnabled=false); } } diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs index 515494d..7015b3e 100644 --- a/CryptoExchange.Net/Objects/Options.cs +++ b/CryptoExchange.Net/Objects/Options.cs @@ -127,7 +127,7 @@ 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); - + public bool IsRequestsTracingEnabled { get; set; } = false; /// /// ctor /// diff --git a/CryptoExchange.Net/Requests/Request.cs b/CryptoExchange.Net/Requests/Request.cs index 5c4530c..0afa03e 100644 --- a/CryptoExchange.Net/Requests/Request.cs +++ b/CryptoExchange.Net/Requests/Request.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Net.Http; using System.Net.Http.Headers; using System.Text; @@ -21,10 +22,15 @@ namespace CryptoExchange.Net.Requests /// /// /// - public Request(HttpRequestMessage request, HttpClient client) + /// if true, should assign unique id for request + public Request(HttpRequestMessage request, HttpClient client, bool isTracingEnabled=false) { httpClient = client; this.request = request; + if (isTracingEnabled) + { + RequestId = Path.GetRandomFileName(); + } } /// @@ -45,6 +51,8 @@ namespace CryptoExchange.Net.Requests /// public Uri Uri => request.RequestUri; + /// + public string? RequestId { get; } /// public void SetContent(string data, string contentType) diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs index 2434ca1..dd5d861 100644 --- a/CryptoExchange.Net/Requests/RequestFactory.cs +++ b/CryptoExchange.Net/Requests/RequestFactory.cs @@ -12,10 +12,11 @@ namespace CryptoExchange.Net.Requests public class RequestFactory : IRequestFactory { private HttpClient? httpClient; - + private bool isTracingEnabled; /// - public void Configure(TimeSpan requestTimeout, ApiProxy? proxy) + public void Configure(TimeSpan requestTimeout, ApiProxy? proxy, bool isTracingEnabled = false) { + this.isTracingEnabled = isTracingEnabled; HttpMessageHandler handler = new HttpClientHandler() { Proxy = proxy == null ? null : new WebProxy @@ -25,7 +26,7 @@ namespace CryptoExchange.Net.Requests } }; - httpClient = new HttpClient(handler) {Timeout = requestTimeout}; + httpClient = new HttpClient(handler) { Timeout = requestTimeout }; } /// @@ -34,7 +35,7 @@ namespace CryptoExchange.Net.Requests if (httpClient == null) throw new InvalidOperationException("Cant create request before configuring http client"); - return new Request(new HttpRequestMessage(method, uri), httpClient); + return new Request(new HttpRequestMessage(method, uri), httpClient, isTracingEnabled); } } } diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index d3a0f55..1072ca8 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -83,7 +83,7 @@ namespace CryptoExchange.Net throw new ArgumentNullException(nameof(exchangeOptions)); RequestTimeout = exchangeOptions.RequestTimeout; - RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy); + RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy,exchangeOptions.IsRequestsTracingEnabled); RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour; var rateLimiters = new List(); foreach (var rateLimiter in exchangeOptions.RateLimiters) @@ -190,14 +190,14 @@ namespace CryptoExchange.Net } if (limitResult.Data > 0) - log.Write(LogVerbosity.Debug, $"Request {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) 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}")}"); + log.Write(LogVerbosity.Debug, $"Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null? "": $" via proxy {apiProxy.Host}")} {(request.RequestId==null?"":$" with id {request.RequestId}")}"); return await GetResponse(request, cancellationToken).ConfigureAwait(false); } @@ -224,7 +224,7 @@ namespace CryptoExchange.Net var data = await reader.ReadToEndAsync().ConfigureAwait(false); responseStream.Close(); response.Close(); - log.Write(LogVerbosity.Debug, $"Data received: {data}"); + log.Write(LogVerbosity.Debug, $"Data {(request.RequestId==null?"":$"for request {request.RequestId} ")}received: {data}"); var parseResult = ValidateJson(data); if (!parseResult.Success) @@ -249,7 +249,7 @@ namespace CryptoExchange.Net { using var reader = new StreamReader(responseStream); var data = await reader.ReadToEndAsync().ConfigureAwait(false); - log.Write(LogVerbosity.Debug, $"Error received: {data}"); + log.Write(LogVerbosity.Debug, $"Error {(request.RequestId == null ? "" : $"for request {request.RequestId} ")}received: {data}"); responseStream.Close(); response.Close(); var parseResult = ValidateJson(data); @@ -258,7 +258,7 @@ namespace CryptoExchange.Net } catch (HttpRequestException requestException) { - log.Write(LogVerbosity.Warning, "Request exception: " + requestException.Message); + log.Write(LogVerbosity.Warning, $"Request {request.RequestId} exception: " + requestException.Message); return new WebCallResult(null, null, default, new ServerError(requestException.Message)); } catch (TaskCanceledException canceledException) @@ -266,14 +266,14 @@ namespace CryptoExchange.Net if(canceledException.CancellationToken == cancellationToken) { // Cancellation token cancelled - log.Write(LogVerbosity.Warning, "Request cancel requested"); + log.Write(LogVerbosity.Warning, $"Request {request.RequestId} cancel requested"); return new WebCallResult(null, null, default, new CancellationRequestedError()); } else { // Request timed out - log.Write(LogVerbosity.Warning, "Request timed out"); - return new WebCallResult(null, null, default, new WebError("Request timed out")); + log.Write(LogVerbosity.Warning, $"Request {request.RequestId} timed out"); + return new WebCallResult(null, null, default, new WebError($"Request {request.RequestId} timed out")); } } } From fb61b6931bd61b4968673321e66f891c02b60922 Mon Sep 17 00:00:00 2001 From: Artem Kurianov Date: Mon, 17 Aug 2020 08:44:30 +0000 Subject: [PATCH 2/5] resync --- CryptoExchange.Net/CryptoExchange.Net.xml | 6180 ++++++++--------- CryptoExchange.Net/Interfaces/IRequest.cs | 2 +- .../Interfaces/IRequestFactory.cs | 5 +- CryptoExchange.Net/Objects/Options.cs | 2 +- CryptoExchange.Net/Requests/Request.cs | 12 +- CryptoExchange.Net/Requests/RequestFactory.cs | 7 +- CryptoExchange.Net/RestClient.cs | 8 +- 7 files changed, 3089 insertions(+), 3127 deletions(-) diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index ff62654..fa58cf8 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -1,3106 +1,3074 @@ - - - - CryptoExchange.Net - - - - - Used for conversion in ArrayConverter - - - - - Marks property as optional - - - - - Api credentials info - - - - - The api key to authenticate requests - - - - - The api secret to authenticate requests - - - - - The private key to authenticate requests - - - - - Create Api credentials providing a private key for authentication - - The private key used for signing - - - - Create Api credentials providing a api key and secret for authentication - - The api key used for identification - The api secret used for signing - - - - Create Api credentials providing a api key and secret for authentication - - The api key used for identification - The api secret used for signing - - - - Copy the credentials - - - - - - Create Api credentials providing a stream containing json data. The json data should include two values: apiKey and apiSecret - - The stream containing the json data - A key to identify the credentials for the API. For example, when set to `binanceKey` the json data should contain a value for the property `binanceKey`. Defaults to 'apiKey'. - A key to identify the credentials for the API. For example, when set to `binanceSecret` the json data should contain a value for the property `binanceSecret`. Defaults to 'apiSecret'. - - - - Try get the value of a key from a JToken - - - - - - - - Dispose - - - - - Base class for authentication providers - - - - - The provided credentials - - - - - ctor - - - - - - Add authentication to the parameter list - - - - - - - - - - - - Add authentication to the header dictionary - - - - - - - - - - - - Sign a string - - - - - - - Sign a byte array - - - - - - - Convert byte array to hex - - - - - - - Private key info - - - - - The private key - - - - - The private key's pass phrase - - - - - Indicates if the private key is encrypted or not - - - - - Create a private key providing an encrypted key information - - The private key used for signing - The private key's passphrase - - - - Create a private key providing an encrypted key information - - The private key used for signing - The private key's passphrase - - - - Create a private key providing an unencrypted key information - - The private key used for signing - - - - Create a private key providing an encrypted key information - - The private key used for signing - - - - Copy the private key - - - - - - Dispose - - - - - The base for all clients - - - - - The address of the client - - - - - The log object - - - - - The api proxy - - - - - The auth provider - - - - - The last used id - - - - - Lock for id generating - - - - - Last is used - - - - - ctor - - - - - - - Set the authentication provider - - - - - - Tries to parse the json data and returns a token - - The data to parse - - - - - Deserialize a string into an object - - The type to deserialize into - The data to deserialize - Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) - A specific serializer to use - - - - - Deserialize a JToken into an object - - The type to deserialize into - The data to deserialize - Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) - A specific serializer to use - - - - - Deserialize a stream into an object - - The type to deserialize into - The stream to deserialize - A specific serializer to use - - - - - Generate a unique id - - - - - - Fill parameters in a path. Parameters are specified by '{}' and should be specified in occuring sequence - - The total path string - The values to fill - - - - - Dispose - - - - - Converter for arrays to properties - - - - - - - - - - - - - - Mark property as an index in the array - - - - - The index in the array - - - - - ctor - - - - - - Base class for enum converters - - Type of enum to convert - - - - The enum->string mapping - - - - - ctor - - - - - - - - - - - - Convert a string value - - - - - - - - - - converter for milliseconds to datetime - - - - - - - - - - - - - - Converter for nanoseconds to datetime - - - - - - - - - - - - - - Converter for seconds to datetime - - - - - - - - - - - - - - Converter for utc datetime - - - - - - - - - - - - - - Helper methods - - - - - Add a parameter - - - - - - - - Add a parameter - - - - - - - - Add an optional parameter. Not added if value is null - - - - - - - - Add an optional parameter. Not added if value is null - - - - - - - - Create a query string of the specified parameters - - The parameters to use - Whether or not the values should be url encoded - How to serialize array parameters - - - - - Get the string the secure string is representing - - The source secure string - - - - - Create a secure string from a string - - - - - - - Wait one async - - - - - - - - - Wait one async - - - - - - - - String to JToken - - - - - - - - Validates an int is one of the allowed values - - Value of the int - Name of the parameter - Allowed values - - - - Validates an int is between two values - - The value of the int - Name of the parameter - Min value - Max value - - - - Validates a string is not null or empty - - The value of the string - Name of the parameter - - - - Validates an object is not null - - The value of the object - Name of the parameter - - - - Validates a list is not null or empty - - The value of the object - Name of the parameter - - - - Rate limiter interface - - - - - Limit the request if needed - - - - - - - - - Request interface - - - - - Accept header - - - - - Content - - - - - Method - - - - - Uri - - - - - internal request id for tracing - - - - - Set byte content - - - - - - Set string content - - - - - - - Add a header to the request - - - - - - - Get the response - - - - - - - Request factory interface - - - - - Create a request for an uri - - - - - - - - Configure the requests created by this factory - - Request timeout to use - Proxy settings to use - Should generate unique id for requests - - - - Response object interface - - - - - The response status code - - - - - Whether the status code indicates a success status - - - - - The response headers - - - - - Get the response stream - - - - - - Close the response - - - - - Base class for rest API implementations - - - - - The factory for creating requests. Used for unit testing - - - - - What should happen when hitting a rate limit - - - - - List of active rate limiters - - - - - The total amount of requests made - - - - - The base address of the API - - - - - Adds a rate limiter to the client. There are 2 choices, the and the . - - The limiter to add - - - - Removes all rate limiters from this client - - - - - Ping to see if the server is reachable - - The roundtrip time of the ping request - - - - Ping to see if the server is reachable - - The roundtrip time of the ping request - - - - Base class for socket API implementations - - - - - The factory for creating sockets. Used for unit testing - - - - - The time in between reconnect attempts - - - - - Whether the client should try to auto reconnect when losing connection - - - - - The base address of the API - - - - - - - - - - - The max amount of concurrent socket connections - - - - - - - - Unsubscribe from a stream - - The subscription to unsubscribe - - - - - Unsubscribe all subscriptions - - - - - - Interface for order book - - - - - The status of the order book. Order book is up to date when the status is `Synced` - - - - - Last update identifier - - - - - The symbol of the order book - - - - - Event when the state changes - - - - - Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets - - - - - Event when the BestBid or BestAsk changes ie a Pricing Tick - - - - - Timestamp of the last update - - - - - The number of asks in the book - - - - - The number of bids in the book - - - - - The list of asks - - - - - The list of bids - - - - - The best bid currently in the order book - - - - - The best ask currently in the order book - - - - - BestBid/BesAsk returned as a pair - - - - - Start connecting and synchronizing the order book - - - - - - Start connecting and synchronizing the order book - - - - - - Stop syncing the order book - - - - - - Stop syncing the order book - - - - - - Interface for order book entries - - - - - The quantity of the entry - - - - - The price of the entry - - - - - Interface for order book entries - - - - - Sequence of the update - - - - - Interface for websocket interaction - - - - - Websocket closed - - - - - Websocket message received - - - - - Websocket error - - - - - Websocket opened - - - - - Id - - - - - Origin - - - - - Reconnecting - - - - - Handler for byte data - - - - - Handler for string data - - - - - Socket url - - - - - State - - - - - Is closed - - - - - Is open - - - - - Supported ssl protocols - - - - - Timeout - - - - - Connect the socket - - - - - - Send data - - - - - - Reset socket - - - - - Close the connecting - - - - - - Set proxy - - - - - - - Websocket factory interface - - - - - Create a websocket for an url - - - - - - - - Create a websocket for an url - - - - - - - - - - Default log writer, writes to debug - - - - - - - - - - - Log implementation - - - - - The verbosity of the logging - - - - - ctor - - - - - Set the writers - - - - - - Write a log entry - - - - - - - The log verbosity - - - - - Debug logging - - - - - Info logging - - - - - Warning logging - - - - - Error logging - - - - - None, used for disabling logging - - - - - File writer - - - - - - - - ctor - - - - - - - - - Dispose - - - - - - Proxy info - - - - - The host address of the proxy - - - - - The port of the proxy - - - - - The login of the proxy - - - - - The password of the proxy - - - - - Create new settings for a proxy - - The proxy hostname/ip - The proxy port - - - - Create new settings for a proxy - - The proxy hostname/ip - The proxy port - The proxy login - The proxy password - - - - Create new settings for a proxy - - The proxy hostname/ip - The proxy port - The proxy login - The proxy password - - - - Comparer for byte order - - - - - Compare function - - - - - - - - The result of an operation - - - - - - The data returned by the call - - - - - An error if the call didn't succeed - - - - - Whether the call was successful - - - - - ctor - - - - - - - Overwrite bool check so we can use if(callResult) instead of if(callResult.Success) - - - - - - The result of a request - - - - - - The status code of the response. Note that a OK status does not always indicate success, check the Success parameter for this. - - - - - The response headers - - - - - ctor - - - - - - - - - Create an error result - - - - - - - Create an error result - - - - - - - - - Constants - - - - - Json content type header - - - - - Form content type header - - - - - What to do when a request would exceed the rate limit - - - - - Fail the request - - - - - Wait till the request can be send - - - - - Where the post parameters should be added - - - - - Post parameters in body - - - - - Post parameters in url - - - - - The format of the request body - - - - - Form data - - - - - Json - - - - - Status of the order book - - - - - Not connected - - - - - Connecting - - - - - Reconnecting - - - - - Syncing data - - - - - Data synced, order book is up to date - - - - - Order book entry type - - - - - Ask - - - - - Bid - - - - - Define how array parameters should be send - - - - - Send multiple key=value for each entry - - - - - Create an []=value array - - - - - Base class for errors - - - - - The error code - - - - - The message for the error that occured - - - - - Optional data for the error - - - - - ctor - - - - - - - - String representation - - - - - - Cant reach server error - - - - - ctor - - - - - No api credentials provided while trying to access private endpoint - - - - - ctor - - - - - Error returned by the server - - - - - ctor - - - - - - - ctor - - - - - - - - Web error returned by the server - - - - - ctor - - - - - - Error while deserializing data - - - - - ctor - - Deserializing data - - - - Unknown error - - - - - ctor - - Error data - - - - An invalid parameter has been provided - - - - - ctor - - - - - - Rate limit exceeded - - - - - ctor - - - - - - Cancellation requested - - - - - ctor - - - - - Base options - - - - - The log verbosity - - - - - The log writers - - - - - - - - Base for order book options - - - - - The name of the order book implementation - - - - - Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped. - - - - - Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10, - when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed - - - - - - The name of the order book implementation - Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped. - Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10, - when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed - Amount of levels for this order book - - - - - - - Base client options - - - - - The base address of the client - - - - - The api credentials - - - - - Proxy to use - - - - - ctor - - - - - - - - - Base for rest client options - - - - - List of rate limiters to use - - - - - What to do when a call would exceed the rate limit - - - - - The time the server has to respond to a request before timing out - - - - - ctor - - - - - - Create a copy of the options - - - - - - - - - - Base for socket client options - - - - - Whether or not the socket should automatically reconnect when losing connection - - - - - Time to wait between reconnect attempts - - - - - The time to wait for a socket response - - - - - The time after which the connection is assumed to be dropped - - - - - The amount of subscriptions that should be made on a single socket connection. Not all exchanges support multiple subscriptions on a single socket. - Setting this to a higher number increases subscription speed, but having more subscriptions on a single connection will also increase the amount of traffic on that single connection. - - - - - ctor - - - - - - Create a copy of the options - - - - - - - - - - Buffer entry with a first and last update id - - - - - First update id - - - - - Last update id - - - - - List of asks - - - - - List of bids - - - - - Base for order book implementations - - - - - The process buffer, used while syncing - - - - - The ask list - - - - - The bid list - - - - - Order book implementation id - - - - - The log - - - - - If order book is set - - - - - The amount of levels for this book - - - - - The status of the order book. Order book is up to date when the status is `Synced` - - - - - Last update identifier - - - - - The symbol of the order book - - - - - Event when the state changes - - - - - Event when the BestBid or BestAsk changes ie a Pricing Tick - - - - - Event when order book was updated, containing the changed bids and asks. Be careful! It can generate a lot of events at high-liquidity markets - - - - - Timestamp of the last update - - - - - The number of asks in the book - - - - - The number of bids in the book - - - - - The list of asks - - - - - The list of bids - - - - - The best bid currently in the order book - - - - - The best ask currently in the order book - - - - - BestBid/BesAsk returned as a pair - - - - - ctor - - - - - - - Start connecting and synchronizing the order book - - - - - - Start connecting and synchronizing the order book - - - - - - Stop syncing the order book - - - - - - Stop syncing the order book - - - - - - Start the order book - - - - - - Reset the order book - - - - - Resync the order book - - - - - - Validate a checksum with the current order book - - - - - - - Set the initial data for the order book - - The last update sequence number - List of asks - List of bids - - - - Update the order book using a single id for an update - - - - - - - - Add a checksum to the process queue - - - - - - Update the order book using a first/last update id - - - - - - - - - Update the order book using sequenced entries - - List of bids - List of asks - - - - Check and empty the process buffer; see what entries to update the book with - - - - - Update order book with an entry - - Sequence number of the update - Type of entry - The entry - - - - Wait until the order book has been set - - Max wait time - - - - - Dispose the order book - - - - - String representation of the top 3 entries - - - - - - String representation of the top x entries - - - - - - Limits the amount of requests per time period to a certain limit, counts the request per API key. - - - - - Create a new RateLimiterAPIKey. This rate limiter limits the amount of requests per time period to a certain limit, counts the request per API key. - - The amount to limit to - The time period over which the limit counts - - - - - - - Limits the amount of requests per time period to a certain limit, counts the request per endpoint. - - - - - Create a new RateLimiterPerEndpoint. This rate limiter limits the amount of requests per time period to a certain limit, counts the request per endpoint. - - The amount to limit to - The time period over which the limit counts - - - - - - - Limits the amount of requests per time period to a certain limit, counts the total amount of requests. - - - - - Create a new RateLimiterTotal. This rate limiter limits the amount of requests per time period to a certain limit, counts the total amount of requests. - - The amount to limit to - The time period over which the limit counts - - - - - - - Rate limiting object - - - - - Lock - - - - - ctor - - - - - Get time to wait for a specific time - - - - - - - - - Add an executed request time - - - - - - Request object - - - - - Create request object for web request - - - - if true, should assign unique id for request - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - WebRequest factory - - - - - - - - - - - HttpWebResponse response object - - - - - - - - - - - - - - Create response for a http response message - - The actual response - - - - - - - - - - Base rest client - - - - - The factory for creating requests. Used for unit testing - - - - - Where to place post parameters - - - - - Request body content type - - - - - Whether or not we need to manually parse an error instead of relying on the http status code - - - - - How to serialize array parameters - - - - - What request body should be when no data is send - - - - - Timeout for requests - - - - - Rate limiting behaviour - - - - - List of rate limiters - - - - - Total requests made - - - - - ctor - - - - - - - Adds a rate limiter to the client. There are 2 choices, the and the . - - The limiter to add - - - - Removes all rate limiters from this client - - - - - Ping to see if the server is reachable - - The roundtrip time of the ping request - - - - Ping to see if the server is reachable - - The roundtrip time of the ping request - - - - Execute a request - - The expected result type - The uri to send the request to - The method of the request - Cancellation token - The parameters of the request - Whether or not the request should be authenticated - Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) - Where the post parameters should be placed - How array paramters should be serialized - - - - - Executes the request and returns the string result - - The request object to execute - Cancellation token - - - - - Can be used to parse an error even though response status indicates success. Some apis always return 200 OK, even though there is an error. - This can be used together with ManualParseError to check if it is an error before deserializing to an object - - Received data - Null if not an error, Error otherwise - - - - Creates a request object - - The uri to send the request to - The method of the request - The parameters of the request - Whether or not the request should be authenticated - Where the post parameters should be placed - How array paramters should be serialized - - - - - Writes the parameters of the request to the request object, either in the query string or the request body - - - - - - - - Parse an error response from the server. Only used when server returns a status other than Success(200) - - The string the request returned - - - - - Base for socket client implementations - - - - - The factory for creating sockets. Used for unit testing - - - - - List of socket connections currently connecting/connected - - - - - - - - - - - - - - - - - - - - - The max amount of concurrent socket connections - - - - - - - - Handler for byte data - - - - - Handler for string data - - - - - Generic handlers - - - - - Periodic task - - - - - Periodic task event - - - - - Is disposing - - - - - If true; data which is a response to a query will also be distributed to subscriptions - If false; data which is a response to a query won't get forwarded to subscriptions as well - - - - - Create a socket client - - Client options - Authentication provider - - - - Set a function to interpret the data, used when the data is received as bytes instead of a string - - Handler for byte data - Handler for string data - - - - Subscribe - - The expected return data - The request to send - The identifier to use - If the subscription should be authenticated - The handler of update data - - - - - Subscribe using a specif URL - - The type of the expected data - The URL to connect to - The request to send - The identifier to use - If the subscription should be authenticated - The handler of update data - - - - - Sends the subscribe request and waits for a response to that request - - The connection to send the request on - The request to send - The subscription the request is for - - - - - Query for data - - Expected result type - The request to send - Whether the socket should be authenticated - - - - - Query for data - - The expected result type - The url for the request - The request to send - Whether the socket should be authenticated - - - - - Sends the query request and waits for the result - - The expected result type - The connection to send and wait on - The request to send - - - - - Checks if a socket needs to be connected and does so if needed - - The connection to check - Whether the socket should authenticated - - - - - Needs to check if a received message was an answer to a query request (preferable by id) and set the callResult out to whatever the response is - - The type of response - The socket connection - The request that a response is awaited for - The message - The interpretation (null if message wasn't a response to the request) - True if the message was a response to the query - - - - Needs to check if a received message was an answer to a subscription request (preferable by id) and set the callResult out to whatever the response is - - The socket connection - - The request that a response is awaited for - The message - The interpretation (null if message wasn't a response to the request) - True if the message was a response to the subscription request - - - - Needs to check if a received message matches a handler. Typically if an update message matches the request - - The received data - The subscription request - - - - - Needs to check if a received message matches a handler. Typically if an received message matches a ping request or a other information pushed from the the server - - The received data - The string identifier of the handler - - - - - Needs to authenticate the socket so authenticated queries/subscriptions can be made on this socket connection - - - - - - - Needs to unsubscribe a subscription, typically by sending an unsubscribe request. If multiple subscriptions per socket is not allowed this can just return since the socket will be closed anyway - - The connection on which to unsubscribe - The subscription to unsubscribe - - - - - Optional handler to interpolate data before sending it to the handlers - - - - - - - Add a handler for a subscription - - The type of data the subscription expects - The request of the subscription - The identifier of the subscription (can be null if request param is used) - Whether or not this is a user subscription (counts towards the max amount of handlers on a socket) - The socket connection the handler is on - The handler of the data received - - - - - Adds a generic message handler. Used for example to reply to ping requests - - The name of the request handler. Needs to be unique - The action to execute when receiving a message for this handler (checked by ) - - - - Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one. - - The address the socket is for - Whether the socket should be authenticated - - - - - Connect a socket - - The socket to connect - - - - - Create a socket for an address - - The address the socket should connect to - - - - - Periodically sends an object to a socket - - How often - Method returning the object to send - - - - Unsubscribe from a stream - - The subscription to unsubscribe - - - - - Unsubscribe all subscriptions - - - - - - Dispose the client - - - - - Socket implementation - - - - - Socket - - - - - Log - - - - - Error handlers - - - - - Open handlers - - - - - Close handlers - - - - - Message handlers - - - - - Id - - - - - If is reconnecting - - - - - Origin - - - - - Url - - - - - Is closed - - - - - Is open - - - - - Protocols - - - - - Interpreter for bytes - - - - - Interpreter for strings - - - - - Last action time - - - - - Timeout - - - - - Socket state - - - - - ctor - - - - - - - ctor - - - - - - - - - On close - - - - - On message - - - - - On error - - - - - On open - - - - - Handle - - - - - - Handle - - - - - - - - Checks if timed out - - - - - - Close socket - - - - - - Reset socket - - - - - Send data - - - - - - Connect socket - - - - - - Set a proxy - - - - - - - Dispose - - - - - Socket connecting - - - - - Connection lost event - - - - - Connecting restored event - - - - - The connection is paused event - - - - - The connection is unpaused event - - - - - Connecting closed event - - - - - The amount of handlers - - - - - If connection is authenticated - - - - - If connection is made - - - - - The socket - - - - - If should reconnect upon closing - - - - - Time of disconnecting - - - - - If activity is paused - - - - - New socket connection - - The socket client - The socket - - - - Add handler - - - - - - Send data - - The data type - The object to send - The timeout for response - The response handler - - - - - Send data to the websocket - - The type of the object to send - The object to send - How null values should be serialized - - - - Send string data to the websocket - - The data to send - - - - Handler for a socket closing. Reconnects the socket if needed, or removes it from the active socket list if not - - - - - Close the connection - - - - - - Close the subscription - - Subscription to close - - - - - Socket subscription - - - - - Exception event - - - - - Message handlers for this subscription. Should return true if the message is handled and should not be distributed to the other handlers - - - - - Request object - - - - - Subscription identifier - - - - - Is user subscription or generic - - - - - If the subscription has been confirmed - - - - - Create SocketSubscription for a request - - - - - - - - - Create SocketSubscription for an identifier - - - - - - - - - Invoke the exception event - - - - - - Subscription - - - - - Event when the connection is lost. The socket will automatically reconnect when possible. - - - - - Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting - - - - - Event when the connection to the server is paused. No operations can be performed while paused - - - - - Event when the connection to the server is unpaused - - - - - Event when an exception happened - - - - - The id of the socket - - - - - ctor - - - - - - - Close the subscription - - - - - - Close the socket to cause a reconnect - - - - - - Factory implementation - - - - - - - - - - - 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 . - - - - + + + + CryptoExchange.Net + + + + + Used for conversion in ArrayConverter + + + + + Marks property as optional + + + + + Api credentials info + + + + + The api key to authenticate requests + + + + + The api secret to authenticate requests + + + + + The private key to authenticate requests + + + + + Create Api credentials providing a private key for authentication + + The private key used for signing + + + + Create Api credentials providing a api key and secret for authentication + + The api key used for identification + The api secret used for signing + + + + Create Api credentials providing a api key and secret for authentication + + The api key used for identification + The api secret used for signing + + + + Copy the credentials + + + + + + Create Api credentials providing a stream containing json data. The json data should include two values: apiKey and apiSecret + + The stream containing the json data + A key to identify the credentials for the API. For example, when set to `binanceKey` the json data should contain a value for the property `binanceKey`. Defaults to 'apiKey'. + A key to identify the credentials for the API. For example, when set to `binanceSecret` the json data should contain a value for the property `binanceSecret`. Defaults to 'apiSecret'. + + + + Try get the value of a key from a JToken + + + + + + + + Dispose + + + + + Base class for authentication providers + + + + + The provided credentials + + + + + ctor + + + + + + Add authentication to the parameter list + + + + + + + + + + + + Add authentication to the header dictionary + + + + + + + + + + + + Sign a string + + + + + + + Sign a byte array + + + + + + + Convert byte array to hex + + + + + + + Private key info + + + + + The private key + + + + + The private key's pass phrase + + + + + Indicates if the private key is encrypted or not + + + + + Create a private key providing an encrypted key information + + The private key used for signing + The private key's passphrase + + + + Create a private key providing an encrypted key information + + The private key used for signing + The private key's passphrase + + + + Create a private key providing an unencrypted key information + + The private key used for signing + + + + Create a private key providing an encrypted key information + + The private key used for signing + + + + Copy the private key + + + + + + Dispose + + + + + The base for all clients + + + + + The address of the client + + + + + The log object + + + + + The api proxy + + + + + The auth provider + + + + + The last used id + + + + + Lock for id generating + + + + + Last is used + + + + + ctor + + + + + + + Set the authentication provider + + + + + + Tries to parse the json data and returns a token + + The data to parse + + + + + Deserialize a string into an object + + The type to deserialize into + The data to deserialize + Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) + A specific serializer to use + + + + + Deserialize a JToken into an object + + The type to deserialize into + The data to deserialize + Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) + A specific serializer to use + + + + + Deserialize a stream into an object + + The type to deserialize into + The stream to deserialize + A specific serializer to use + + + + + Generate a unique id + + + + + + Fill parameters in a path. Parameters are specified by '{}' and should be specified in occuring sequence + + The total path string + The values to fill + + + + + Dispose + + + + + Converter for arrays to properties + + + + + + + + + + + + + + Mark property as an index in the array + + + + + The index in the array + + + + + ctor + + + + + + Base class for enum converters + + Type of enum to convert + + + + The enum->string mapping + + + + + ctor + + + + + + + + + + + + Convert a string value + + + + + + + + + + converter for milliseconds to datetime + + + + + + + + + + + + + + Converter for nanoseconds to datetime + + + + + + + + + + + + + + Converter for seconds to datetime + + + + + + + + + + + + + + Converter for utc datetime + + + + + + + + + + + + + + Helper methods + + + + + Add a parameter + + + + + + + + Add a parameter + + + + + + + + Add an optional parameter. Not added if value is null + + + + + + + + Add an optional parameter. Not added if value is null + + + + + + + + Create a query string of the specified parameters + + The parameters to use + Whether or not the values should be url encoded + How to serialize array parameters + + + + + Get the string the secure string is representing + + The source secure string + + + + + Create a secure string from a string + + + + + + + Wait one async + + + + + + + + + Wait one async + + + + + + + + String to JToken + + + + + + + + Validates an int is one of the allowed values + + Value of the int + Name of the parameter + Allowed values + + + + Validates an int is between two values + + The value of the int + Name of the parameter + Min value + Max value + + + + Validates a string is not null or empty + + The value of the string + Name of the parameter + + + + Validates an object is not null + + The value of the object + Name of the parameter + + + + Validates a list is not null or empty + + The value of the object + Name of the parameter + + + + Rate limiter interface + + + + + Limit the request if needed + + + + + + + + + Request interface + + + + + Accept header + + + + + Content + + + + + Method + + + + + Uri + + + + + internal request id for tracing + + + + + Set byte content + + + + + + Set string content + + + + + + + Add a header to the request + + + + + + + Get the response + + + + + + + Request factory interface + + + + + Create a request for an uri + + + + + + + + Configure the requests created by this factory + + Request timeout to use + Proxy settings to use + + + + Response object interface + + + + + The response status code + + + + + Whether the status code indicates a success status + + + + + The response headers + + + + + Get the response stream + + + + + + Close the response + + + + + Base class for rest API implementations + + + + + The factory for creating requests. Used for unit testing + + + + + What should happen when hitting a rate limit + + + + + List of active rate limiters + + + + + The total amount of requests made + + + + + The base address of the API + + + + + Adds a rate limiter to the client. There are 2 choices, the and the . + + The limiter to add + + + + Removes all rate limiters from this client + + + + + Ping to see if the server is reachable + + The roundtrip time of the ping request + + + + Ping to see if the server is reachable + + The roundtrip time of the ping request + + + + Base class for socket API implementations + + + + + The factory for creating sockets. Used for unit testing + + + + + The time in between reconnect attempts + + + + + Whether the client should try to auto reconnect when losing connection + + + + + The base address of the API + + + + + + + + + + + The max amount of concurrent socket connections + + + + + + + + Unsubscribe from a stream + + The subscription to unsubscribe + + + + + Unsubscribe all subscriptions + + + + + + Interface for order book + + + + + The status of the order book. Order book is up to date when the status is `Synced` + + + + + Last update identifier + + + + + The symbol of the order book + + + + + Event when the state changes + + + + + Event when order book was updated. Be careful! It can generate a lot of events at high-liquidity markets + + + + + Event when the BestBid or BestAsk changes ie a Pricing Tick + + + + + Timestamp of the last update + + + + + The number of asks in the book + + + + + The number of bids in the book + + + + + The list of asks + + + + + The list of bids + + + + + The best bid currently in the order book + + + + + The best ask currently in the order book + + + + + BestBid/BesAsk returned as a pair + + + + + Start connecting and synchronizing the order book + + + + + + Start connecting and synchronizing the order book + + + + + + Stop syncing the order book + + + + + + Stop syncing the order book + + + + + + Interface for order book entries + + + + + The quantity of the entry + + + + + The price of the entry + + + + + Interface for order book entries + + + + + Sequence of the update + + + + + Interface for websocket interaction + + + + + Websocket closed + + + + + Websocket message received + + + + + Websocket error + + + + + Websocket opened + + + + + Id + + + + + Origin + + + + + Reconnecting + + + + + Handler for byte data + + + + + Handler for string data + + + + + Socket url + + + + + State + + + + + Is closed + + + + + Is open + + + + + Supported ssl protocols + + + + + Timeout + + + + + Connect the socket + + + + + + Send data + + + + + + Reset socket + + + + + Close the connecting + + + + + + Set proxy + + + + + + + Websocket factory interface + + + + + Create a websocket for an url + + + + + + + + Create a websocket for an url + + + + + + + + + + Default log writer, writes to debug + + + + + + + + + + + Log implementation + + + + + The verbosity of the logging + + + + + ctor + + + + + Set the writers + + + + + + Write a log entry + + + + + + + The log verbosity + + + + + Debug logging + + + + + Info logging + + + + + Warning logging + + + + + Error logging + + + + + None, used for disabling logging + + + + + File writer + + + + + + + + ctor + + + + + + + + + Dispose + + + + + + Proxy info + + + + + The host address of the proxy + + + + + The port of the proxy + + + + + The login of the proxy + + + + + The password of the proxy + + + + + Create new settings for a proxy + + The proxy hostname/ip + The proxy port + + + + Create new settings for a proxy + + The proxy hostname/ip + The proxy port + The proxy login + The proxy password + + + + Create new settings for a proxy + + The proxy hostname/ip + The proxy port + The proxy login + The proxy password + + + + Comparer for byte order + + + + + Compare function + + + + + + + + The result of an operation + + + + + + The data returned by the call + + + + + An error if the call didn't succeed + + + + + Whether the call was successful + + + + + ctor + + + + + + + Overwrite bool check so we can use if(callResult) instead of if(callResult.Success) + + + + + + The result of a request + + + + + + The status code of the response. Note that a OK status does not always indicate success, check the Success parameter for this. + + + + + The response headers + + + + + ctor + + + + + + + + + Create an error result + + + + + + + Create an error result + + + + + + + + + Constants + + + + + Json content type header + + + + + Form content type header + + + + + What to do when a request would exceed the rate limit + + + + + Fail the request + + + + + Wait till the request can be send + + + + + Where the post parameters should be added + + + + + Post parameters in body + + + + + Post parameters in url + + + + + The format of the request body + + + + + Form data + + + + + Json + + + + + Status of the order book + + + + + Not connected + + + + + Connecting + + + + + Reconnecting + + + + + Syncing data + + + + + Data synced, order book is up to date + + + + + Order book entry type + + + + + Ask + + + + + Bid + + + + + Define how array parameters should be send + + + + + Send multiple key=value for each entry + + + + + Create an []=value array + + + + + Base class for errors + + + + + The error code + + + + + The message for the error that occured + + + + + Optional data for the error + + + + + ctor + + + + + + + + String representation + + + + + + Cant reach server error + + + + + ctor + + + + + No api credentials provided while trying to access private endpoint + + + + + ctor + + + + + Error returned by the server + + + + + ctor + + + + + + + ctor + + + + + + + + Web error returned by the server + + + + + ctor + + + + + + Error while deserializing data + + + + + ctor + + Deserializing data + + + + Unknown error + + + + + ctor + + Error data + + + + An invalid parameter has been provided + + + + + ctor + + + + + + Rate limit exceeded + + + + + ctor + + + + + + Cancellation requested + + + + + ctor + + + + + Base options + + + + + The log verbosity + + + + + The log writers + + + + + + + + Base for order book options + + + + + The name of the order book implementation + + + + + Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped. + + + + + Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10, + when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed + + + + + + The name of the order book implementation + Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped. + Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10, + when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed + Amount of levels for this order book + + + + + + + Base client options + + + + + The base address of the client + + + + + The api credentials + + + + + Proxy to use + + + + + ctor + + + + + + + + + Base for rest client options + + + + + List of rate limiters to use + + + + + What to do when a call would exceed the rate limit + + + + + The time the server has to respond to a request before timing out + + + + + ctor + + + + + + Create a copy of the options + + + + + + + + + + Base for socket client options + + + + + Whether or not the socket should automatically reconnect when losing connection + + + + + Time to wait between reconnect attempts + + + + + The time to wait for a socket response + + + + + The time after which the connection is assumed to be dropped + + + + + The amount of subscriptions that should be made on a single socket connection. Not all exchanges support multiple subscriptions on a single socket. + Setting this to a higher number increases subscription speed, but having more subscriptions on a single connection will also increase the amount of traffic on that single connection. + + + + + ctor + + + + + + Create a copy of the options + + + + + + + + + + Buffer entry with a first and last update id + + + + + First update id + + + + + Last update id + + + + + List of asks + + + + + List of bids + + + + + Base for order book implementations + + + + + The process buffer, used while syncing + + + + + The ask list + + + + + The bid list + + + + + Order book implementation id + + + + + The log + + + + + If order book is set + + + + + The amount of levels for this book + + + + + The status of the order book. Order book is up to date when the status is `Synced` + + + + + Last update identifier + + + + + The symbol of the order book + + + + + Event when the state changes + + + + + Event when the BestBid or BestAsk changes ie a Pricing Tick + + + + + Event when order book was updated, containing the changed bids and asks. Be careful! It can generate a lot of events at high-liquidity markets + + + + + Timestamp of the last update + + + + + The number of asks in the book + + + + + The number of bids in the book + + + + + The list of asks + + + + + The list of bids + + + + + The best bid currently in the order book + + + + + The best ask currently in the order book + + + + + BestBid/BesAsk returned as a pair + + + + + ctor + + + + + + + Start connecting and synchronizing the order book + + + + + + Start connecting and synchronizing the order book + + + + + + Stop syncing the order book + + + + + + Stop syncing the order book + + + + + + Start the order book + + + + + + Reset the order book + + + + + Resync the order book + + + + + + Validate a checksum with the current order book + + + + + + + Set the initial data for the order book + + The last update sequence number + List of asks + List of bids + + + + Update the order book using a single id for an update + + + + + + + + Add a checksum to the process queue + + + + + + Update the order book using a first/last update id + + + + + + + + + Update the order book using sequenced entries + + List of bids + List of asks + + + + Check and empty the process buffer; see what entries to update the book with + + + + + Update order book with an entry + + Sequence number of the update + Type of entry + The entry + + + + Wait until the order book has been set + + Max wait time + + + + + Dispose the order book + + + + + String representation of the top 3 entries + + + + + + String representation of the top x entries + + + + + + Limits the amount of requests per time period to a certain limit, counts the request per API key. + + + + + Create a new RateLimiterAPIKey. This rate limiter limits the amount of requests per time period to a certain limit, counts the request per API key. + + The amount to limit to + The time period over which the limit counts + + + + + + + Limits the amount of requests per time period to a certain limit, counts the request per endpoint. + + + + + Create a new RateLimiterPerEndpoint. This rate limiter limits the amount of requests per time period to a certain limit, counts the request per endpoint. + + The amount to limit to + The time period over which the limit counts + + + + + + + Limits the amount of requests per time period to a certain limit, counts the total amount of requests. + + + + + Create a new RateLimiterTotal. This rate limiter limits the amount of requests per time period to a certain limit, counts the total amount of requests. + + The amount to limit to + The time period over which the limit counts + + + + + + + Rate limiting object + + + + + Lock + + + + + ctor + + + + + Get time to wait for a specific time + + + + + + + + + Add an executed request time + + + + + + Request object + + + + + Create request object for web request + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WebRequest factory + + + + + + + + + + + HttpWebResponse response object + + + + + + + + + + + + + + Create response for a http response message + + The actual response + + + + + + + + + + Base rest client + + + + + The factory for creating requests. Used for unit testing + + + + + Where to place post parameters + + + + + Request body content type + + + + + Whether or not we need to manually parse an error instead of relying on the http status code + + + + + How to serialize array parameters + + + + + What request body should be when no data is send + + + + + Timeout for requests + + + + + Rate limiting behaviour + + + + + List of rate limiters + + + + + Total requests made + + + + + ctor + + + + + + + Adds a rate limiter to the client. There are 2 choices, the and the . + + The limiter to add + + + + Removes all rate limiters from this client + + + + + Ping to see if the server is reachable + + The roundtrip time of the ping request + + + + Ping to see if the server is reachable + + The roundtrip time of the ping request + + + + Execute a request + + The expected result type + The uri to send the request to + The method of the request + Cancellation token + The parameters of the request + Whether or not the request should be authenticated + Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) + Where the post parameters should be placed + How array paramters should be serialized + + + + + Executes the request and returns the string result + + The request object to execute + Cancellation token + + + + + Can be used to parse an error even though response status indicates success. Some apis always return 200 OK, even though there is an error. + This can be used together with ManualParseError to check if it is an error before deserializing to an object + + Received data + Null if not an error, Error otherwise + + + + Creates a request object + + The uri to send the request to + The method of the request + The parameters of the request + Whether or not the request should be authenticated + Where the post parameters should be placed + How array paramters should be serialized + + + + + Writes the parameters of the request to the request object, either in the query string or the request body + + + + + + + + Parse an error response from the server. Only used when server returns a status other than Success(200) + + The string the request returned + + + + + Base for socket client implementations + + + + + The factory for creating sockets. Used for unit testing + + + + + List of socket connections currently connecting/connected + + + + + + + + + + + + + + + + + + + + + The max amount of concurrent socket connections + + + + + + + + Handler for byte data + + + + + Handler for string data + + + + + Generic handlers + + + + + Periodic task + + + + + Periodic task event + + + + + Is disposing + + + + + If true; data which is a response to a query will also be distributed to subscriptions + If false; data which is a response to a query won't get forwarded to subscriptions as well + + + + + Create a socket client + + Client options + Authentication provider + + + + Set a function to interpret the data, used when the data is received as bytes instead of a string + + Handler for byte data + Handler for string data + + + + Subscribe + + The expected return data + The request to send + The identifier to use + If the subscription should be authenticated + The handler of update data + + + + + Subscribe using a specif URL + + The type of the expected data + The URL to connect to + The request to send + The identifier to use + If the subscription should be authenticated + The handler of update data + + + + + Sends the subscribe request and waits for a response to that request + + The connection to send the request on + The request to send + The subscription the request is for + + + + + Query for data + + Expected result type + The request to send + Whether the socket should be authenticated + + + + + Query for data + + The expected result type + The url for the request + The request to send + Whether the socket should be authenticated + + + + + Sends the query request and waits for the result + + The expected result type + The connection to send and wait on + The request to send + + + + + Checks if a socket needs to be connected and does so if needed + + The connection to check + Whether the socket should authenticated + + + + + Needs to check if a received message was an answer to a query request (preferable by id) and set the callResult out to whatever the response is + + The type of response + The socket connection + The request that a response is awaited for + The message + The interpretation (null if message wasn't a response to the request) + True if the message was a response to the query + + + + Needs to check if a received message was an answer to a subscription request (preferable by id) and set the callResult out to whatever the response is + + The socket connection + + The request that a response is awaited for + The message + The interpretation (null if message wasn't a response to the request) + True if the message was a response to the subscription request + + + + Needs to check if a received message matches a handler. Typically if an update message matches the request + + The received data + The subscription request + + + + + Needs to check if a received message matches a handler. Typically if an received message matches a ping request or a other information pushed from the the server + + The received data + The string identifier of the handler + + + + + Needs to authenticate the socket so authenticated queries/subscriptions can be made on this socket connection + + + + + + + Needs to unsubscribe a subscription, typically by sending an unsubscribe request. If multiple subscriptions per socket is not allowed this can just return since the socket will be closed anyway + + The connection on which to unsubscribe + The subscription to unsubscribe + + + + + Optional handler to interpolate data before sending it to the handlers + + + + + + + Add a handler for a subscription + + The type of data the subscription expects + The request of the subscription + The identifier of the subscription (can be null if request param is used) + Whether or not this is a user subscription (counts towards the max amount of handlers on a socket) + The socket connection the handler is on + The handler of the data received + + + + + Adds a generic message handler. Used for example to reply to ping requests + + The name of the request handler. Needs to be unique + The action to execute when receiving a message for this handler (checked by ) + + + + Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one. + + The address the socket is for + Whether the socket should be authenticated + + + + + Connect a socket + + The socket to connect + + + + + Create a socket for an address + + The address the socket should connect to + + + + + Periodically sends an object to a socket + + How often + Method returning the object to send + + + + Unsubscribe from a stream + + The subscription to unsubscribe + + + + + Unsubscribe all subscriptions + + + + + + Dispose the client + + + + + Socket implementation + + + + + Socket + + + + + Log + + + + + Error handlers + + + + + Open handlers + + + + + Close handlers + + + + + Message handlers + + + + + Id + + + + + If is reconnecting + + + + + Origin + + + + + Url + + + + + Is closed + + + + + Is open + + + + + Protocols + + + + + Interpreter for bytes + + + + + Interpreter for strings + + + + + Last action time + + + + + Timeout + + + + + Socket state + + + + + ctor + + + + + + + ctor + + + + + + + + + On close + + + + + On message + + + + + On error + + + + + On open + + + + + Handle + + + + + + Handle + + + + + + + + Checks if timed out + + + + + + Close socket + + + + + + Reset socket + + + + + Send data + + + + + + Connect socket + + + + + + Set a proxy + + + + + + + Dispose + + + + + Socket connecting + + + + + Connection lost event + + + + + Connecting restored event + + + + + The connection is paused event + + + + + The connection is unpaused event + + + + + Connecting closed event + + + + + The amount of handlers + + + + + If connection is authenticated + + + + + If connection is made + + + + + The socket + + + + + If should reconnect upon closing + + + + + Time of disconnecting + + + + + If activity is paused + + + + + New socket connection + + The socket client + The socket + + + + Add handler + + + + + + Send data + + The data type + The object to send + The timeout for response + The response handler + + + + + Send data to the websocket + + The type of the object to send + The object to send + How null values should be serialized + + + + Send string data to the websocket + + The data to send + + + + Handler for a socket closing. Reconnects the socket if needed, or removes it from the active socket list if not + + + + + Close the connection + + + + + + Close the subscription + + Subscription to close + + + + + Socket subscription + + + + + Exception event + + + + + Message handlers for this subscription. Should return true if the message is handled and should not be distributed to the other handlers + + + + + Request object + + + + + Subscription identifier + + + + + Is user subscription or generic + + + + + If the subscription has been confirmed + + + + + Create SocketSubscription for a request + + + + + + + + + Create SocketSubscription for an identifier + + + + + + + + + Invoke the exception event + + + + + + Subscription + + + + + Event when the connection is lost. The socket will automatically reconnect when possible. + + + + + Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting + + + + + Event when the connection to the server is paused. No operations can be performed while paused + + + + + Event when the connection to the server is unpaused + + + + + Event when an exception happened + + + + + The id of the socket + + + + + ctor + + + + + + + Close the subscription + + + + + + Close the socket to cause a reconnect + + + + + + Factory implementation + + + + + + + + + + +ember name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute"> + + 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/IRequest.cs b/CryptoExchange.Net/Interfaces/IRequest.cs index 9d02651..64a714e 100644 --- a/CryptoExchange.Net/Interfaces/IRequest.cs +++ b/CryptoExchange.Net/Interfaces/IRequest.cs @@ -29,7 +29,7 @@ namespace CryptoExchange.Net.Interfaces /// /// internal request id for tracing /// - string? RequestId { get; } + string RequestId { get; } /// /// Set byte content /// diff --git a/CryptoExchange.Net/Interfaces/IRequestFactory.cs b/CryptoExchange.Net/Interfaces/IRequestFactory.cs index d519fe9..59b1bb8 100644 --- a/CryptoExchange.Net/Interfaces/IRequestFactory.cs +++ b/CryptoExchange.Net/Interfaces/IRequestFactory.cs @@ -21,8 +21,7 @@ namespace CryptoExchange.Net.Interfaces /// Configure the requests created by this factory /// /// Request timeout to use - /// Proxy settings to use - /// Should generate unique id for requests - void Configure(TimeSpan requestTimeout, ApiProxy? proxy, bool isTracingEnabled=false); + /// Proxy settings to use + void Configure(TimeSpan requestTimeout, ApiProxy? proxy); } } diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs index 7015b3e..8249f5d 100644 --- a/CryptoExchange.Net/Objects/Options.cs +++ b/CryptoExchange.Net/Objects/Options.cs @@ -127,7 +127,7 @@ 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); - public bool IsRequestsTracingEnabled { get; set; } = false; + /// /// ctor /// diff --git a/CryptoExchange.Net/Requests/Request.cs b/CryptoExchange.Net/Requests/Request.cs index 0afa03e..6fcecc3 100644 --- a/CryptoExchange.Net/Requests/Request.cs +++ b/CryptoExchange.Net/Requests/Request.cs @@ -21,16 +21,12 @@ namespace CryptoExchange.Net.Requests /// Create request object for web request /// /// - /// - /// if true, should assign unique id for request - public Request(HttpRequestMessage request, HttpClient client, bool isTracingEnabled=false) + /// + public Request(HttpRequestMessage request, HttpClient client) { httpClient = client; this.request = request; - if (isTracingEnabled) - { - RequestId = Path.GetRandomFileName(); - } + RequestId = Path.GetRandomFileName(); } /// @@ -52,7 +48,7 @@ namespace CryptoExchange.Net.Requests /// public Uri Uri => request.RequestUri; /// - public string? RequestId { get; } + public string RequestId { get; } /// public void SetContent(string data, string contentType) diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs index dd5d861..54cd8a2 100644 --- a/CryptoExchange.Net/Requests/RequestFactory.cs +++ b/CryptoExchange.Net/Requests/RequestFactory.cs @@ -14,9 +14,8 @@ namespace CryptoExchange.Net.Requests private HttpClient? httpClient; private bool isTracingEnabled; /// - public void Configure(TimeSpan requestTimeout, ApiProxy? proxy, bool isTracingEnabled = false) - { - this.isTracingEnabled = isTracingEnabled; + public void Configure(TimeSpan requestTimeout, ApiProxy? proxy) + { HttpMessageHandler handler = new HttpClientHandler() { Proxy = proxy == null ? null : new WebProxy @@ -35,7 +34,7 @@ namespace CryptoExchange.Net.Requests if (httpClient == null) throw new InvalidOperationException("Cant create request before configuring http client"); - return new Request(new HttpRequestMessage(method, uri), httpClient, isTracingEnabled); + return new Request(new HttpRequestMessage(method, uri), httpClient); } } } diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index 1072ca8..e9932d2 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -83,7 +83,7 @@ namespace CryptoExchange.Net throw new ArgumentNullException(nameof(exchangeOptions)); RequestTimeout = exchangeOptions.RequestTimeout; - RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy,exchangeOptions.IsRequestsTracingEnabled); + RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy); RateLimitBehaviour = exchangeOptions.RateLimitingBehaviour; var rateLimiters = new List(); foreach (var rateLimiter in exchangeOptions.RateLimiters) @@ -197,7 +197,7 @@ namespace CryptoExchange.Net 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}")} {(request.RequestId==null?"":$" 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); } @@ -224,7 +224,7 @@ namespace CryptoExchange.Net var data = await reader.ReadToEndAsync().ConfigureAwait(false); responseStream.Close(); response.Close(); - log.Write(LogVerbosity.Debug, $"Data {(request.RequestId==null?"":$"for request {request.RequestId} ")}received: {data}"); + log.Write(LogVerbosity.Debug, $"Data for request {request.RequestId} received: {data}"); var parseResult = ValidateJson(data); if (!parseResult.Success) @@ -249,7 +249,7 @@ namespace CryptoExchange.Net { using var reader = new StreamReader(responseStream); var data = await reader.ReadToEndAsync().ConfigureAwait(false); - log.Write(LogVerbosity.Debug, $"Error {(request.RequestId == null ? "" : $"for request {request.RequestId} ")}received: {data}"); + log.Write(LogVerbosity.Debug, $"Error for request {request.RequestId} received: {data}"); responseStream.Close(); response.Close(); var parseResult = ValidateJson(data); From 387ef0d1a65795dad88e5e60830377b0a64e7d78 Mon Sep 17 00:00:00 2001 From: Artem Kurianov Date: Tue, 18 Aug 2020 09:39:14 +0000 Subject: [PATCH 3/5] added ability to set optional http client for preventing connection pool exhaustion https://stackoverflow.com/a/48778707/4993930 --- CryptoExchange.Net/CryptoExchange.Net.xml | 168 +++++++++++++++++- .../Interfaces/IRequestFactory.cs | 3 +- CryptoExchange.Net/Objects/Options.cs | 19 +- CryptoExchange.Net/Requests/RequestFactory.cs | 35 ++-- CryptoExchange.Net/RestClient.cs | 44 ++--- 5 files changed, 222 insertions(+), 47 deletions(-) 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) From 1a73ecf89a5fecb657cdcf506deb16b706241cc6 Mon Sep 17 00:00:00 2001 From: Artem Kurianov Date: Thu, 20 Aug 2020 16:47:34 +0000 Subject: [PATCH 4/5] setted checking object parameter at Deserialize to optional for backward compatibility, and added the same config field for preventing checking objects at Stream deserializing. added checking type for not null at CheckObject --- CryptoExchange.Net/BaseClient.cs | 14 +++++++++++--- CryptoExchange.Net/Objects/Options.cs | 6 ++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CryptoExchange.Net/BaseClient.cs b/CryptoExchange.Net/BaseClient.cs index b01e1a1..8e73049 100644 --- a/CryptoExchange.Net/BaseClient.cs +++ b/CryptoExchange.Net/BaseClient.cs @@ -36,6 +36,10 @@ namespace CryptoExchange.Net /// The auth provider /// protected internal AuthenticationProvider? authProvider; + /// + /// Should check received objects + /// + public bool ShouldCheckObjects { get; set; } /// /// The last used id @@ -73,6 +77,7 @@ namespace CryptoExchange.Net apiProxy = options.Proxy; log.Write(LogVerbosity.Debug, $"Client configuration: {options}"); + ShouldCheckObjects = options.ShouldCheckObjects; } /// @@ -128,7 +133,7 @@ namespace CryptoExchange.Net /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) /// A specific serializer to use /// - protected CallResult Deserialize(string data, bool checkObject = true, JsonSerializer? serializer = null) + protected CallResult Deserialize(string data, bool? checkObject = null, JsonSerializer? serializer = null) { var tokenResult = ValidateJson(data); if (!tokenResult) @@ -148,14 +153,14 @@ namespace CryptoExchange.Net /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) /// A specific serializer to use /// - protected CallResult Deserialize(JToken obj, bool checkObject = true, JsonSerializer? serializer = null) + protected CallResult Deserialize(JToken obj, bool? checkObject = null, JsonSerializer? serializer = null) { if (serializer == null) serializer = defaultSerializer; try { - if (checkObject && log.Level == LogVerbosity.Debug) + if ((checkObject ?? ShouldCheckObjects)&& log.Level == LogVerbosity.Debug) { try { @@ -256,6 +261,9 @@ namespace CryptoExchange.Net private void CheckObject(Type type, JObject obj) { + if (type == null) + return; + if (type.GetCustomAttribute(true) != null) // If type has a custom JsonConverter we assume this will handle property mapping return; diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs index 577de1e..80354a7 100644 --- a/CryptoExchange.Net/Objects/Options.cs +++ b/CryptoExchange.Net/Objects/Options.cs @@ -84,8 +84,10 @@ namespace CryptoExchange.Net.Objects /// The api credentials /// public ApiCredentials? ApiCredentials { get; set; } - - + /// + /// ShoouldCheckObjects + /// + public bool ShouldCheckObjects { get; set; } = true; /// /// Proxy to use /// From 664cbee8dddd3d14a91fda3f6458115f72c57022 Mon Sep 17 00:00:00 2001 From: JKorf Date: Mon, 24 Aug 2020 12:01:49 +0200 Subject: [PATCH 5/5] Changed requestId to int, added it to more logging related to requests. Added some comments --- CryptoExchange.Net/BaseClient.cs | 48 ++--- CryptoExchange.Net/CryptoExchange.Net.xml | 186 +++--------------- CryptoExchange.Net/Interfaces/IRequest.cs | 2 +- .../Interfaces/IRequestFactory.cs | 3 +- CryptoExchange.Net/Objects/Options.cs | 16 +- CryptoExchange.Net/Requests/Request.cs | 7 +- CryptoExchange.Net/Requests/RequestFactory.cs | 4 +- CryptoExchange.Net/RestClient.cs | 38 ++-- 8 files changed, 93 insertions(+), 211 deletions(-) diff --git a/CryptoExchange.Net/BaseClient.cs b/CryptoExchange.Net/BaseClient.cs index 8e73049..8088790 100644 --- a/CryptoExchange.Net/BaseClient.cs +++ b/CryptoExchange.Net/BaseClient.cs @@ -37,7 +37,7 @@ namespace CryptoExchange.Net /// protected internal AuthenticationProvider? authProvider; /// - /// Should check received objects + /// Should check objects for missing properties based on the model and the received JSON /// public bool ShouldCheckObjects { get; set; } @@ -132,8 +132,9 @@ namespace CryptoExchange.Net /// The data to deserialize /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) /// A specific serializer to use + /// Id of the request /// - protected CallResult Deserialize(string data, bool? checkObject = null, JsonSerializer? serializer = null) + protected CallResult Deserialize(string data, bool? checkObject = null, JsonSerializer? serializer = null, int? requestId = null) { var tokenResult = ValidateJson(data); if (!tokenResult) @@ -142,7 +143,7 @@ namespace CryptoExchange.Net return new CallResult(default, tokenResult.Error); } - return Deserialize(tokenResult.Data, checkObject, serializer); + return Deserialize(tokenResult.Data, checkObject, serializer, requestId); } /// @@ -153,7 +154,7 @@ namespace CryptoExchange.Net /// Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) /// A specific serializer to use /// - protected CallResult Deserialize(JToken obj, bool? checkObject = null, JsonSerializer? serializer = null) + protected CallResult Deserialize(JToken obj, bool? checkObject = null, JsonSerializer? serializer = null, int? requestId = null) { if (serializer == null) serializer = defaultSerializer; @@ -166,17 +167,17 @@ namespace CryptoExchange.Net { if (obj is JObject o) { - CheckObject(typeof(T), o); + CheckObject(typeof(T), o, requestId); } else if (obj is JArray j) { if (j.HasValues && j[0] is JObject jObject) - CheckObject(typeof(T).GetElementType(), jObject); + CheckObject(typeof(T).GetElementType(), jObject, requestId); } } catch (Exception e) { - log.Write(LogVerbosity.Debug, "Failed to check response data: " + e.Message); + log.Write(LogVerbosity.Debug, $"{(requestId != null ? $"[{ requestId}] " : "")}Failed to check response data: " + e.Message); } } @@ -184,19 +185,19 @@ namespace CryptoExchange.Net } catch (JsonReaderException jre) { - var info = $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {obj}"; + var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {obj}"; log.Write(LogVerbosity.Error, info); return new CallResult(default, new DeserializeError(info)); } catch (JsonSerializationException jse) { - var info = $"Deserialize JsonSerializationException: {jse.Message}. Received data: {obj}"; + var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}. Received data: {obj}"; log.Write(LogVerbosity.Error, info); return new CallResult(default, new DeserializeError(info)); } catch (Exception ex) { - var info = $"Deserialize Unknown Exception: {ex.Message}. Received data: {obj}"; + var info = $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {ex.Message}. Received data: {obj}"; log.Write(LogVerbosity.Error, info); return new CallResult(default, new DeserializeError(info)); } @@ -208,8 +209,9 @@ namespace CryptoExchange.Net /// The type to deserialize into /// The stream to deserialize /// A specific serializer to use + /// Id of the request /// - protected async Task> Deserialize(Stream stream, JsonSerializer? serializer = null) + protected async Task> Deserialize(Stream stream, JsonSerializer? serializer = null, int? requestId = null) { if (serializer == null) serializer = defaultSerializer; @@ -220,8 +222,8 @@ namespace CryptoExchange.Net if (log.Level == LogVerbosity.Debug) { var data = await reader.ReadToEndAsync().ConfigureAwait(false); - log.Write(LogVerbosity.Debug, $"Data received: {data}"); - return Deserialize(data); + log.Write(LogVerbosity.Debug, $"{(requestId != null ? $"[{requestId}] ": "")}Data received: {data}"); + return Deserialize(data, null, serializer, requestId); } using var jsonReader = new JsonTextReader(reader); @@ -232,7 +234,7 @@ namespace CryptoExchange.Net if(stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); var data = await ReadStream(stream).ConfigureAwait(false); - log.Write(LogVerbosity.Error, $"Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}"); + log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonReaderException: {jre.Message}, Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}, data: {data}"); return new CallResult(default, new DeserializeError(data)); } catch (JsonSerializationException jse) @@ -240,7 +242,7 @@ namespace CryptoExchange.Net if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); var data = await ReadStream(stream).ConfigureAwait(false); - log.Write(LogVerbosity.Error, $"Deserialize JsonSerializationException: {jse.Message}, data: {data}"); + log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize JsonSerializationException: {jse.Message}, data: {data}"); return new CallResult(default, new DeserializeError(data)); } catch (Exception ex) @@ -248,7 +250,7 @@ namespace CryptoExchange.Net if (stream.CanSeek) stream.Seek(0, SeekOrigin.Begin); var data = await ReadStream(stream).ConfigureAwait(false); - log.Write(LogVerbosity.Error, $"Deserialize Unknown Exception: {ex.Message}, data: {data}"); + log.Write(LogVerbosity.Error, $"{(requestId != null ? $"[{requestId}] " : "")}Deserialize Unknown Exception: {ex.Message}, data: {data}"); return new CallResult(default, new DeserializeError(data)); } } @@ -259,7 +261,7 @@ namespace CryptoExchange.Net return await reader.ReadToEndAsync().ConfigureAwait(false); } - private void CheckObject(Type type, JObject obj) + private void CheckObject(Type type, JObject obj, int? requestId = null) { if (type == null) return; @@ -273,7 +275,7 @@ namespace CryptoExchange.Net if (!obj.HasValues && type != typeof(object)) { - log.Write(LogVerbosity.Warning, $"Expected `{type.Name}`, but received object was empty"); + log.Write(LogVerbosity.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Expected `{type.Name}`, but received object was empty"); return; } @@ -299,7 +301,7 @@ namespace CryptoExchange.Net { if (!(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))) { - log.Write(LogVerbosity.Warning, $"Local object doesn't have property `{token.Key}` expected in type `{type.Name}`"); + log.Write(LogVerbosity.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Local object doesn't have property `{token.Key}` expected in type `{type.Name}`"); isDif = true; } continue; @@ -314,9 +316,9 @@ namespace CryptoExchange.Net if (!IsSimple(propType) && propType != typeof(DateTime)) { if (propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject) - CheckObject(propType.GetElementType(), (JObject)token.Value[0]); + CheckObject(propType.GetElementType(), (JObject)token.Value[0], requestId); else if (token.Value is JObject o) - CheckObject(propType, o); + CheckObject(propType, o, requestId); } } @@ -329,11 +331,11 @@ namespace CryptoExchange.Net continue; isDif = true; - log.Write(LogVerbosity.Warning, $"Local object has property `{prop}` but was not found in received object of type `{type.Name}`"); + log.Write(LogVerbosity.Warning, $"{(requestId != null ? $"[{requestId}] " : "")}Local object has property `{prop}` but was not found in received object of type `{type.Name}`"); } if (isDif) - log.Write(LogVerbosity.Debug, "Returned data: " + obj); + log.Write(LogVerbosity.Debug, $"{(requestId != null ? $"[{ requestId}] " : "")}Returned data: " + obj); } private static PropertyInfo? GetProperty(string name, IEnumerable props) diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index 0f06bb5..a171b30 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -224,6 +224,11 @@ The auth provider + + + Should check objects for missing properties based on the model and the received JSON + + The last used id @@ -259,7 +264,7 @@ The data to parse - + Deserialize a string into an object @@ -267,9 +272,10 @@ The data to deserialize Whether or not the parsing should be checked for missing properties (will output data to the logging if log verbosity is Debug) A specific serializer to use + Id of the request - + Deserialize a JToken into an object @@ -279,13 +285,14 @@ A specific serializer to use - + Deserialize a stream into an object The type to deserialize into The stream to deserialize A specific serializer to use + Id of the request @@ -625,12 +632,13 @@ Request factory interface - + Create a request for an uri + @@ -1588,6 +1596,11 @@ The api credentials + + + Should check objects for missing properties based on the model and the received JSON + + Proxy to use @@ -1622,23 +1635,23 @@ The time the server has to respond to a request before timing out - + - http client + Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options will be ignored and should be set on the provided HttpClient instance ctor - + The base address of the API ctor - - Shared http client + The base address of the API + Shared http client instance @@ -2042,12 +2055,13 @@ Request object - + Create request object for web request + @@ -2084,7 +2098,7 @@ - + @@ -2210,7 +2224,7 @@ Whether or not the request should be authenticated Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) Where the post parameters should be placed - How array paramters should be serialized + How array parameters should be serialized @@ -2229,7 +2243,7 @@ Received data Null if not an error, Error otherwise - + Creates a request object @@ -2238,7 +2252,8 @@ The parameters of the request Whether or not the request should be authenticated Where the post parameters should be placed - How array paramters should be serialized + How array parameters should be serialized + Unique id of a request @@ -2969,148 +2984,5 @@ - - - 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/IRequest.cs b/CryptoExchange.Net/Interfaces/IRequest.cs index 64a714e..1544436 100644 --- a/CryptoExchange.Net/Interfaces/IRequest.cs +++ b/CryptoExchange.Net/Interfaces/IRequest.cs @@ -29,7 +29,7 @@ namespace CryptoExchange.Net.Interfaces /// /// internal request id for tracing /// - string RequestId { get; } + int RequestId { get; } /// /// Set byte content /// diff --git a/CryptoExchange.Net/Interfaces/IRequestFactory.cs b/CryptoExchange.Net/Interfaces/IRequestFactory.cs index e05b371..2779d92 100644 --- a/CryptoExchange.Net/Interfaces/IRequestFactory.cs +++ b/CryptoExchange.Net/Interfaces/IRequestFactory.cs @@ -14,8 +14,9 @@ namespace CryptoExchange.Net.Interfaces /// /// /// + /// /// - IRequest Create(HttpMethod method, string uri); + IRequest Create(HttpMethod method, string uri, int requestId); /// /// Configure the requests created by this factory diff --git a/CryptoExchange.Net/Objects/Options.cs b/CryptoExchange.Net/Objects/Options.cs index 502bcce..cc3cd9b 100644 --- a/CryptoExchange.Net/Objects/Options.cs +++ b/CryptoExchange.Net/Objects/Options.cs @@ -85,10 +85,12 @@ namespace CryptoExchange.Net.Objects /// The api credentials /// public ApiCredentials? ApiCredentials { get; set; } + /// - /// ShoouldCheckObjects + /// Should check objects for missing properties based on the model and the received JSON /// public bool ShouldCheckObjects { get; set; } = true; + /// /// Proxy to use /// @@ -129,22 +131,24 @@ 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 + /// Http client to use. If a HttpClient is provided in this property the RequestTimeout and Proxy options will be ignored and should be set on the provided HttpClient instance /// - public HttpClient? HttpClient; + public HttpClient? HttpClient { get; set; } + /// /// ctor /// - /// + /// The base address of the API public RestClientOptions(string baseAddress): base(baseAddress) { } /// /// ctor /// - /// - /// Shared http client + /// The base address of the API + /// Shared http client instance public RestClientOptions(HttpClient httpClient, string baseAddress) : base(baseAddress) { HttpClient = httpClient; diff --git a/CryptoExchange.Net/Requests/Request.cs b/CryptoExchange.Net/Requests/Request.cs index 6fcecc3..b0b669a 100644 --- a/CryptoExchange.Net/Requests/Request.cs +++ b/CryptoExchange.Net/Requests/Request.cs @@ -22,11 +22,12 @@ namespace CryptoExchange.Net.Requests /// /// /// - public Request(HttpRequestMessage request, HttpClient client) + /// + public Request(HttpRequestMessage request, HttpClient client, int requestId) { httpClient = client; this.request = request; - RequestId = Path.GetRandomFileName(); + RequestId = requestId; } /// @@ -48,7 +49,7 @@ namespace CryptoExchange.Net.Requests /// public Uri Uri => request.RequestUri; /// - public string RequestId { get; } + public int RequestId { get; } /// public void SetContent(string data, string contentType) diff --git a/CryptoExchange.Net/Requests/RequestFactory.cs b/CryptoExchange.Net/Requests/RequestFactory.cs index 3502c61..be68097 100644 --- a/CryptoExchange.Net/Requests/RequestFactory.cs +++ b/CryptoExchange.Net/Requests/RequestFactory.cs @@ -36,12 +36,12 @@ namespace CryptoExchange.Net.Requests } /// - public IRequest Create(HttpMethod method, string uri) + public IRequest Create(HttpMethod method, string uri, int requestId) { if (httpClient == null) throw new InvalidOperationException("Cant create request before configuring http client"); - return new Request(new HttpRequestMessage(method, uri), httpClient); + return new Request(new HttpRequestMessage(method, uri), httpClient, requestId); } } } diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index 945ff79..11b07d8 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -166,38 +166,39 @@ namespace CryptoExchange.Net /// Whether or not the request should be authenticated /// Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug) /// Where the post parameters should be placed - /// How array paramters should be serialized + /// How array parameters should be serialized /// [return: NotNull] protected virtual async Task> SendRequest(Uri uri, HttpMethod method, CancellationToken cancellationToken, Dictionary? parameters = null, bool signed = false, bool checkResult = true, PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null) where T : class { - log.Write(LogVerbosity.Debug, "Creating request for " + uri); + var requestId = NextId(); + log.Write(LogVerbosity.Debug, $"[{requestId}] Creating request for " + uri); if (signed && authProvider == null) { - log.Write(LogVerbosity.Warning, $"Request {uri.AbsolutePath} failed because no ApiCredentials were provided"); + log.Write(LogVerbosity.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided"); return new WebCallResult(null, null, null, new NoApiCredentialsError()); } - var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization); + var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization, requestId); foreach (var limiter in RateLimiters) { var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour); if (!limitResult.Success) { - log.Write(LogVerbosity.Debug, $"Request {request.RequestId} {uri.AbsolutePath} failed because of rate limit"); + log.Write(LogVerbosity.Debug, $"[{requestId}] Request {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, $"[{requestId}] Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}"); } 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, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null ? "" : $" via proxy {apiProxy.Host}")}"); return await GetResponse(request, cancellationToken).ConfigureAwait(false); } @@ -224,7 +225,7 @@ namespace CryptoExchange.Net var data = await reader.ReadToEndAsync().ConfigureAwait(false); responseStream.Close(); response.Close(); - log.Write(LogVerbosity.Debug, $"Data for request {request.RequestId} received: {data}"); + log.Write(LogVerbosity.Debug, $"[{request.RequestId}] Data received: {data}"); var parseResult = ValidateJson(data); if (!parseResult.Success) @@ -233,12 +234,12 @@ namespace CryptoExchange.Net if (error != null) return WebCallResult.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error); - var deserializeResult = Deserialize(parseResult.Data); + var deserializeResult = Deserialize(parseResult.Data, null, null, request.RequestId); return new WebCallResult(response.StatusCode, response.ResponseHeaders, deserializeResult.Data, deserializeResult.Error); } else { - var desResult = await Deserialize(responseStream).ConfigureAwait(false); + var desResult = await Deserialize(responseStream, null, request.RequestId).ConfigureAwait(false); responseStream.Close(); response.Close(); @@ -249,7 +250,7 @@ namespace CryptoExchange.Net { using var reader = new StreamReader(responseStream); var data = await reader.ReadToEndAsync().ConfigureAwait(false); - log.Write(LogVerbosity.Debug, $"Error for request {request.RequestId} received: {data}"); + log.Write(LogVerbosity.Debug, $"[{request.RequestId}] Error received: {data}"); responseStream.Close(); response.Close(); var parseResult = ValidateJson(data); @@ -258,7 +259,7 @@ namespace CryptoExchange.Net } catch (HttpRequestException requestException) { - log.Write(LogVerbosity.Warning, $"Request {request.RequestId} exception: " + requestException.Message); + log.Write(LogVerbosity.Warning, $"[{request.RequestId}] Request exception: " + requestException.Message); return new WebCallResult(null, null, default, new ServerError(requestException.Message)); } catch (TaskCanceledException canceledException) @@ -266,14 +267,14 @@ namespace CryptoExchange.Net if (canceledException.CancellationToken == cancellationToken) { // Cancellation token cancelled - log.Write(LogVerbosity.Warning, $"Request {request.RequestId} cancel requested"); + log.Write(LogVerbosity.Warning, $"[{request.RequestId}] Request cancel requested"); return new WebCallResult(null, null, default, new CancellationRequestedError()); } else { // Request timed out - log.Write(LogVerbosity.Warning, $"Request {request.RequestId} timed out"); - return new WebCallResult(null, null, default, new WebError($"Request {request.RequestId} timed out")); + log.Write(LogVerbosity.Warning, $"[{request.RequestId}] Request timed out"); + return new WebCallResult(null, null, default, new WebError($"[{request.RequestId}] Request timed out")); } } } @@ -297,9 +298,10 @@ namespace CryptoExchange.Net /// The parameters of the request /// Whether or not the request should be authenticated /// Where the post parameters should be placed - /// How array paramters should be serialized + /// How array parameters should be serialized + /// Unique id of a request /// - protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization) + protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization, int requestId) { if (parameters == null) parameters = new Dictionary(); @@ -312,7 +314,7 @@ namespace CryptoExchange.Net uriString += "?" + parameters.CreateParamString(true, arraySerialization); var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; - var request = RequestFactory.Create(method, uriString); + var request = RequestFactory.Create(method, uriString, requestId); request.Accept = Constants.JsonContentHeader; var headers = new Dictionary();