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

Added option to send additional headers for each request or for individual requests, added HttpMethod parameter position configuration to rest client

This commit is contained in:
Jkorf 2021-09-15 14:48:19 +02:00
parent f5df46a8a5
commit d1a2856a08
9 changed files with 203 additions and 196 deletions

View File

@ -10,6 +10,8 @@ using System.Linq;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.RateLimiter;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading.Tasks;
namespace CryptoExchange.Net.UnitTests
{
@ -22,7 +24,7 @@ namespace CryptoExchange.Net.UnitTests
// arrange
var client = new TestRestClient();
var expected = new TestObject() { DecimalData = 1.23M, IntData = 10, StringData = "Some data" };
client.SetResponse(JsonConvert.SerializeObject(expected));
client.SetResponse(JsonConvert.SerializeObject(expected), out _);
// act
var result = client.Request<TestObject>().Result;
@ -37,7 +39,7 @@ namespace CryptoExchange.Net.UnitTests
{
// arrange
var client = new TestRestClient();
client.SetResponse("{\"property\": 123");
client.SetResponse("{\"property\": 123", out _);
// act
var result = client.Request<TestObject>().Result;
@ -119,6 +121,46 @@ namespace CryptoExchange.Net.UnitTests
Assert.IsTrue(client.RequestTimeout == TimeSpan.FromMinutes(1));
}
[TestCase("GET", HttpMethodParameterPosition.InUri)] // No need to test InBody for GET since thats not valid
[TestCase("POST", HttpMethodParameterPosition.InBody)]
[TestCase("POST", HttpMethodParameterPosition.InUri)]
[TestCase("DELETE", HttpMethodParameterPosition.InBody)]
[TestCase("DELETE", HttpMethodParameterPosition.InUri)]
[TestCase("PUT", HttpMethodParameterPosition.InUri)]
[TestCase("PUT", HttpMethodParameterPosition.InBody)]
public async Task Setting_Should_ResultInOptionsSet(string method, HttpMethodParameterPosition pos)
{
// arrange
// act
var client = new TestRestClient(new RestClientOptions("")
{
BaseAddress = "http://test.address.com",
});
client.SetParameterPosition(new HttpMethod(method), pos);
client.SetResponse("{}", out var request);
await client.RequestWithParams<TestObject>(new HttpMethod(method), new Dictionary<string, object>
{
{ "TestParam1", "Value1" },
{ "TestParam2", 2 },
},
new Dictionary<string, string>
{
{ "TestHeader", "123" }
});
// assert
Assert.AreEqual(request.Method, new HttpMethod(method));
Assert.AreEqual(request.Content?.Contains("TestParam1") == true, pos == HttpMethodParameterPosition.InBody);
Assert.AreEqual(request.Uri.ToString().Contains("TestParam1"), pos == HttpMethodParameterPosition.InUri);
Assert.AreEqual(request.Content?.Contains("TestParam2") == true, pos == HttpMethodParameterPosition.InBody);
Assert.AreEqual(request.Uri.ToString().Contains("TestParam2"), pos == HttpMethodParameterPosition.InUri);
Assert.AreEqual(request.GetHeaders().First().Key, "TestHeader");
Assert.IsTrue(request.GetHeaders().First().Value.Contains("123"));
}
[TestCase]
public void SettingRateLimitingBehaviourToFail_Should_FailLimitedRequests()
{
@ -128,12 +170,12 @@ namespace CryptoExchange.Net.UnitTests
RateLimiters = new List<IRateLimiter> { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) },
RateLimitingBehaviour = RateLimitingBehaviour.Fail
});
client.SetResponse("{\"property\": 123}");
client.SetResponse("{\"property\": 123}", out _);
// act
var result1 = client.Request<TestObject>().Result;
client.SetResponse("{\"property\": 123}");
client.SetResponse("{\"property\": 123}", out _);
var result2 = client.Request<TestObject>().Result;
@ -151,13 +193,13 @@ namespace CryptoExchange.Net.UnitTests
RateLimiters = new List<IRateLimiter> { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) },
RateLimitingBehaviour = RateLimitingBehaviour.Wait
});
client.SetResponse("{\"property\": 123}");
client.SetResponse("{\"property\": 123}", out _);
// act
var sw = Stopwatch.StartNew();
var result1 = client.Request<TestObject>().Result;
client.SetResponse("{\"property\": 123}"); // reset response stream
client.SetResponse("{\"property\": 123}", out _); // reset response stream
var result2 = client.Request<TestObject>().Result;
sw.Stop();
@ -178,17 +220,17 @@ namespace CryptoExchange.Net.UnitTests
LogLevel = LogLevel.Debug,
ApiCredentials = new ApiCredentials("TestKey", "TestSecret")
});
client.SetResponse("{\"property\": 123}");
client.SetResponse("{\"property\": 123}", out _);
// act
var sw = Stopwatch.StartNew();
var result1 = client.Request<TestObject>().Result;
client.SetKey("TestKey2", "TestSecret2"); // set to different key
client.SetResponse("{\"property\": 123}"); // reset response stream
client.SetResponse("{\"property\": 123}", out _); // reset response stream
var result2 = client.Request<TestObject>().Result;
client.SetKey("TestKey", "TestSecret"); // set back to original key, should delay
client.SetResponse("{\"property\": 123}"); // reset response stream
client.SetResponse("{\"property\": 123}", out _); // reset response stream
var result3 = client.Request<TestObject>().Result;
sw.Stop();

