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.Interfaces;
using CryptoExchange.Net.RateLimiter; using CryptoExchange.Net.RateLimiter;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading.Tasks;
namespace CryptoExchange.Net.UnitTests namespace CryptoExchange.Net.UnitTests
{ {
@ -22,7 +24,7 @@ namespace CryptoExchange.Net.UnitTests
// arrange // arrange
var client = new TestRestClient(); var client = new TestRestClient();
var expected = new TestObject() { DecimalData = 1.23M, IntData = 10, StringData = "Some data" }; var expected = new TestObject() { DecimalData = 1.23M, IntData = 10, StringData = "Some data" };
client.SetResponse(JsonConvert.SerializeObject(expected)); client.SetResponse(JsonConvert.SerializeObject(expected), out _);
// act // act
var result = client.Request<TestObject>().Result; var result = client.Request<TestObject>().Result;
@ -37,7 +39,7 @@ namespace CryptoExchange.Net.UnitTests
{ {
// arrange // arrange
var client = new TestRestClient(); var client = new TestRestClient();
client.SetResponse("{\"property\": 123"); client.SetResponse("{\"property\": 123", out _);
// act // act
var result = client.Request<TestObject>().Result; var result = client.Request<TestObject>().Result;
@ -119,6 +121,46 @@ namespace CryptoExchange.Net.UnitTests
Assert.IsTrue(client.RequestTimeout == TimeSpan.FromMinutes(1)); 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] [TestCase]
public void SettingRateLimitingBehaviourToFail_Should_FailLimitedRequests() public void SettingRateLimitingBehaviourToFail_Should_FailLimitedRequests()
{ {
@ -128,12 +170,12 @@ namespace CryptoExchange.Net.UnitTests
RateLimiters = new List<IRateLimiter> { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) }, RateLimiters = new List<IRateLimiter> { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) },
RateLimitingBehaviour = RateLimitingBehaviour.Fail RateLimitingBehaviour = RateLimitingBehaviour.Fail
}); });
client.SetResponse("{\"property\": 123}"); client.SetResponse("{\"property\": 123}", out _);
// act // act
var result1 = client.Request<TestObject>().Result; var result1 = client.Request<TestObject>().Result;
client.SetResponse("{\"property\": 123}"); client.SetResponse("{\"property\": 123}", out _);
var result2 = client.Request<TestObject>().Result; var result2 = client.Request<TestObject>().Result;
@ -151,13 +193,13 @@ namespace CryptoExchange.Net.UnitTests
RateLimiters = new List<IRateLimiter> { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) }, RateLimiters = new List<IRateLimiter> { new RateLimiterTotal(1, TimeSpan.FromSeconds(1)) },
RateLimitingBehaviour = RateLimitingBehaviour.Wait RateLimitingBehaviour = RateLimitingBehaviour.Wait
}); });
client.SetResponse("{\"property\": 123}"); client.SetResponse("{\"property\": 123}", out _);
// act // act
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var result1 = client.Request<TestObject>().Result; 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; var result2 = client.Request<TestObject>().Result;
sw.Stop(); sw.Stop();
@ -178,17 +220,17 @@ namespace CryptoExchange.Net.UnitTests
LogLevel = LogLevel.Debug, LogLevel = LogLevel.Debug,
ApiCredentials = new ApiCredentials("TestKey", "TestSecret") ApiCredentials = new ApiCredentials("TestKey", "TestSecret")
}); });
client.SetResponse("{\"property\": 123}"); client.SetResponse("{\"property\": 123}", out _);
// act // act
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var result1 = client.Request<TestObject>().Result; var result1 = client.Request<TestObject>().Result;
client.SetKey("TestKey2", "TestSecret2"); // set to different key 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; var result2 = client.Request<TestObject>().Result;
client.SetKey("TestKey", "TestSecret"); // set back to original key, should delay 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; var result3 = client.Request<TestObject>().Result;
sw.Stop(); 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); 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); return base.AddAuthenticationToParameters(uri, method, parameters, signed, postParameters, arraySerialization);
} }

View File

