@@ -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) => | |||
{ | |||
@@ -532,9 +532,15 @@ | |||
<Compile Include="..\Discord.Net\Net\Rest\CompletedRequestEventArgs.cs"> | |||
<Link>Net\Rest\CompletedRequestEventArgs.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Net\Rest\ETFRestClient.cs"> | |||
<Link>Net\Rest\ETFRestClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Net\Rest\IRestEngine.cs"> | |||
<Link>Net\Rest\IRestEngine.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Net\Rest\JsonRestClient.cs"> | |||
<Link>Net\Rest\JsonRestClient.cs</Link> | |||
</Compile> | |||
<Compile Include="..\Discord.Net\Net\Rest\RequestEventArgs.cs"> | |||
<Link>Net\Rest\RequestEventArgs.cs</Link> | |||
</Compile> | |||
@@ -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) => | |||
{ | |||
@@ -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<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) | |||
{ | |||
if (obj != null) | |||
@@ -302,9 +343,13 @@ 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 | |||
if (isObject && typeInfo.IsValueType) | |||
@@ -314,9 +359,13 @@ 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 | |||
if (isObject && typeInfo.IsValueType) | |||
@@ -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; | |||
@@ -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.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<ResponseT>(results.Response); | |||
var response = Deserialize<ResponseT>(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<ResponseT>(results.Response); | |||
var response = Deserialize<ResponseT>(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<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); | |||
} | |||
} |