1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-06-08 08:26:20 +00:00

Added support for sending request with empty response

This commit is contained in:
Jan Korf 2022-05-01 13:50:23 +02:00
parent cb9a766c3b
commit 2d470d18e2

View File

@ -61,6 +61,44 @@ namespace CryptoExchange.Net
apiClient.SetApiCredentials(credentials); apiClient.SetApiCredentials(credentials);
} }
/// <summary>
/// Execute a request to the uri and returns if it was successful
/// </summary>
/// <param name="apiClient">The API client the request is for</param>
/// <param name="uri">The uri to send the request to</param>
/// <param name="method">The method of the request</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param>
/// <param name="deserializer">The JsonSerializer to use for deserialization</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <param name="ignoreRatelimit">Ignore rate limits for this request</param>
/// <returns></returns>
[return: NotNull]
protected virtual async Task<WebCallResult> SendRequestAsync(RestApiClient apiClient,
Uri uri,
HttpMethod method,
CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null,
bool signed = false,
HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1,
JsonSerializer? deserializer = null,
Dictionary<string, string>? additionalHeaders = null,
bool ignoreRatelimit = false)
{
var request = await PrepareRequestAsync(apiClient, uri, method, cancellationToken, parameters, signed, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false);
if (!request)
return new WebCallResult(request.Error!);
var result = await GetResponseAsync<object>(apiClient, request.Data, deserializer, cancellationToken, true).ConfigureAwait(false);
return result.AsDataless();
}
/// <summary> /// <summary>
/// Execute a request to the uri and deserialize the response into the provided type parameter /// Execute a request to the uri and deserialize the response into the provided type parameter
/// </summary> /// </summary>
@ -93,6 +131,42 @@ namespace CryptoExchange.Net
Dictionary<string, string>? additionalHeaders = null, Dictionary<string, string>? additionalHeaders = null,
bool ignoreRatelimit = false bool ignoreRatelimit = false
) where T : class ) where T : class
{
var request = await PrepareRequestAsync(apiClient, uri, method, cancellationToken, parameters, signed, parameterPosition, arraySerialization, requestWeight, deserializer, additionalHeaders, ignoreRatelimit).ConfigureAwait(false);
if (!request)
return new WebCallResult<T>(request.Error!);
return await GetResponseAsync<T>(apiClient, request.Data, deserializer, cancellationToken, false).ConfigureAwait(false);
}
/// <summary>
/// Prepares a request to be sent to the server
/// </summary>
/// <param name="apiClient">The API client the request is for</param>
/// <param name="uri">The uri to send the request to</param>
/// <param name="method">The method of the request</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
/// <param name="requestWeight">Credits used for the request</param>
/// <param name="deserializer">The JsonSerializer to use for deserialization</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <param name="ignoreRatelimit">Ignore rate limits for this request</param>
/// <returns></returns>
protected virtual async Task<CallResult<IRequest>> PrepareRequestAsync(RestApiClient apiClient,
Uri uri,
HttpMethod method,
CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null,
bool signed = false,
HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null,
int requestWeight = 1,
JsonSerializer? deserializer = null,
Dictionary<string, string>? additionalHeaders = null,
bool ignoreRatelimit = false)
{ {
var requestId = NextId(); var requestId = NextId();
@ -102,7 +176,7 @@ namespace CryptoExchange.Net
if (!syncTimeResult) if (!syncTimeResult)
{ {
log.Write(LogLevel.Debug, $"[{requestId}] Failed to sync time, aborting request: " + syncTimeResult.Error); log.Write(LogLevel.Debug, $"[{requestId}] Failed to sync time, aborting request: " + syncTimeResult.Error);
return syncTimeResult.As<T>(default); return syncTimeResult.As<IRequest>(default);
} }
} }
@ -112,14 +186,14 @@ namespace CryptoExchange.Net
{ {
var limitResult = await limiter.LimitRequestAsync(log, uri.AbsolutePath, method, signed, apiClient.Options.ApiCredentials?.Key, apiClient.Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false); var limitResult = await limiter.LimitRequestAsync(log, uri.AbsolutePath, method, signed, apiClient.Options.ApiCredentials?.Key, apiClient.Options.RateLimitingBehaviour, requestWeight, cancellationToken).ConfigureAwait(false);
if (!limitResult.Success) if (!limitResult.Success)
return new WebCallResult<T>(limitResult.Error!); return new CallResult<IRequest>(limitResult.Error!);
} }
} }
if (signed && apiClient.AuthenticationProvider == null) if (signed && apiClient.AuthenticationProvider == null)
{ {
log.Write(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided"); log.Write(LogLevel.Warning, $"[{requestId}] Request {uri.AbsolutePath} failed because no ApiCredentials were provided");
return new WebCallResult<T>(new NoApiCredentialsError()); return new CallResult<IRequest>(new NoApiCredentialsError());
} }
log.Write(LogLevel.Information, $"[{requestId}] Creating request for " + uri); log.Write(LogLevel.Information, $"[{requestId}] Creating request for " + uri);
@ -136,9 +210,11 @@ namespace CryptoExchange.Net
apiClient.TotalRequestsMade++; apiClient.TotalRequestsMade++;
log.Write(LogLevel.Trace, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(ClientOptions.Proxy == null ? "" : $" via proxy {ClientOptions.Proxy.Host}")}"); log.Write(LogLevel.Trace, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(ClientOptions.Proxy == null ? "" : $" via proxy {ClientOptions.Proxy.Host}")}");
return await GetResponseAsync<T>(apiClient, request, deserializer, cancellationToken).ConfigureAwait(false); return new CallResult<IRequest>(request);
} }
/// <summary> /// <summary>
/// Executes the request and returns the result deserialized into the type parameter class /// Executes the request and returns the result deserialized into the type parameter class
/// </summary> /// </summary>
@ -146,8 +222,14 @@ namespace CryptoExchange.Net
/// <param name="request">The request object to execute</param> /// <param name="request">The request object to execute</param>
/// <param name="deserializer">The JsonSerializer to use for deserialization</param> /// <param name="deserializer">The JsonSerializer to use for deserialization</param>
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <param name="expectedEmptyResponse">If an empty response is expected</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(BaseApiClient apiClient, IRequest request, JsonSerializer? deserializer, CancellationToken cancellationToken) protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
BaseApiClient apiClient,
IRequest request,
JsonSerializer? deserializer,
CancellationToken cancellationToken,
bool expectedEmptyResponse)
{ {
try try
{ {
@ -169,6 +251,8 @@ namespace CryptoExchange.Net
response.Close(); response.Close();
log.Write(LogLevel.Debug, $"[{request.RequestId}] Response received in {sw.ElapsedMilliseconds}ms{(log.Level == LogLevel.Trace ? (": "+data): "")}"); log.Write(LogLevel.Debug, $"[{request.RequestId}] Response received in {sw.ElapsedMilliseconds}ms{(log.Level == LogLevel.Trace ? (": "+data): "")}");
if (!expectedEmptyResponse)
{
// Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example // Validate if it is valid json. Sometimes other data will be returned, 502 error html pages for example
var parseResult = ValidateJson(data); var parseResult = ValidateJson(data);
if (!parseResult.Success) if (!parseResult.Success)
@ -185,6 +269,34 @@ namespace CryptoExchange.Net
} }
else else
{ {
if (!string.IsNullOrEmpty(data))
{
var parseResult = ValidateJson(data);
if (!parseResult.Success)
// Not empty, and not json
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, ClientOptions.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, parseResult.Error!);
var error = await TryParseErrorAsync(parseResult.Data).ConfigureAwait(false);
if (error != null)
// Error response
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, ClientOptions.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, error!);
}
// Empty success response; okay
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, ClientOptions.OutputOriginalData ? data : null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, default);
}
}
else
{
if (expectedEmptyResponse)
{
// We expected an empty response and the request is successful and don't manually parse errors, so assume it's correct
responseStream.Close();
response.Close();
return new WebCallResult<T>(statusCode, headers, sw.Elapsed, null, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), default, null);
}
// Success status code, and we don't have to check for errors. Continue deserializing directly from the stream // Success status code, and we don't have to check for errors. Continue deserializing directly from the stream
var desResult = await DeserializeAsync<T>(responseStream, deserializer, request.RequestId, sw.ElapsedMilliseconds).ConfigureAwait(false); var desResult = await DeserializeAsync<T>(responseStream, deserializer, request.RequestId, sw.ElapsedMilliseconds).ConfigureAwait(false);
responseStream.Close(); responseStream.Close();