View File

@ -38,12 +38,12 @@ namespace CryptoExchange.Net.UnitTests
{
}
public override Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization)
public override Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization)
{
return base.AddAuthenticationToHeaders(uri, method, parameters, signed, postParameters, arraySerialization);
}
public override Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization)
public override Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, HttpMethodParameterPosition postParameters, ArrayParametersSerialization arraySerialization)
{
return base.AddAuthenticationToParameters(uri, method, parameters, signed, postParameters, arraySerialization);
}

View File

@ -11,6 +11,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.Authentication;
using System.Collections.Generic;
namespace CryptoExchange.Net.UnitTests.TestImplementations
{
@ -26,12 +27,17 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
RequestFactory = new Mock<IRequestFactory>().Object;
}
public void SetParameterPosition(HttpMethod method, HttpMethodParameterPosition position)
{
ParameterPositions[method] = position;
}
public void SetKey(string key, string secret)
{
SetAuthenticationProvider(new UnitTests.TestAuthProvider(new ApiCredentials(key, secret)));
}
public void SetResponse(string responseData, Stream requestStream = null)
public void SetResponse(string responseData, out IRequest requestObj)
{
var expectedBytes = Encoding.UTF8.GetBytes(responseData);
var responseStream = new MemoryStream();
@ -42,13 +48,23 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
response.Setup(c => c.IsSuccessStatusCode).Returns(true);
response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream));
var headers = new Dictionary<string, IEnumerable<string>>();
var request = new Mock<IRequest>();
request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com"));
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(response.Object));
request.Setup(c => c.SetContent(It.IsAny<string>(), It.IsAny<string>())).Callback(new Action<string, string>((content, type) => { request.Setup(r => r.Content).Returns(content); }));
request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new List<string> { val }));
request.Setup(c => c.GetHeaders()).Returns(() => headers);
var factory = Mock.Get(RequestFactory);
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
.Callback<HttpMethod, string, int>((method, uri, id) =>
{
request.Setup(a => a.Uri).Returns(new Uri(uri));
request.Setup(a => a.Method).Returns(method);
})
.Returns(request.Object);
requestObj = request.Object;
}
public void SetErrorWithoutResponse(HttpStatusCode code, string message)
@ -75,12 +91,16 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
response.Setup(c => c.IsSuccessStatusCode).Returns(false);
response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream));
var headers = new Dictionary<string, IEnumerable<string>>();
var request = new Mock<IRequest>();
request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com"));
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(response.Object));
request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new List<string> { val }));
request.Setup(c => c.GetHeaders()).Returns(headers);
var factory = Mock.Get(RequestFactory);
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>()))
.Callback<HttpMethod, string, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(new Uri(uri)))
.Returns(request.Object);
}
@ -88,6 +108,11 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
{
return await SendRequestAsync<T>(new Uri("http://www.test.com"), HttpMethod.Get, ct);
}
public async Task<CallResult<T>> RequestWithParams<T>(HttpMethod method, Dictionary<string, object> parameters, Dictionary<string, string> headers) where T : class
{
return await SendRequestAsync<T>(new Uri("http://www.test.com"), method, default, parameters, additionalHeaders: headers);
}
}
public class TestAuthProvider : AuthenticationProvider

