diff --git a/src/Discord.Net.Audio/AudioClient.cs b/src/Discord.Net.Audio/AudioClient.cs index 279557029..c7d9d182d 100644 --- a/src/Discord.Net.Audio/AudioClient.cs +++ b/src/Discord.Net.Audio/AudioClient.cs @@ -87,7 +87,7 @@ namespace Discord.Audio //Networking if (Config.EnableMultiserver) { - ClientAPI = new RestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}")); + ClientAPI = new JsonRestClient(_config, DiscordConfig.ClientAPIUrl, client.Log.CreateLogger($"ClientAPI #{id}")); GatewaySocket = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}")); GatewaySocket.Connected += (s, e) => { diff --git a/src/Discord.Net.Net45/Discord.Net.csproj b/src/Discord.Net.Net45/Discord.Net.csproj index 0dce8fc1c..665efdb95 100644 --- a/src/Discord.Net.Net45/Discord.Net.csproj +++ b/src/Discord.Net.Net45/Discord.Net.csproj @@ -532,9 +532,15 @@ Net\Rest\CompletedRequestEventArgs.cs + + Net\Rest\ETFRestClient.cs + Net\Rest\IRestEngine.cs + + Net\Rest\JsonRestClient.cs + Net\Rest\RequestEventArgs.cs diff --git a/src/Discord.Net/DiscordClient.cs b/src/Discord.Net/DiscordClient.cs index 46f740763..5d929a581 100644 --- a/src/Discord.Net/DiscordClient.cs +++ b/src/Discord.Net/DiscordClient.cs @@ -135,8 +135,8 @@ namespace Discord }; //Networking - ClientAPI = new RestClient(Config, DiscordConfig.ClientAPIUrl, Log.CreateLogger("ClientAPI")); - StatusAPI = new RestClient(Config, DiscordConfig.StatusAPIUrl, Log.CreateLogger("StatusAPI")); + ClientAPI = new JsonRestClient(Config, DiscordConfig.ClientAPIUrl, Log.CreateLogger("ClientAPI")); + StatusAPI = new JsonRestClient(Config, DiscordConfig.StatusAPIUrl, Log.CreateLogger("StatusAPI")); GatewaySocket = new GatewaySocket(Config, Serializer, Log.CreateLogger("Gateway")); GatewaySocket.Connected += (s, e) => { diff --git a/src/Discord.Net/ETF/ETFWriter.cs b/src/Discord.Net/ETF/ETFWriter.cs index 06641e664..6222d6427 100644 --- a/src/Discord.Net/ETF/ETFWriter.cs +++ b/src/Discord.Net/ETF/ETFWriter.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -12,6 +13,7 @@ namespace Discord.ETF public unsafe class ETFWriter : IDisposable { private readonly static byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; + private readonly static byte[] _nilExtBytes = new byte[] { (byte)ETFType.NIL_EXT}; private readonly static byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; private readonly static byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; @@ -238,6 +240,45 @@ namespace Discord.ETF else WriteNil(); } + public void Write(IEnumerable obj) + { + if (obj != null) + { + var array = obj.ToArray(); + int length = array.Length; + _buffer[0] = (byte)ETFType.LIST_EXT; + _buffer[1] = (byte)((length >> 24) & 0xFF); + _buffer[2] = (byte)((length >> 16) & 0xFF); + _buffer[3] = (byte)((length >> 8) & 0xFF); + _buffer[4] = (byte)(length & 0xFF); + for (int i = 0; i < array.Length; i++) + Write(array[i]); + WriteNilExt(); + _stream.Write(_buffer, 0, 5); + } + else + WriteNil(); + } + public void Write(IDictionary obj) + { + if (obj != null) + { + int length = obj.Count; + _buffer[0] = (byte)ETFType.MAP_EXT; + _buffer[1] = (byte)((length >> 24) & 0xFF); + _buffer[2] = (byte)((length >> 16) & 0xFF); + _buffer[3] = (byte)((length >> 8) & 0xFF); + _buffer[4] = (byte)(length & 0xFF); + foreach (var pair in obj) + { + Write(pair.Key); + Write(pair.Value); + } + _stream.Write(_buffer, 0, 5); + } + else + WriteNil(); + } public void Write(object obj) { if (obj != null) @@ -302,8 +343,12 @@ namespace Discord.ETF //TODO: Add field/property names typeInfo.ForEachField(f => { - if (!f.IsPublic) return; + string name; + if (!f.IsPublic || !IsETFProperty(f, out name)) return; + generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) + generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue @@ -314,8 +359,12 @@ namespace Discord.ETF typeInfo.ForEachProperty(p => { - if (!p.CanRead || !p.GetMethod.IsPublic) return; + string name; + if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; + generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) + generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name + generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(this), obj.propValue @@ -400,6 +449,30 @@ namespace Discord.ETF } private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length); + private void WriteNilExt() => _stream.Write(_nilExtBytes, 0, _nilExtBytes.Length); + + private bool IsETFProperty(FieldInfo f, out string name) + { + var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); + if (attrib != null) + { + name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? f.Name; + return true; + } + name = null; + return false; + } + private bool IsETFProperty(PropertyInfo p, out string name) + { + var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); + if (attrib != null) + { + name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? p.Name; + return true; + } + name = null; + return false; + } #region IDisposable private bool _isDisposed = false; diff --git a/src/Discord.Net/Net/Rest/ETFRestClient.cs b/src/Discord.Net/Net/Rest/ETFRestClient.cs new file mode 100644 index 000000000..b52da1037 --- /dev/null +++ b/src/Discord.Net/Net/Rest/ETFRestClient.cs @@ -0,0 +1,27 @@ +using Discord.ETF; +using System.IO; +using System; +using Discord.Logging; + +namespace Discord.Net.Rest +{ + public class ETFRestClient : RestClient + { + private readonly ETFWriter _serializer; + + public ETFRestClient(DiscordConfig config, string baseUrl, Logger logger) + : base(config, baseUrl, logger) + { + _serializer = new ETFWriter(new MemoryStream()); + } + + protected override string Serialize(T obj) + { + throw new NotImplementedException(); + } + protected override T Deserialize(string json) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Discord.Net/Net/Rest/JsonRestClient.cs b/src/Discord.Net/Net/Rest/JsonRestClient.cs new file mode 100644 index 000000000..37bb093ab --- /dev/null +++ b/src/Discord.Net/Net/Rest/JsonRestClient.cs @@ -0,0 +1,37 @@ +using Discord.Logging; +using Newtonsoft.Json; + +namespace Discord.Net.Rest +{ + public class JsonRestClient : RestClient + { + private JsonSerializerSettings _deserializeSettings; + + public JsonRestClient(DiscordConfig config, string baseUrl, Logger logger) + : base(config, baseUrl, logger) + { + _deserializeSettings = new JsonSerializerSettings(); +#if TEST_RESPONSES + _deserializeSettings.CheckAdditionalContent = true; + _deserializeSettings.MissingMemberHandling = MissingMemberHandling.Error; +#else + _deserializeSettings.CheckAdditionalContent = false; + _deserializeSettings.MissingMemberHandling = MissingMemberHandling.Ignore; +#endif + } + + protected override string Serialize(T obj) + { + return JsonConvert.SerializeObject(obj); + } + + protected override T Deserialize(string json) + { +#if TEST_RESPONSES + if (string.IsNullOrEmpty(json)) + throw new Exception("API check failed: Response is empty."); +#endif + return JsonConvert.DeserializeObject(json, _deserializeSettings); + } + } +} diff --git a/src/Discord.Net/Net/Rest/RestClient.cs b/src/Discord.Net/Net/Rest/RestClient.cs index 8e5ce180c..a32771e0d 100644 --- a/src/Discord.Net/Net/Rest/RestClient.cs +++ b/src/Discord.Net/Net/Rest/RestClient.cs @@ -1,14 +1,15 @@ using Discord.API; +using Discord.ETF; using Discord.Logging; -using Newtonsoft.Json; using System; using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; namespace Discord.Net.Rest { - public partial class RestClient + public abstract partial class RestClient { private struct RestResults { @@ -32,8 +33,8 @@ namespace Discord.Net.Rest private readonly DiscordConfig _config; private readonly IRestEngine _engine; + private readonly ETFWriter _serializer; private string _token; - private JsonSerializerSettings _deserializeSettings; internal Logger Logger { get; } @@ -49,26 +50,17 @@ namespace Discord.Net.Rest } } - public RestClient(DiscordConfig config, string baseUrl, Logger logger) + protected RestClient(DiscordConfig config, string baseUrl, Logger logger) { _config = config; Logger = logger; #if !DOTNET5_4 - _engine = new RestSharpEngine(config, baseUrl, logger); + _engine = new RestSharpEngine(config, baseUrl, logger); #else _engine = new BuiltInEngine(config, baseUrl, logger); #endif - _deserializeSettings = new JsonSerializerSettings(); -#if TEST_RESPONSES - _deserializeSettings.CheckAdditionalContent = true; - _deserializeSettings.MissingMemberHandling = MissingMemberHandling.Error; -#else - _deserializeSettings.CheckAdditionalContent = false; - _deserializeSettings.MissingMemberHandling = MissingMemberHandling.Ignore; -#endif - if (Logger.Level >= LogSeverity.Verbose) { this.SentRequest += (s, e) => @@ -98,7 +90,7 @@ namespace Discord.Net.Rest OnSendingRequest(request); var results = await Send(request, true).ConfigureAwait(false); - var response = DeserializeResponse(results.Response); + var response = Deserialize(results.Response); OnSentRequest(request, response, results.Response, results.Milliseconds); return response; @@ -118,9 +110,8 @@ namespace Discord.Net.Rest if (request == null) throw new ArgumentNullException(nameof(request)); OnSendingRequest(request); - var requestJson = JsonConvert.SerializeObject(request.Payload); var results = await SendFile(request, true).ConfigureAwait(false); - var response = DeserializeResponse(results.Response); + var response = Deserialize(results.Response); OnSentRequest(request, response, results.Response, results.Milliseconds); return response; @@ -139,7 +130,7 @@ namespace Discord.Net.Rest object payload = request.Payload; string requestJson = null; if (payload != null) - requestJson = JsonConvert.SerializeObject(payload); + requestJson = Serialize(payload); Stopwatch stopwatch = Stopwatch.StartNew(); string responseJson = await _engine.Send(request.Method, request.Endpoint, requestJson, CancelToken).ConfigureAwait(false); @@ -159,13 +150,7 @@ namespace Discord.Net.Rest return new RestResults(responseJson, milliseconds); } - private T DeserializeResponse(string json) - { -#if TEST_RESPONSES - if (string.IsNullOrEmpty(json)) - throw new Exception("API check failed: Response is empty."); -#endif - return JsonConvert.DeserializeObject(json, _deserializeSettings); - } + protected abstract string Serialize(T obj); + protected abstract T Deserialize(string json); } }