From 2bf860947acf1d135b8deb192f68b4186e4ed868 Mon Sep 17 00:00:00 2001 From: JKorf Date: Wed, 28 Feb 2018 13:53:29 +0100 Subject: [PATCH] Initial commit --- ApiProxy.cs | 25 +++ Authentication/ApiCredentials.cs | 27 +++ Authentication/AuthenticationProvider.cs | 25 +++ BaseConverter.cs | 36 ++++ CallResult.cs | 24 +++ CryptoExchange.Net.csproj | 22 +++ Error.cs | 49 +++++ ExchangeClient.cs | 240 +++++++++++++++++++++++ ExchangeOptions.cs | 40 ++++ ExtensionMethods.cs | 29 +++ Interfaces/IExchangeClient.cs | 14 ++ Interfaces/IRequest.cs | 15 ++ Interfaces/IRequestFactory.cs | 8 + Interfaces/IResponse.cs | 9 + Logging/DebugTextWriter.cs | 16 ++ Logging/Log.cs | 25 +++ RateLimiter/IRateLimiter.cs | 7 + RateLimiter/RateLimitObject.cs | 32 +++ RateLimiter/RateLimiterPerEndpoint.cs | 62 ++++++ RateLimiter/RateLimiterTotal.cs | 57 ++++++ Requests/Request.cs | 41 ++++ Requests/RequestFactory.cs | 13 ++ Requests/Response.cs | 21 ++ 23 files changed, 837 insertions(+) create mode 100644 ApiProxy.cs create mode 100644 Authentication/ApiCredentials.cs create mode 100644 Authentication/AuthenticationProvider.cs create mode 100644 BaseConverter.cs create mode 100644 CallResult.cs create mode 100644 CryptoExchange.Net.csproj create mode 100644 Error.cs create mode 100644 ExchangeClient.cs create mode 100644 ExchangeOptions.cs create mode 100644 ExtensionMethods.cs create mode 100644 Interfaces/IExchangeClient.cs create mode 100644 Interfaces/IRequest.cs create mode 100644 Interfaces/IRequestFactory.cs create mode 100644 Interfaces/IResponse.cs create mode 100644 Logging/DebugTextWriter.cs create mode 100644 Logging/Log.cs create mode 100644 RateLimiter/IRateLimiter.cs create mode 100644 RateLimiter/RateLimitObject.cs create mode 100644 RateLimiter/RateLimiterPerEndpoint.cs create mode 100644 RateLimiter/RateLimiterTotal.cs create mode 100644 Requests/Request.cs create mode 100644 Requests/RequestFactory.cs create mode 100644 Requests/Response.cs diff --git a/ApiProxy.cs b/ApiProxy.cs new file mode 100644 index 0000000..f84f86a --- /dev/null +++ b/ApiProxy.cs @@ -0,0 +1,25 @@ +using System; + +namespace CryptoExchange.Net +{ + public class ApiProxy + { + /// + /// The host address of the proxy + /// + public string Host { get; } + /// + /// The port of the proxy + /// + public int Port { get; } + + public ApiProxy(string host, int port) + { + if(string.IsNullOrEmpty(host) || port <= 0) + throw new ArgumentException("Proxy host or port not filled"); + + Host = host; + Port = port; + } + } +} diff --git a/Authentication/ApiCredentials.cs b/Authentication/ApiCredentials.cs new file mode 100644 index 0000000..141ff6a --- /dev/null +++ b/Authentication/ApiCredentials.cs @@ -0,0 +1,27 @@ +using System; + +namespace CryptoExchange.Net.Authentication +{ + public class ApiCredentials + { + /// + /// The api key + /// + public string Key { get; } + /// + /// The api secret + /// + public string Secret { get; } + + public ApiCredentials() { } + + public ApiCredentials(string key, string secret) + { + if(string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret)) + throw new ArgumentException("Apikey or apisecret not provided"); + + Key = key; + Secret = secret; + } + } +} diff --git a/Authentication/AuthenticationProvider.cs b/Authentication/AuthenticationProvider.cs new file mode 100644 index 0000000..d10cc3a --- /dev/null +++ b/Authentication/AuthenticationProvider.cs @@ -0,0 +1,25 @@ +using CryptoExchange.Net.Interfaces; + +namespace CryptoExchange.Net.Authentication +{ + public abstract class AuthenticationProvider + { + protected ApiCredentials credentials; + + protected AuthenticationProvider(ApiCredentials credentials) + { + this.credentials = credentials; + } + + public abstract string AddAuthenticationToUriString(string uri); + public abstract IRequest AddAuthenticationToRequest(IRequest request); + + protected string ByteToString(byte[] buff) + { + var sbinary = ""; + foreach (byte t in buff) + sbinary += t.ToString("X2"); /* hex format */ + return sbinary; + } + } +} diff --git a/BaseConverter.cs b/BaseConverter.cs new file mode 100644 index 0000000..af8cb36 --- /dev/null +++ b/BaseConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; + +namespace CryptoExchange.Net +{ + public abstract class BaseConverter: JsonConverter + { + protected abstract Dictionary Mapping { get; } + private readonly bool quotes; + + protected BaseConverter(bool useQuotes) + { + quotes = useQuotes; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (quotes) + writer.WriteValue(Mapping[(T)value]); + else + writer.WriteRawValue(Mapping[(T)value]); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return Mapping.Single(v => v.Value.ToLower() == reader.Value.ToString().ToLower()).Key; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(T); + } + } +} diff --git a/CallResult.cs b/CallResult.cs new file mode 100644 index 0000000..029c68a --- /dev/null +++ b/CallResult.cs @@ -0,0 +1,24 @@ +namespace CryptoExchange.Net +{ + public class CallResult + { + /// + /// The data returned by the call + /// + public T Data { get; internal set; } + /// + /// An error if the call didn't succeed + /// + public Error Error { get; internal set; } + /// + /// Whether the call was successful + /// + public bool Success => Error == null; + + public CallResult(T data, Error error) + { + Data = data; + Error = error; + } + } +} diff --git a/CryptoExchange.Net.csproj b/CryptoExchange.Net.csproj new file mode 100644 index 0000000..7c9bc66 --- /dev/null +++ b/CryptoExchange.Net.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.0 + + + + CryptoExchange.Net + JKorf + 0.0.1 + false + https://github.com/JKorf/CryptoExchange.Net + https://github.com/JKorf/CryptoExchange.Net/blob/master/LICENSE + en + true + + + + + + + diff --git a/Error.cs b/Error.cs new file mode 100644 index 0000000..4309d95 --- /dev/null +++ b/Error.cs @@ -0,0 +1,49 @@ +namespace CryptoExchange.Net +{ + public abstract class Error + { + public int Code { get; set; } + public string Message { get; set; } + + protected Error(int code, string message) + { + Code = code; + Message = message; + } + + public override string ToString() + { + return $"{Code}: {Message}"; + } + } + + public class CantConnectError : Error + { + public CantConnectError() : base(1, "Can't connect to the server") { } + } + + public class NoApiCredentialsError : Error + { + public NoApiCredentialsError() : base(2, "No credentials provided for private endpoint") { } + } + + public class ServerError: Error + { + public ServerError(string message) : base(3, "Server error: " + message) { } + } + + public class WebError : Error + { + public WebError(string message) : base(3, "Web error: " + message) { } + } + + public class DeserializeError : Error + { + public DeserializeError(string message) : base(4, "Error deserializing data: " + message) { } + } + + public class UnknownError : Error + { + public UnknownError(string message) : base(5, "Unknown error occured " + message) { } + } +} diff --git a/ExchangeClient.cs b/ExchangeClient.cs new file mode 100644 index 0000000..bb376dd --- /dev/null +++ b/ExchangeClient.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Threading.Tasks; +using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Logging; +using CryptoExchange.Net.RateLimiter; +using CryptoExchange.Net.Requests; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace CryptoExchange.Net +{ + public abstract class ExchangeClient: IDisposable + { + public IRequestFactory RequestFactory { get; set; } = new RequestFactory(); + + protected Log log; + protected ApiProxy apiProxy; + + private AuthenticationProvider authProvider; + private readonly List rateLimiters = new List(); + + protected ExchangeClient(ExchangeOptions exchangeOptions, AuthenticationProvider authentictationProvider) + { + log = new Log(); + authProvider = authentictationProvider; + Configure(exchangeOptions); + } + + /// + /// Configure the client using the provided options + /// + /// Options + protected void Configure(ExchangeOptions exchangeOptions) + { + log.Level = exchangeOptions.LogVerbosity; + apiProxy = exchangeOptions.Proxy; + + foreach (var rateLimiter in exchangeOptions.RateLimiters) + rateLimiters.Add(rateLimiter); + } + + /// + /// Adds a rate limiter to the client. There are 2 choices, the and the . + /// + /// The limiter to add + public void AddRateLimiter(IRateLimiter limiter) + { + rateLimiters.Add(limiter); + } + + /// + /// Removes all rate limiters from this client + /// + public void RemoveRateLimiters() + { + rateLimiters.Clear(); + } + + /// + /// Set the authentication provider + /// + /// + protected void SetAuthenticationProvider(AuthenticationProvider authentictationProvider) + { + authProvider = authentictationProvider; + } + + protected async Task> ExecuteRequest(Uri uri, string method = "GET", Dictionary parameters = null, bool signed = false) where T : class + { + if(signed && authProvider == null) + return new CallResult(null, new NoApiCredentialsError()); + + var uriString = uri.ToString(); + + if (parameters != null) + { + if (!uriString.EndsWith("?")) + uriString += "?"; + + uriString += $"{string.Join("&", parameters.Select(s => $"{s.Key}={s.Value}"))}"; + } + + if (signed) + uriString = authProvider.AddAuthenticationToUriString(uriString); + + var request = RequestFactory.Create(uriString); + request.Method = method; + + if (apiProxy != null) + request.SetProxy(apiProxy.Host, apiProxy.Port); + + if (signed) + request = authProvider.AddAuthenticationToRequest(request); + + foreach (var limiter in rateLimiters) + { + double limitedBy = limiter.LimitRequest(uri.AbsolutePath); + if (limitedBy > 0) + log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitedBy}ms by {limiter.GetType().Name}"); + } + + log.Write(LogVerbosity.Debug, $"Sending request to {uriString}"); + var result = await ExecuteRequest(request); + if (result.Error != null) + return new CallResult(null, result.Error); + + return Deserialize(result.Data); + } + + private async Task> ExecuteRequest(IRequest request) + { + string returnedData = ""; + try + { + var response = request.GetResponse(); + using (var reader = new StreamReader(response.GetResponseStream())) + { + returnedData = await reader.ReadToEndAsync().ConfigureAwait(false); + return new CallResult(returnedData, null); + } + } + catch (WebException we) + { + var response = (HttpWebResponse)we.Response; + string infoMessage = response == null ? "No response from server" : $"Status: {response.StatusCode}-{response.StatusDescription}, Message: {we.Message}"; + return new CallResult(null, new WebError(infoMessage)); + } + catch (Exception e) + { + return new CallResult(null, new UnknownError(e.Message + ", data: " + returnedData)); + } + } + + private CallResult Deserialize(string data) where T: class + { + try + { + var obj = JToken.Parse(data); + if (log.Level == LogVerbosity.Debug) + { + if (obj is JObject o) + CheckObject(typeof(T), o); + else + CheckObject(typeof(T), (JObject) ((JArray) obj)[0]); + } + + return new CallResult(obj.ToObject(), null); + } + catch (JsonReaderException jre) + { + return new CallResult(null, new DeserializeError($"Error occured at Path: {jre.Path}, LineNumber: {jre.LineNumber}, LinePosition: {jre.LinePosition}. Received data: {data}")); + } + catch (JsonSerializationException jse) + { + return new CallResult(null, new DeserializeError($"Message: {jse.Message}. Received data: {data}")); + } + } + + private void CheckObject(Type type, JObject obj) + { + var properties = new List(); + var props = type.GetProperties(); + foreach (var prop in props) + { + var attr = prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault(); + var ignore = prop.GetCustomAttributes(typeof(JsonIgnoreAttribute), false).FirstOrDefault(); + if (ignore != null) + continue; + + properties.Add(attr == null ? prop.Name.ToLower() : ((JsonPropertyAttribute) attr).PropertyName.ToLower()); + } + foreach (var token in obj) + { + var d = properties.SingleOrDefault(p => p == token.Key.ToLower()); + if (d == null) + log.Write(LogVerbosity.Warning, $"Didn't find property `{token.Key}` in object of type `{type.Name}`"); + else + { + properties.Remove(d); + + var propType = GetProperty(d, props)?.PropertyType; + if (propType == null) + continue; + if (!IsSimple(propType) && propType != typeof(DateTime)) + { + if(propType.IsArray && token.Value.HasValues && ((JArray)token.Value).Any() && ((JArray)token.Value)[0] is JObject) + CheckObject(propType.GetElementType(), (JObject)token.Value[0]); + else if(token.Value is JObject) + CheckObject(propType, (JObject)token.Value); + } + } + } + + foreach(var prop in properties) + log.Write(LogVerbosity.Warning, $"Didn't find key `{prop}` in returned data object of type `{type.Name}`"); + } + + private PropertyInfo GetProperty(string name, PropertyInfo[] props) + { + foreach (var prop in props) + { + var attr = prop.GetCustomAttributes(typeof(JsonPropertyAttribute), false).FirstOrDefault(); + if (attr == null) + { + if (prop.Name.ToLower() == name.ToLower()) + return prop; + } + else + { + if (((JsonPropertyAttribute) attr).PropertyName.ToLower() == name) + return prop; + } + } + return null; + } + + private bool IsSimple(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + // nullable type, check if the nested type is simple. + return IsSimple(type.GetGenericArguments()[0]); + } + return type.IsPrimitive + || type.IsEnum + || type == typeof(string) + || type == typeof(decimal); + } + + public virtual void Dispose() + { + } + } +} diff --git a/ExchangeOptions.cs b/ExchangeOptions.cs new file mode 100644 index 0000000..dca29a1 --- /dev/null +++ b/ExchangeOptions.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.IO; +using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Logging; +using CryptoExchange.Net.RateLimiter; + +namespace CryptoExchange.Net +{ + /// + /// Options + /// + public class ExchangeOptions + { + + /// + /// The api credentials + /// + public ApiCredentials ApiCredentials { get; set; } + + /// + /// Proxy to use + /// + public ApiProxy Proxy { get; set; } + + /// + /// The log verbosity + /// + public LogVerbosity LogVerbosity { get; set; } = LogVerbosity.Warning; + + /// + /// The log writer + /// + public TextWriter LogWriter { get; set; } = new DebugTextWriter(); + + /// + /// List of ratelimiters to use + /// + public List RateLimiters { get; set; } = new List(); + } +} diff --git a/ExtensionMethods.cs b/ExtensionMethods.cs new file mode 100644 index 0000000..83dec83 --- /dev/null +++ b/ExtensionMethods.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace CryptoExchange.Net +{ + public static class ExtensionMethods + { + public static void AddParameter(this Dictionary parameters, string key, string value) + { + parameters.Add(key, value); + } + + public static void AddParameter(this Dictionary parameters, string key, object value) + { + parameters.Add(key, value); + } + + public static void AddOptionalParameter(this Dictionary parameters, string key, object value) + { + if(value != null) + parameters.Add(key, value); + } + + public static void AddOptionalParameter(this Dictionary parameters, string key, string value) + { + if (value != null) + parameters.Add(key, value); + } + } +} diff --git a/Interfaces/IExchangeClient.cs b/Interfaces/IExchangeClient.cs new file mode 100644 index 0000000..0f37f72 --- /dev/null +++ b/Interfaces/IExchangeClient.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using CryptoExchange.Net.RateLimiter; + +namespace CryptoExchange.Net.Interfaces +{ + public interface IExchangeClient + { + IRequestFactory RequestFactory { get; set; } + void AddRateLimiter(IRateLimiter limiter); + void RemoveRateLimiters(); + } +} diff --git a/Interfaces/IRequest.cs b/Interfaces/IRequest.cs new file mode 100644 index 0000000..f17fff3 --- /dev/null +++ b/Interfaces/IRequest.cs @@ -0,0 +1,15 @@ +using System; +using System.Net; + +namespace CryptoExchange.Net.Interfaces +{ + public interface IRequest + { + Uri Uri { get; } + WebHeaderCollection Headers { get; set; } + string Method { get; set; } + + void SetProxy(string host, int port); + IResponse GetResponse(); + } +} diff --git a/Interfaces/IRequestFactory.cs b/Interfaces/IRequestFactory.cs new file mode 100644 index 0000000..1bbf5ac --- /dev/null +++ b/Interfaces/IRequestFactory.cs @@ -0,0 +1,8 @@ + +namespace CryptoExchange.Net.Interfaces +{ + public interface IRequestFactory + { + IRequest Create(string uri); + } +} diff --git a/Interfaces/IResponse.cs b/Interfaces/IResponse.cs new file mode 100644 index 0000000..cdf6da2 --- /dev/null +++ b/Interfaces/IResponse.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace CryptoExchange.Net.Interfaces +{ + public interface IResponse + { + Stream GetResponseStream(); + } +} diff --git a/Logging/DebugTextWriter.cs b/Logging/DebugTextWriter.cs new file mode 100644 index 0000000..9cea25b --- /dev/null +++ b/Logging/DebugTextWriter.cs @@ -0,0 +1,16 @@ +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace CryptoExchange.Net.Logging +{ + public class DebugTextWriter: TextWriter + { + public override Encoding Encoding => Encoding.ASCII; + + public override void WriteLine(string value) + { + Debug.WriteLine(value); + } + } +} diff --git a/Logging/Log.cs b/Logging/Log.cs new file mode 100644 index 0000000..60fdb79 --- /dev/null +++ b/Logging/Log.cs @@ -0,0 +1,25 @@ +using System; +using System.IO; + +namespace CryptoExchange.Net.Logging +{ + public class Log + { + public TextWriter TextWriter { get; internal set; } = new DebugTextWriter(); + public LogVerbosity Level { get; internal set; } = LogVerbosity.Warning; + + public void Write(LogVerbosity logType, string message) + { + if ((int)logType >= (int)Level) + TextWriter.WriteLine($"{DateTime.Now:hh:mm:ss:fff} | {logType} | {message}"); + } + } + + public enum LogVerbosity + { + Debug, + Warning, + Error, + None + } +} diff --git a/RateLimiter/IRateLimiter.cs b/RateLimiter/IRateLimiter.cs new file mode 100644 index 0000000..4a1d5c9 --- /dev/null +++ b/RateLimiter/IRateLimiter.cs @@ -0,0 +1,7 @@ +namespace CryptoExchange.Net.RateLimiter +{ + public interface IRateLimiter + { + double LimitRequest(string url); + } +} diff --git a/RateLimiter/RateLimitObject.cs b/RateLimiter/RateLimitObject.cs new file mode 100644 index 0000000..04efdb6 --- /dev/null +++ b/RateLimiter/RateLimitObject.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CryptoExchange.Net.RateLimiter +{ + public class RateLimitObject + { + public object LockObject { get; } + private List Times { get; } + + public RateLimitObject() + { + LockObject = new object(); + Times = new List(); + } + + public double GetWaitTime(DateTime time, int limit, TimeSpan perTimePeriod) + { + Times.RemoveAll(d => d < time - perTimePeriod); + if (Times.Count >= limit) + return (Times.First() - (time - perTimePeriod)).TotalMilliseconds; + return 0; + } + + public void Add(DateTime time) + { + Times.Add(time); + Times.Sort(); + } + } +} diff --git a/RateLimiter/RateLimiterPerEndpoint.cs b/RateLimiter/RateLimiterPerEndpoint.cs new file mode 100644 index 0000000..2586a1e --- /dev/null +++ b/RateLimiter/RateLimiterPerEndpoint.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; + +namespace CryptoExchange.Net.RateLimiter +{ + /// + /// Limits the amount of requests per time period to a certain limit, counts the request per endpoint. + /// + public class RateLimiterPerEndpoint: IRateLimiter + { + internal Dictionary history = new Dictionary(); + + private int limitPerEndpoint; + private TimeSpan perTimePeriod; + private object historyLock = new object(); + + /// + /// Create a new RateLimiterPerEndpoint. This rate limiter limits the amount of requests per time period to a certain limit, counts the request per endpoint. + /// + /// The amount to limit to + /// The time period over which the limit counts + public RateLimiterPerEndpoint(int limitPerEndpoint, TimeSpan perTimePeriod) + { + this.limitPerEndpoint = limitPerEndpoint; + this.perTimePeriod = perTimePeriod; + } + + public double LimitRequest(string url) + { + double waitTime; + RateLimitObject rlo; + lock (historyLock) + { + if (history.ContainsKey(url)) + rlo = history[url]; + else + { + rlo = new RateLimitObject(); + history.Add(url, rlo); + } + } + + var sw = Stopwatch.StartNew(); + lock (rlo.LockObject) + { + sw.Stop(); + waitTime = rlo.GetWaitTime(DateTime.UtcNow, limitPerEndpoint, perTimePeriod); + if (waitTime != 0) + { + Thread.Sleep(Convert.ToInt32(waitTime)); + waitTime += sw.ElapsedMilliseconds; + } + + rlo.Add(DateTime.UtcNow); + } + + return waitTime; + } + } +} diff --git a/RateLimiter/RateLimiterTotal.cs b/RateLimiter/RateLimiterTotal.cs new file mode 100644 index 0000000..67f72c4 --- /dev/null +++ b/RateLimiter/RateLimiterTotal.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace CryptoExchange.Net.RateLimiter +{ + /// + /// Limits the amount of requests per time period to a certain limit, counts the total amount of requests. + /// + public class RateLimiterTotal: IRateLimiter + { + internal List history = new List(); + + private int limit; + private TimeSpan perTimePeriod; + private object requestLock = new object(); + + /// + /// Create a new RateLimiterTotal. This rate limiter limits the amount of requests per time period to a certain limit, counts the total amount of requests. + /// + /// The amount to limit to + /// The time period over which the limit counts + public RateLimiterTotal(int limit, TimeSpan perTimePeriod) + { + this.limit = limit; + this.perTimePeriod = perTimePeriod; + } + + public double LimitRequest(string url) + { + var sw = Stopwatch.StartNew(); + lock (requestLock) + { + sw.Stop(); + double waitTime = 0; + var checkTime = DateTime.UtcNow; + history.RemoveAll(d => d < checkTime - perTimePeriod); + + if (history.Count >= limit) + { + waitTime = (history.First() - (checkTime - perTimePeriod)).TotalMilliseconds; + if (waitTime > 0) + { + Thread.Sleep(Convert.ToInt32(waitTime)); + waitTime += sw.ElapsedMilliseconds; + } + } + + history.Add(DateTime.UtcNow); + history.Sort(); + return waitTime; + } + } + } +} diff --git a/Requests/Request.cs b/Requests/Request.cs new file mode 100644 index 0000000..b4ac071 --- /dev/null +++ b/Requests/Request.cs @@ -0,0 +1,41 @@ +using System; +using System.Net; +using CryptoExchange.Net.Interfaces; + +namespace CryptoExchange.Net.Requests +{ + public class Request : IRequest + { + private readonly WebRequest request; + + public Request(WebRequest request) + { + this.request = request; + } + + public WebHeaderCollection Headers + { + get => request.Headers; + set => request.Headers = value; + } + public string Method + { + get => request.Method; + set => request.Method = value; + } + public Uri Uri + { + get => request.RequestUri; + } + + public void SetProxy(string host, int port) + { + request.Proxy = new WebProxy(host, port); ; + } + + public IResponse GetResponse() + { + return new Response(request.GetResponse()); + } + } +} diff --git a/Requests/RequestFactory.cs b/Requests/RequestFactory.cs new file mode 100644 index 0000000..4469293 --- /dev/null +++ b/Requests/RequestFactory.cs @@ -0,0 +1,13 @@ +using System.Net; +using CryptoExchange.Net.Interfaces; + +namespace CryptoExchange.Net.Requests +{ + public class RequestFactory : IRequestFactory + { + public IRequest Create(string uri) + { + return new Request(WebRequest.Create(uri)); + } + } +} diff --git a/Requests/Response.cs b/Requests/Response.cs new file mode 100644 index 0000000..f4da0af --- /dev/null +++ b/Requests/Response.cs @@ -0,0 +1,21 @@ +using System.IO; +using System.Net; +using CryptoExchange.Net.Interfaces; + +namespace CryptoExchange.Net.Requests +{ + public class Response : IResponse + { + private readonly WebResponse response; + + public Response(WebResponse response) + { + this.response = response; + } + + public Stream GetResponseStream() + { + return response.GetResponseStream(); + } + } +}