View File

@ -30,11 +30,11 @@ namespace CryptoExchange.Net.Authentication
/// <param name="method">The HTTP method of the request</param>
/// <param name="parameters">The provided parameters for the request</param>
/// <param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param>
/// <param name="postParameterPosition">Where post parameters are placed, in the URI or in the request body</param>
/// <param name="parameterPosition">Where parameters are placed, in the URI or in the request body</param>
/// <param name="arraySerialization">How array parameters are serialized</param>
/// <returns>Should return the original parameter list including any authentication parameters needed</returns>
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization)
HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization)
{
return parameters;
}
@ -46,11 +46,11 @@ namespace CryptoExchange.Net.Authentication
/// <param name="method">The HTTP method of the request</param>
/// <param name="parameters">The provided parameters for the request</param>
/// <param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param>
/// <param name="postParameterPosition">Where post parameters are placed, in the URI or in the request body</param>
/// <param name="parameterPosition">Where post parameters are placed, in the URI or in the request body</param>
/// <param name="arraySerialization">How array parameters are serialized</param>
/// <returns>Should return a dictionary containing any header key/value pairs needed for authenticating the request</returns>
public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization)
HttpMethodParameterPosition parameterPosition, ArrayParametersSerialization arraySerialization)
{
return new Dictionary<string, string>();
}

View File

