| @@ -87,7 +87,7 @@ namespace Discord.Audio | |||||
| //Networking | //Networking | ||||
| if (Config.EnableMultiserver) | 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 = new GatewaySocket(_config, client.Serializer, client.Log.CreateLogger($"Gateway #{id}")); | ||||
| GatewaySocket.Connected += (s, e) => | GatewaySocket.Connected += (s, e) => | ||||
| { | { | ||||
| @@ -532,9 +532,15 @@ | |||||
| <Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs"> | <Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs"> | ||||
| <Link>Net\Rest\CompletedRequestEventArgs.cs</Link> | <Link>Net\Rest\CompletedRequestEventArgs.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Net\Rest\ETFRestClient.cs"> | |||||
| <Link>Net\Rest\ETFRestClient.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | <Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | ||||
| <Link>Net\Rest\IRestEngine.cs</Link> | <Link>Net\Rest\IRestEngine.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| <Compile Include="..\Discord.Net\Net\Rest\JsonRestClient.cs"> | |||||
| <Link>Net\Rest\JsonRestClient.cs</Link> | |||||
| </Compile> | |||||
| <Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> | <Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> | ||||
| <Link>Net\Rest\RequestEventArgs.cs</Link> | <Link>Net\Rest\RequestEventArgs.cs</Link> | ||||
| </Compile> | </Compile> | ||||
| @@ -135,8 +135,8 @@ namespace Discord | |||||
| }; | }; | ||||
| //Networking | //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 = new GatewaySocket(Config, Serializer, Log.CreateLogger("Gateway")); | ||||
| GatewaySocket.Connected += (s, e) => | GatewaySocket.Connected += (s, e) => | ||||
| { | { | ||||
| @@ -1,4 +1,5 @@ | |||||
| using System; | |||||
| using Newtonsoft.Json; | |||||
| using System; | |||||
| using System.Collections.Concurrent; | using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||
| using System.IO; | using System.IO; | ||||
| @@ -12,6 +13,7 @@ namespace Discord.ETF | |||||
| public unsafe class ETFWriter : IDisposable | 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[] _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[] _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' }; | 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 | else | ||||
| WriteNil(); | WriteNil(); | ||||
| } | } | ||||
| public void Write<T>(IEnumerable<T> 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<TKey, TValue>(IDictionary<TKey, TValue> 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) | public void Write(object obj) | ||||
| { | { | ||||
| if (obj != null) | if (obj != null) | ||||
| @@ -302,8 +343,12 @@ namespace Discord.ETF | |||||
| //TODO: Add field/property names | //TODO: Add field/property names | ||||
| typeInfo.ForEachField(f => | 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_0); //ETFWriter(this) | ||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | ||||
| generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue | generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue | ||||
| @@ -314,8 +359,12 @@ namespace Discord.ETF | |||||
| typeInfo.ForEachProperty(p => | 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_0); //ETFWriter(this) | ||||
| generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj | ||||
| generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(this), obj.propValue | 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 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 | #region IDisposable | ||||
| private bool _isDisposed = false; | private bool _isDisposed = false; | ||||
| @@ -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>(T obj) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| protected override T Deserialize<T>(string json) | |||||
| { | |||||
| throw new NotImplementedException(); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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>(T obj) | |||||
| { | |||||
| return JsonConvert.SerializeObject(obj); | |||||
| } | |||||
| protected override T Deserialize<T>(string json) | |||||
| { | |||||
| #if TEST_RESPONSES | |||||
| if (string.IsNullOrEmpty(json)) | |||||
| throw new Exception("API check failed: Response is empty."); | |||||
| #endif | |||||
| return JsonConvert.DeserializeObject<T>(json, _deserializeSettings); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,14 +1,15 @@ | |||||
| using Discord.API; | using Discord.API; | ||||
| using Discord.ETF; | |||||
| using Discord.Logging; | using Discord.Logging; | ||||
| using Newtonsoft.Json; | |||||
| using System; | using System; | ||||
| using System.Diagnostics; | using System.Diagnostics; | ||||
| using System.IO; | |||||
| using System.Threading; | using System.Threading; | ||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
| namespace Discord.Net.Rest | namespace Discord.Net.Rest | ||||
| { | { | ||||
| public partial class RestClient | |||||
| public abstract partial class RestClient | |||||
| { | { | ||||
| private struct RestResults | private struct RestResults | ||||
| { | { | ||||
| @@ -32,8 +33,8 @@ namespace Discord.Net.Rest | |||||
| private readonly DiscordConfig _config; | private readonly DiscordConfig _config; | ||||
| private readonly IRestEngine _engine; | private readonly IRestEngine _engine; | ||||
| private readonly ETFWriter _serializer; | |||||
| private string _token; | private string _token; | ||||
| private JsonSerializerSettings _deserializeSettings; | |||||
| internal Logger Logger { get; } | 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; | _config = config; | ||||
| Logger = logger; | Logger = logger; | ||||
| #if !DOTNET5_4 | #if !DOTNET5_4 | ||||
| _engine = new RestSharpEngine(config, baseUrl, logger); | |||||
| _engine = new RestSharpEngine(config, baseUrl, logger); | |||||
| #else | #else | ||||
| _engine = new BuiltInEngine(config, baseUrl, logger); | _engine = new BuiltInEngine(config, baseUrl, logger); | ||||
| #endif | #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) | if (Logger.Level >= LogSeverity.Verbose) | ||||
| { | { | ||||
| this.SentRequest += (s, e) => | this.SentRequest += (s, e) => | ||||
| @@ -98,7 +90,7 @@ namespace Discord.Net.Rest | |||||
| OnSendingRequest(request); | OnSendingRequest(request); | ||||
| var results = await Send(request, true).ConfigureAwait(false); | var results = await Send(request, true).ConfigureAwait(false); | ||||
| var response = DeserializeResponse<ResponseT>(results.Response); | |||||
| var response = Deserialize<ResponseT>(results.Response); | |||||
| OnSentRequest(request, response, results.Response, results.Milliseconds); | OnSentRequest(request, response, results.Response, results.Milliseconds); | ||||
| return response; | return response; | ||||
| @@ -118,9 +110,8 @@ namespace Discord.Net.Rest | |||||
| if (request == null) throw new ArgumentNullException(nameof(request)); | if (request == null) throw new ArgumentNullException(nameof(request)); | ||||
| OnSendingRequest(request); | OnSendingRequest(request); | ||||
| var requestJson = JsonConvert.SerializeObject(request.Payload); | |||||
| var results = await SendFile(request, true).ConfigureAwait(false); | var results = await SendFile(request, true).ConfigureAwait(false); | ||||
| var response = DeserializeResponse<ResponseT>(results.Response); | |||||
| var response = Deserialize<ResponseT>(results.Response); | |||||
| OnSentRequest(request, response, results.Response, results.Milliseconds); | OnSentRequest(request, response, results.Response, results.Milliseconds); | ||||
| return response; | return response; | ||||
| @@ -139,7 +130,7 @@ namespace Discord.Net.Rest | |||||
| object payload = request.Payload; | object payload = request.Payload; | ||||
| string requestJson = null; | string requestJson = null; | ||||
| if (payload != null) | if (payload != null) | ||||
| requestJson = JsonConvert.SerializeObject(payload); | |||||
| requestJson = Serialize(payload); | |||||
| Stopwatch stopwatch = Stopwatch.StartNew(); | Stopwatch stopwatch = Stopwatch.StartNew(); | ||||
| string responseJson = await _engine.Send(request.Method, request.Endpoint, requestJson, CancelToken).ConfigureAwait(false); | 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); | return new RestResults(responseJson, milliseconds); | ||||
| } | } | ||||
| private T DeserializeResponse<T>(string json) | |||||
| { | |||||
| #if TEST_RESPONSES | |||||
| if (string.IsNullOrEmpty(json)) | |||||
| throw new Exception("API check failed: Response is empty."); | |||||
| #endif | |||||
| return JsonConvert.DeserializeObject<T>(json, _deserializeSettings); | |||||
| } | |||||
| protected abstract string Serialize<T>(T obj); | |||||
| protected abstract T Deserialize<T>(string json); | |||||
| } | } | ||||
| } | } | ||||