@ -11,6 +11,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using System.Collections.Generic;
namespace CryptoExchange.Net.UnitTests.TestImplementations namespace CryptoExchange.Net.UnitTests.TestImplementations
{ {
@ -26,12 +27,17 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
RequestFactory = new Mock<IRequestFactory>().Object; RequestFactory = new Mock<IRequestFactory>().Object;
} }
public void SetParameterPosition(HttpMethod method, HttpMethodParameterPosition position)
{
ParameterPositions[method] = position;
}
public void SetKey(string key, string secret) public void SetKey(string key, string secret)
{ {
SetAuthenticationProvider(new UnitTests.TestAuthProvider(new ApiCredentials(key, 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 expectedBytes = Encoding.UTF8.GetBytes(responseData);
var responseStream = new MemoryStream(); var responseStream = new MemoryStream();
@ -42,13 +48,23 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
response.Setup(c => c.IsSuccessStatusCode).Returns(true); response.Setup(c => c.IsSuccessStatusCode).Returns(true);
response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream)); response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream));
var headers = new Dictionary<string, IEnumerable<string>>();
var request = new Mock<IRequest>(); var request = new Mock<IRequest>();
request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); 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.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); var factory = Mock.Get(RequestFactory);
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>())) 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); .Returns(request.Object);
requestObj = request.Object;
} }
public void SetErrorWithoutResponse(HttpStatusCode code, string message) 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.IsSuccessStatusCode).Returns(false);
response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream)); response.Setup(c => c.GetResponseStreamAsync()).Returns(Task.FromResult((Stream)responseStream));
var headers = new Dictionary<string, IEnumerable<string>>();
var request = new Mock<IRequest>(); var request = new Mock<IRequest>();
request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com")); 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.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); var factory = Mock.Get(RequestFactory);
factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<string>(), It.IsAny<int>())) 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); .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); 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 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="method">The HTTP method of the request</param>
/// <param name="parameters">The provided parameters for 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="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> /// <param name="arraySerialization">How array parameters are serialized</param>
/// <returns>Should return the original parameter list including any authentication parameters needed</returns> /// <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, 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; return parameters;
} }
@ -46,11 +46,11 @@ namespace CryptoExchange.Net.Authentication
/// <param name="method">The HTTP method of the request</param> /// <param name="method">The HTTP method of the request</param>
/// <param name="parameters">The provided parameters for 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="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> /// <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> /// <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, 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>(); return new Dictionary<string, string>();
} }

View File

@ -97,7 +97,7 @@
</summary> </summary>
<param name="credentials"></param> <param name="credentials"></param>
</member> </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> <summary>
Add authentication to the parameter list based on the provided credentials Add authentication to the parameter list based on the provided credentials
</summary> </summary>
@ -105,11 +105,11 @@
<param name="method">The HTTP method of the request</param> <param name="method">The HTTP method of the request</param>
<param name="parameters">The provided parameters for 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="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> <param name="arraySerialization">How array parameters are serialized</param>
<returns>Should return the original parameter list including any authentication parameters needed</returns> <returns>Should return the original parameter list including any authentication parameters needed</returns>
</member> </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> <summary>
Add authentication to the header dictionary based on the provided credentials Add authentication to the header dictionary based on the provided credentials
</summary> </summary>
@ -117,7 +117,7 @@
<param name="method">The HTTP method of the request</param> <param name="method">The HTTP method of the request</param>
<param name="parameters">The provided parameters for 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="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> <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> <returns>Should return a dictionary containing any header key/value pairs needed for authenticating the request</returns>
</member> </member>
@ -1163,6 +1163,12 @@
<param name="key"></param> <param name="key"></param>
<param name="value"></param> <param name="value"></param>
</member> </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)"> <member name="M:CryptoExchange.Net.Interfaces.IRequest.GetResponseAsync(System.Threading.CancellationToken)">
<summary> <summary>
Get the response Get the response
@ -1934,19 +1940,19 @@
Wait till the request can be send Wait till the request can be send
</summary> </summary>
</member> </member>
<member name="T:CryptoExchange.Net.Objects.PostParameters"> <member name="T:CryptoExchange.Net.Objects.HttpMethodParameterPosition">
<summary> <summary>
Where the parameters for a post request should be added Where the parameters for a HttpMethod should be added in a request
</summary> </summary>
</member> </member>
<member name="F:CryptoExchange.Net.Objects.PostParameters.InBody"> <member name="F:CryptoExchange.Net.Objects.HttpMethodParameterPosition.InBody">
<summary> <summary>
Post parameters in body Parameters in body
</summary> </summary>
</member> </member>
<member name="F:CryptoExchange.Net.Objects.PostParameters.InUri"> <member name="F:CryptoExchange.Net.Objects.HttpMethodParameterPosition.InUri">
<summary> <summary>
Post parameters in url Parameters in url
</summary> </summary>
</member> </member>
<member name="T:CryptoExchange.Net.Objects.RequestBodyFormat"> <member name="T:CryptoExchange.Net.Objects.RequestBodyFormat">
@ -2802,6 +2808,9 @@
<member name="M:CryptoExchange.Net.Requests.Request.AddHeader(System.String,System.String)"> <member name="M:CryptoExchange.Net.Requests.Request.AddHeader(System.String,System.String)">
<inheritdoc /> <inheritdoc />
</member> </member>
<member name="M:CryptoExchange.Net.Requests.Request.GetHeaders">
<inheritdoc />
</member>
<member name="M:CryptoExchange.Net.Requests.Request.SetContent(System.Byte[])"> <member name="M:CryptoExchange.Net.Requests.Request.SetContent(System.Byte[])">
<inheritdoc /> <inheritdoc />
</member> </member>
@ -2855,9 +2864,9 @@
The factory for creating requests. Used for unit testing The factory for creating requests. Used for unit testing
</summary> </summary>
</member> </member>
<member name="F:CryptoExchange.Net.RestClient.postParametersPosition"> <member name="P:CryptoExchange.Net.RestClient.ParameterPositions">
<summary> <summary>
Where to place post parameters by default Where to put the parameters for requests with different Http methods
</summary> </summary>
</member> </member>
<member name="F:CryptoExchange.Net.RestClient.requestBodyFormat"> <member name="F:CryptoExchange.Net.RestClient.requestBodyFormat">
@ -2900,6 +2909,11 @@
Total requests made by this client Total requests made by this client
</summary> </summary>
</member> </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)"> <member name="M:CryptoExchange.Net.RestClient.#ctor(System.String,CryptoExchange.Net.Objects.RestClientOptions,CryptoExchange.Net.Authentication.AuthenticationProvider)">
<summary> <summary>
ctor ctor
@ -2931,7 +2945,7 @@
</summary> </summary>
<returns>The roundtrip time of the ping request</returns> <returns>The roundtrip time of the ping request</returns>
</member> </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> <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>
@ -2942,10 +2956,11 @@
<param name="parameters">The parameters 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="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="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="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="credits">Credits used for the request</param>
<param name="deserializer">The JsonSerializer to use for deserialization</param> <param name="deserializer">The JsonSerializer to use for deserialization</param>
<param name="additionalHeaders">Additional headers to send with the request</param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.GetResponseAsync``1(CryptoExchange.Net.Interfaces.IRequest,Newtonsoft.Json.JsonSerializer,System.Threading.CancellationToken)"> <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> <param name="data">Received data</param>
<returns>Null if not an error, Error otherwise</returns> <returns>Null if not an error, Error otherwise</returns>
</member> </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> <summary>
Creates a request object Creates a request object
</summary> </summary>
@ -2974,9 +2989,10 @@
<param name="method">The method of the request</param> <param name="method">The method of the request</param>
<param name="parameters">The parameters 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="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="arraySerialization">How array parameters should be serialized</param>
<param name="requestId">Unique id of a request</param> <param name="requestId">Unique id of a request</param>
<param name="additionalHeaders">Additional headers to send with the request</param>
<returns></returns> <returns></returns>
</member> </member>
<member name="M:CryptoExchange.Net.RestClient.WriteParamBody(CryptoExchange.Net.Interfaces.IRequest,System.Collections.Generic.Dictionary{System.String,System.Object},System.String)"> <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})"> <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 /> <inheritdoc />
</member> </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> </members>
</doc> </doc>