@ -97,7 +97,7 @@
</summary>
<param name="credentials"></param>
</member>
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToParameters(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.PostParameters,CryptoExchange.Net.Objects.ArrayParametersSerialization)">
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToParameters(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.HttpMethodParameterPosition,CryptoExchange.Net.Objects.ArrayParametersSerialization)">
<summary>
Add authentication to the parameter list based on the provided credentials
</summary>
@ -105,11 +105,11 @@
<param name="method">The HTTP method of the request</param>
<param name="parameters">The provided parameters for the request</param>
<param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param>
<param name="postParameterPosition">Where post parameters are placed, in the URI or in the request body</param>
<param name="parameterPosition">Where parameters are placed, in the URI or in the request body</param>
<param name="arraySerialization">How array parameters are serialized</param>
<returns>Should return the original parameter list including any authentication parameters needed</returns>
</member>
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToHeaders(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.PostParameters,CryptoExchange.Net.Objects.ArrayParametersSerialization)">
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToHeaders(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.HttpMethodParameterPosition,CryptoExchange.Net.Objects.ArrayParametersSerialization)">
<summary>
Add authentication to the header dictionary based on the provided credentials
</summary>
@ -117,7 +117,7 @@
<param name="method">The HTTP method of the request</param>
<param name="parameters">The provided parameters for the request</param>
<param name="signed">Wether or not the request needs to be signed. If not typically the parameters list can just be returned</param>
<param name="postParameterPosition">Where post parameters are placed, in the URI or in the request body</param>
<param name="parameterPosition">Where post parameters are placed, in the URI or in the request body</param>
<param name="arraySerialization">How array parameters are serialized</param>
<returns>Should return a dictionary containing any header key/value pairs needed for authenticating the request</returns>
</member>
@ -1163,6 +1163,12 @@
<param name="key"></param>
<param name="value"></param>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRequest.GetHeaders">
<summary>
Get all headers
</summary>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.Interfaces.IRequest.GetResponseAsync(System.Threading.CancellationToken)">
<summary>
Get the response
@ -1934,19 +1940,19 @@
Wait till the request can be send
</summary>
</member>
<member name="T:CryptoExchange.Net.Objects.PostParameters">
<member name="T:CryptoExchange.Net.Objects.HttpMethodParameterPosition">
<summary>
Where the parameters for a post request should be added
Where the parameters for a HttpMethod should be added in a request
</summary>
</member>
<member name="F:CryptoExchange.Net.Objects.PostParameters.InBody">
<member name="F:CryptoExchange.Net.Objects.HttpMethodParameterPosition.InBody">
<summary>
Post parameters in body
Parameters in body
</summary>
</member>
<member name="F:CryptoExchange.Net.Objects.PostParameters.InUri">
<member name="F:CryptoExchange.Net.Objects.HttpMethodParameterPosition.InUri">
<summary>
Post parameters in url
Parameters in url
</summary>
</member>
<member name="T:CryptoExchange.Net.Objects.RequestBodyFormat">
@ -2802,6 +2808,9 @@
<member name="M:CryptoExchange.Net.Requests.Request.AddHeader(System.String,System.String)">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Requests.Request.GetHeaders">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Requests.Request.SetContent(System.Byte[])">
<inheritdoc />
</member>
@ -2855,9 +2864,9 @@
The factory for creating requests. Used for unit testing
</summary>
</member>
<member name="F:CryptoExchange.Net.RestClient.postParametersPosition">
<member name="P:CryptoExchange.Net.RestClient.ParameterPositions">
<summary>
Where to place post parameters by default
Where to put the parameters for requests with different Http methods
</summary>
</member>
<member name="F:CryptoExchange.Net.RestClient.requestBodyFormat">
@ -2900,6 +2909,11 @@
Total requests made by this client
</summary>
</member>
<member name="P:CryptoExchange.Net.RestClient.StandardRequestHeaders">
<summary>
Request headers to be sent with each request
</summary>
</member>
<member name="M:CryptoExchange.Net.RestClient.#ctor(System.String,CryptoExchange.Net.Objects.RestClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary>
ctor
@ -2931,7 +2945,7 @@
</summary>
<returns>The roundtrip time of the ping request</returns>
</member>
<member name="M:CryptoExchange.Net.RestClient.SendRequestAsync``1(System.Uri,System.Net.Http.HttpMethod,System.Threading.CancellationToken,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,System.Boolean,System.Nullable{CryptoExchange.Net.Objects.PostParameters},System.Nullable{CryptoExchange.Net.Objects.ArrayParametersSerialization},System.Int32,Newtonsoft.Json.JsonSerializer)">
<member name="M:CryptoExchange.Net.RestClient.SendRequestAsync``1(System.Uri,System.Net.Http.HttpMethod,System.Threading.CancellationToken,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,System.Boolean,System.Nullable{CryptoExchange.Net.Objects.HttpMethodParameterPosition},System.Nullable{CryptoExchange.Net.Objects.ArrayParametersSerialization},System.Int32,Newtonsoft.Json.JsonSerializer,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Execute a request to the uri and deserialize the response into the provided type parameter
</summary>
@ -2942,10 +2956,11 @@
<param name="parameters">The parameters of the request</param>
<param name="signed">Whether or not the request should be authenticated</param>
<param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
<param name="postPosition">Where the post parameters should be placed, overwrites the value set in the client</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="credits">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>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.RestClient.GetResponseAsync``1(CryptoExchange.Net.Interfaces.IRequest,Newtonsoft.Json.JsonSerializer,System.Threading.CancellationToken)">
@ -2966,7 +2981,7 @@
<param name="data">Received data</param>
<returns>Null if not an error, Error otherwise</returns>
</member>
<member name="M:CryptoExchange.Net.RestClient.ConstructRequest(System.Uri,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.PostParameters,CryptoExchange.Net.Objects.ArrayParametersSerialization,System.Int32)">
<member name="M:CryptoExchange.Net.RestClient.ConstructRequest(System.Uri,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.HttpMethodParameterPosition,CryptoExchange.Net.Objects.ArrayParametersSerialization,System.Int32,System.Collections.Generic.Dictionary{System.String,System.String})">
<summary>
Creates a request object
</summary>
@ -2974,9 +2989,10 @@
<param name="method">The method of the request</param>
<param name="parameters">The parameters of the request</param>
<param name="signed">Whether or not the request should be authenticated</param>
<param name="postPosition">Where the post parameters should be placed</param>
<param name="parameterPosition">Where the parameters should be placed</param>
<param name="arraySerialization">How array parameters should be serialized</param>
<param name="requestId">Unique id of a request</param>
<param name="additionalHeaders">Additional headers to send with the request</param>
<returns></returns>
</member>
<member name="M:CryptoExchange.Net.RestClient.WriteParamBody(CryptoExchange.Net.Interfaces.IRequest,System.Collections.Generic.Dictionary{System.String,System.Object},System.String)">
@ -3924,148 +3940,5 @@
<member name="M:CryptoExchange.Net.Sockets.WebsocketFactory.CreateWebsocket(CryptoExchange.Net.Logging.Log,System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.String})">
<inheritdoc />
</member>
<member name="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute">
<summary>
Specifies that <see langword="null"/> is allowed as an input even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.AllowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute">
<summary>
Specifies that <see langword="null"/> is disallowed as an input even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DisallowNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute">
<summary>
Specifies that a method that will never return under any circumstance.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute">
<summary>
Specifies that the method will not return if the associated <see cref="T:System.Boolean"/>
parameter is passed the specified value.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.ParameterValue">
<summary>
Gets the condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.#ctor(System.Boolean)">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute"/>
class with the specified parameter value.
</summary>
<param name="parameterValue">
The condition parameter value.
Code after the method is considered unreachable by diagnostics if the argument
to the associated parameter matches this value.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute">
<summary>
Specifies that an output may be <see langword="null"/> even if the
corresponding type disallows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue"/>,
the parameter may be <see langword="null"/> even if the corresponding type disallows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter may be <see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullAttribute">
<summary>
Specifies that an output is not <see langword="null"/> even if the
corresponding type allows it.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullAttribute.#ctor">
<summary>
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.NotNullAttribute"/> class.
</summary>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute">
<summary>
Specifies that the output will be non-<see langword="null"/> if the
named parameter is non-<see langword="null"/>.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.ParameterName">
<summary>
Gets the associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.#ctor(System.String)">
<summary>
Initializes the attribute with the associated parameter name.
</summary>
<param name="parameterName">
The associated parameter name.
The output will be non-<see langword="null"/> if the argument to the
parameter specified is non-<see langword="null"/>.
</param>
</member>
<member name="T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute">
<summary>
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue"/>,
the parameter will not be <see langword="null"/> even if the corresponding type allows it.
</summary>
</member>
<member name="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue">
<summary>
Gets the return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</summary>
</member>
<member name="M:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.#ctor(System.Boolean)">
<summary>
Initializes the attribute with the specified return value condition.
</summary>
<param name="returnValue">
The return value condition.
If the method returns this value, the associated parameter will not be <see langword="null"/>.
</param>
</member>
</members>
</doc>

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@ -48,6 +49,13 @@ namespace CryptoExchange.Net.Interfaces
/// <param name="key"></param>
/// <param name="value"></param>
void AddHeader(string key, string value);
/// <summary>
/// Get all headers
/// </summary>
/// <returns></returns>
Dictionary<string, IEnumerable<string>> GetHeaders();
/// <summary>
/// Get the response
/// </summary>

View File

@ -16,16 +16,16 @@
}
/// <summary>
/// Where the parameters for a post request should be added
/// Where the parameters for a HttpMethod should be added in a request
/// </summary>
public enum PostParameters
public enum HttpMethodParameterPosition
{
/// <summary>
/// Post parameters in body
/// Parameters in body
/// </summary>
InBody,
/// <summary>
/// Post parameters in url
/// Parameters in url
/// </summary>
InUri
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
@ -63,6 +65,12 @@ namespace CryptoExchange.Net.Requests
request.Headers.Add(key, value);
}
/// <inheritdoc />
public Dictionary<string, IEnumerable<string>> GetHeaders()
{
return request.Headers.ToDictionary(h => h.Key, h => h.Value);
}
/// <inheritdoc />
public void SetContent(byte[] data)
{

View File

@ -32,9 +32,15 @@ namespace CryptoExchange.Net
public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
/// <summary>
/// Where to place post parameters by default
/// Where to put the parameters for requests with different Http methods
/// </summary>
protected PostParameters postParametersPosition = PostParameters.InBody;
protected Dictionary<HttpMethod, HttpMethodParameterPosition> ParameterPositions { get; set; } = new Dictionary<HttpMethod, HttpMethodParameterPosition>
{
{ HttpMethod.Get, HttpMethodParameterPosition.InUri },
{ HttpMethod.Post, HttpMethodParameterPosition.InBody },
{ HttpMethod.Delete, HttpMethodParameterPosition.InBody },
{ HttpMethod.Put, HttpMethodParameterPosition.InBody }
};
/// <summary>
/// Request body content type
@ -73,6 +79,11 @@ namespace CryptoExchange.Net
/// </summary>
public int TotalRequestsMade { get; private set; }
/// <summary>
/// Request headers to be sent with each request
/// </summary>
protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
/// <summary>
/// ctor
/// </summary>
@ -167,16 +178,25 @@ namespace CryptoExchange.Net
/// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
/// <param name="postPosition">Where the post parameters should be placed, overwrites the value set in the client</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="credits">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>
/// <returns></returns>
[return: NotNull]
protected virtual async Task<WebCallResult<T>> SendRequestAsync<T>(Uri uri, HttpMethod method, CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null, bool signed = false, bool checkResult = true,
PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null, int credits = 1,
JsonSerializer? deserializer = null) where T : class
protected virtual async Task<WebCallResult<T>> SendRequestAsync<T>(
Uri uri,
HttpMethod method,
CancellationToken cancellationToken,
Dictionary<string, object>? parameters = null,
bool signed = false,
bool checkResult = true,
HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null,
int credits = 1,
JsonSerializer? deserializer = null,
Dictionary<string, string>? additionalHeaders = null) where T : class
{
var requestId = NextId();
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri);
@ -186,7 +206,8 @@ namespace CryptoExchange.Net
return new WebCallResult<T>(null, null, null, new NoApiCredentialsError());
}
var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization, requestId);
var paramsPosition = parameterPosition ?? ParameterPositions[method];
var request = ConstructRequest(uri, method, parameters, signed, paramsPosition, arraySerialization ?? this.arraySerialization, requestId, additionalHeaders);
foreach (var limiter in RateLimiters)
{
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour, credits);
@ -201,9 +222,16 @@ namespace CryptoExchange.Net
}
string? paramString = null;
if (method == HttpMethod.Post)
if (parameterPosition == HttpMethodParameterPosition.InBody)
paramString = " with request body " + request.Content;
if (log.Level == LogLevel.Trace)
{
var headers = request.GetHeaders();
if (headers.Any())
paramString = " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"));
}
log.Write(LogLevel.Debug, $"[{requestId}] Sending {method}{(signed ? " signed" : "")} request to {request.Uri}{paramString ?? " "}{(apiProxy == null ? "" : $" via proxy {apiProxy.Host}")}");
return await GetResponseAsync<T>(request, deserializer, cancellationToken).ConfigureAwait(false);
}
@ -320,20 +348,29 @@ namespace CryptoExchange.Net
/// <param name="method">The method of the request</param>
/// <param name="parameters">The parameters of the request</param>
/// <param name="signed">Whether or not the request should be authenticated</param>
/// <param name="postPosition">Where the post parameters should be placed</param>
/// <param name="parameterPosition">Where the parameters should be placed</param>
/// <param name="arraySerialization">How array parameters should be serialized</param>
/// <param name="requestId">Unique id of a request</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <returns></returns>
protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary<string, object>? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization, int requestId)
protected virtual IRequest ConstructRequest(
Uri uri,
HttpMethod method,
Dictionary<string, object>? parameters,
bool signed,
HttpMethodParameterPosition parameterPosition,
ArrayParametersSerialization arraySerialization,
int requestId,
Dictionary<string, string>? additionalHeaders)
{
if (parameters == null)
parameters = new Dictionary<string, object>();
var uriString = uri.ToString();
if (authProvider != null)
parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, postPosition, arraySerialization);
parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, parameterPosition, arraySerialization);
if ((method == HttpMethod.Get || method == HttpMethod.Delete || postPosition == PostParameters.InUri) && parameters?.Any() == true)
if (parameterPosition == HttpMethodParameterPosition.InUri && parameters?.Any() == true)
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
@ -342,12 +379,26 @@ namespace CryptoExchange.Net
var headers = new Dictionary<string, string>();
if (authProvider != null)
headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, postPosition, arraySerialization);
headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, parameterPosition, arraySerialization);
foreach (var header in headers)
request.AddHeader(header.Key, header.Value);
if ((method == HttpMethod.Post || method == HttpMethod.Put) && postPosition != PostParameters.InUri)
if (additionalHeaders != null)
{
foreach (var header in additionalHeaders)
request.AddHeader(header.Key, header.Value);
}
if(StandardRequestHeaders != null)
{
foreach (var header in StandardRequestHeaders)
// Only add it if it isn't overwritten
if(additionalHeaders?.ContainsKey(header.Key) != true)
request.AddHeader(header.Key, header.Value);
}
if (parameterPosition == HttpMethodParameterPosition.InBody)
{
if (parameters?.Any() == true)
WriteParamBody(request, parameters, contentType);