View File

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

View File

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

View File

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

View File

@ -32,9 +32,15 @@ namespace CryptoExchange.Net
public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); public IRequestFactory RequestFactory { get; set; } = new RequestFactory();
/// <summary> /// <summary>
/// Where to place post parameters by default /// Where to put the parameters for requests with different Http methods
/// </summary> /// </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> /// <summary>
/// Request body content type /// Request body content type
@ -73,6 +79,11 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
public int TotalRequestsMade { get; private set; } public int TotalRequestsMade { get; private set; }
/// <summary>
/// Request headers to be sent with each request
/// </summary>
protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -167,16 +178,25 @@ namespace CryptoExchange.Net
/// <param name="parameters">The parameters 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="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="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="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="credits">Credits used for the request</param>
/// <param name="deserializer">The JsonSerializer to use for deserialization</param> /// <param name="deserializer">The JsonSerializer to use for deserialization</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <returns></returns> /// <returns></returns>
[return: NotNull] [return: NotNull]
protected virtual async Task<WebCallResult<T>> SendRequestAsync<T>(Uri uri, HttpMethod method, CancellationToken cancellationToken, protected virtual async Task<WebCallResult<T>> SendRequestAsync<T>(
Dictionary<string, object>? parameters = null, bool signed = false, bool checkResult = true, Uri uri,
PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null, int credits = 1, HttpMethod method,
JsonSerializer? deserializer = null) where T : class 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(); var requestId = NextId();
log.Write(LogLevel.Debug, $"[{requestId}] Creating request for " + uri); 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()); 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) foreach (var limiter in RateLimiters)
{ {
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour, credits); var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour, credits);
@ -201,9 +222,16 @@ namespace CryptoExchange.Net
} }
string? paramString = null; string? paramString = null;
if (method == HttpMethod.Post) if (parameterPosition == HttpMethodParameterPosition.InBody)
paramString = " with request body " + request.Content; 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}")}"); 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); 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="method">The method of the request</param>
/// <param name="parameters">The parameters 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="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="arraySerialization">How array parameters should be serialized</param>
/// <param name="requestId">Unique id of a request</param> /// <param name="requestId">Unique id of a request</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <returns></returns> /// <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) if (parameters == null)
parameters = new Dictionary<string, object>(); parameters = new Dictionary<string, object>();
var uriString = uri.ToString(); var uriString = uri.ToString();
if (authProvider != null) 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); uriString += "?" + parameters.CreateParamString(true, arraySerialization);
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
@ -342,12 +379,26 @@ namespace CryptoExchange.Net
var headers = new Dictionary<string, string>(); var headers = new Dictionary<string, string>();
if (authProvider != null) 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) foreach (var header in headers)
request.AddHeader(header.Key, header.Value); 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) if (parameters?.Any() == true)
WriteParamBody(request, parameters, contentType); WriteParamBody(request, parameters, contentType);