diff --git a/src/Rin.Storage.Redis/JsonConverters.cs b/src/Rin.Storage.Redis/JsonConverters.cs index e99b61c..ed5e760 100644 --- a/src/Rin.Storage.Redis/JsonConverters.cs +++ b/src/Rin.Storage.Redis/JsonConverters.cs @@ -1,127 +1,176 @@ using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; using Rin.Core.Record; -using Newtonsoft.Json.Linq; using Microsoft.Extensions.Primitives; namespace Rin.Storage.Redis { + internal class TimeSpanJsonConverter : JsonConverter + { + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return TimeSpan.FromTicks(reader.GetInt64()); + } + + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value.Ticks, options); + } + } + internal class IPAddressJsonConverter : JsonConverter { - public override IPAddress ReadJson(JsonReader reader, Type objectType, IPAddress existingValue, bool hasExistingValue, JsonSerializer serializer) + public override IPAddress Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var value = reader.Value as string; - if (!String.IsNullOrEmpty(value)) + var value = reader.GetString(); + if (!string.IsNullOrEmpty(value)) { return IPAddress.Parse(value); } - return existingValue; + return default; } - public override void WriteJson(JsonWriter writer, IPAddress value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, IPAddress value, JsonSerializerOptions options) { - writer.WriteValue(value.ToString()); + writer.WriteStringValue(value.ToString()); } } internal class QueryStringJsonConverter : JsonConverter { - public override QueryString ReadJson(JsonReader reader, Type objectType, QueryString existingValue, bool hasExistingValue, JsonSerializer serializer) + public override QueryString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var value = reader.Value as string; + var value = reader.GetString(); return new QueryString(value ?? ""); } - public override void WriteJson(JsonWriter writer, QueryString value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, QueryString value, JsonSerializerOptions options) { - writer.WriteValue(value.ToString()); + writer.WriteStringValue(value.ToString()); } } - internal class PathStringJsonConverter : JsonConverter + internal class PathStringJsonConverter : JsonConverter { - public override PathString ReadJson(JsonReader reader, Type objectType, PathString existingValue, bool hasExistingValue, JsonSerializer serializer) + public override PathString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var value = reader.Value as string; + var value = reader.GetString(); return new PathString(value ?? ""); } - public override void WriteJson(JsonWriter writer, PathString value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, PathString value, JsonSerializerOptions options) { - writer.WriteValue(value.ToString()); + writer.WriteStringValue(value.ToString()); } } - internal class HostStringJsonConverter : JsonConverter + internal class HostStringJsonConverter : JsonConverter { - public override HostString ReadJson(JsonReader reader, Type objectType, HostString existingValue, bool hasExistingValue, JsonSerializer serializer) + public override HostString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var value = reader.Value as string; + var value = reader.GetString(); return new HostString(value ?? ""); } - public override void WriteJson(JsonWriter writer, HostString value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, HostString value, JsonSerializerOptions options) { - writer.WriteValue(value.ToString()); + writer.WriteStringValue(value.ToString()); } } internal class StringValuesJsonConverter : JsonConverter { - public override StringValues ReadJson(JsonReader reader, Type objectType, StringValues existingValue, bool hasExistingValue, JsonSerializer serializer) + public override StringValues Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var value = JArray.ReadFrom(reader).ToObject(); - return new StringValues(value ?? Array.Empty()); + return new StringValues(JsonSerializer.Deserialize(ref reader, options)); } - public override void WriteJson(JsonWriter writer, StringValues value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, StringValues value, JsonSerializerOptions options) { - serializer.Serialize(writer, value.ToArray()); + JsonSerializer.Serialize(writer, value.ToArray(), options); } } - internal class TimelineEventJsonConverter : JsonConverter + internal class TimelineEventJsonConverter : JsonConverter { - public override bool CanConvert(Type objectType) - { - return (objectType == typeof(ITimelineEvent) || objectType == typeof(ITimelineScope) || objectType == typeof(ITimelineStamp)); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override ITimelineEvent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var item = JToken.ReadFrom(reader); - if (item.Type == JTokenType.Null) return null; + var readerTmp = reader; + var timelineEvent = JsonSerializer.Deserialize(ref readerTmp, options); - switch ((string)item["EventType"]) + switch (timelineEvent.EventType) { case nameof(TimelineScope): - return item.ToObject(serializer); + return JsonSerializer.Deserialize(ref reader, options); case nameof(TimelineStamp): - return item.ToObject(serializer); + return JsonSerializer.Deserialize(ref reader, options); default: throw new NotSupportedException(); } } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void Write(Utf8JsonWriter writer, ITimelineEvent value, JsonSerializerOptions options) + { + if (value is ITimelineScope timelineScope) + { + JsonSerializer.Serialize(writer, timelineScope, options); + } + else if (value is ITimelineStamp timelineStamp) + { + JsonSerializer.Serialize(writer, timelineStamp, options); + } + else + { + throw new NotSupportedException(); + } + } + } + + internal class TimelineScopeJsonConverter : JsonConverter + { + public override ITimelineScope Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + public override void Write(Utf8JsonWriter writer, ITimelineScope value, JsonSerializerOptions options) { - serializer.Serialize(writer, value); + JsonSerializer.Serialize(writer, new TimelineScope_(value), options); } } + internal class TimelineStampJsonConverter : JsonConverter + { + public override ITimelineStamp Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + public override void Write(Utf8JsonWriter writer, ITimelineStamp value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, new TimelineStamp_(value), options); + } + } + + internal class TimelineEvent_ + { + public string EventType { get; set; } + } + internal class TimelineScope_ : ITimelineScope { + public string EventType { get; set; } + public TimeSpan Duration { get; set; } public IReadOnlyCollection Children { get; set; } - public string EventType { get; set; } - public string Name { get; set; } public string Category { get; set; } public string Data { get; set; } @@ -134,6 +183,20 @@ public void Complete() public void Dispose() { } + + public TimelineScope_() + {} + + public TimelineScope_(ITimelineScope timelineScope) + { + EventType = timelineScope.EventType; + Duration = timelineScope.Duration; + Children = timelineScope.Children; + Name = timelineScope.Name; + Category = timelineScope.Category; + Data = timelineScope.Data; + Timestamp = timelineScope.Timestamp; + } } internal class TimelineStamp_ : ITimelineStamp @@ -144,5 +207,17 @@ internal class TimelineStamp_ : ITimelineStamp public string Category { get; set; } public string Data { get; set; } public DateTimeOffset Timestamp { get; set; } + + public TimelineStamp_() + {} + + public TimelineStamp_(ITimelineStamp timelineScope) + { + EventType = timelineScope.EventType; + Name = timelineScope.Name; + Category = timelineScope.Category; + Data = timelineScope.Data; + Timestamp = timelineScope.Timestamp; + } } } diff --git a/src/Rin.Storage.Redis/RedisRecordStorage.cs b/src/Rin.Storage.Redis/RedisRecordStorage.cs index 213f48d..3e1feb2 100644 --- a/src/Rin.Storage.Redis/RedisRecordStorage.cs +++ b/src/Rin.Storage.Redis/RedisRecordStorage.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; using Rin.Core; using Rin.Core.Event; using Rin.Core.Record; @@ -7,6 +6,7 @@ using StackExchange.Redis; using System; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Options; @@ -17,7 +17,7 @@ namespace Rin.Storage.Redis /// public class RedisRecordStorage : IRecordStorage { - private static readonly JsonSerializerSettings _jsonSerializerSettings; + private static readonly JsonSerializerOptions _jsonSerializerOptions; private static readonly string _serializeVersion; private const string RedisSubscriptionKey = "RedisRecordStorage-Subscription"; @@ -31,15 +31,18 @@ public class RedisRecordStorage : IRecordStorage static RedisRecordStorage() { - _jsonSerializerSettings = new JsonSerializerSettings(); - _jsonSerializerSettings.Converters.Add(new StringValuesJsonConverter()); - _jsonSerializerSettings.Converters.Add(new IPAddressJsonConverter()); - _jsonSerializerSettings.Converters.Add(new QueryStringJsonConverter()); - _jsonSerializerSettings.Converters.Add(new PathStringJsonConverter()); - _jsonSerializerSettings.Converters.Add(new HostStringJsonConverter()); - _jsonSerializerSettings.Converters.Add(new TimelineEventJsonConverter()); - - _serializeVersion = typeof(Rin.Core.Record.HttpRequestRecord).Assembly.GetName().Version.ToString(); + _jsonSerializerOptions = new JsonSerializerOptions(); + _jsonSerializerOptions.Converters.Add(new TimeSpanJsonConverter()); + _jsonSerializerOptions.Converters.Add(new StringValuesJsonConverter()); + _jsonSerializerOptions.Converters.Add(new IPAddressJsonConverter()); + _jsonSerializerOptions.Converters.Add(new QueryStringJsonConverter()); + _jsonSerializerOptions.Converters.Add(new PathStringJsonConverter()); + _jsonSerializerOptions.Converters.Add(new HostStringJsonConverter()); + _jsonSerializerOptions.Converters.Add(new TimelineScopeJsonConverter()); + _jsonSerializerOptions.Converters.Add(new TimelineStampJsonConverter()); + _jsonSerializerOptions.Converters.Add(new TimelineEventJsonConverter()); + + _serializeVersion = typeof(Rin.Core.Record.HttpRequestRecord).Assembly.GetName().Version!.ToString(); } public RedisRecordStorage(IOptions options, IOptions rinOptions, IMessageEventBus eventBus) @@ -176,14 +179,14 @@ public void Dispose() private string Serialize(T value) { - return JsonConvert.SerializeObject(value, _jsonSerializerSettings); + return JsonSerializer.Serialize(value, _jsonSerializerOptions); } private T Deserialize(string value) { if (value == null) return default(T); - var result = JsonConvert.DeserializeObject(value, _jsonSerializerSettings); + var result = JsonSerializer.Deserialize(value, _jsonSerializerOptions); return result; } } diff --git a/src/Rin/Channel/HubDispatcher.cs b/src/Rin/Channel/HubDispatcher.cs deleted file mode 100644 index 8fd5426..0000000 --- a/src/Rin/Channel/HubDispatcher.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading.Tasks; - -namespace Rin.Channel -{ - internal class HubDispatcher where T: IHub - { - private static Dictionary>> _methodMap = new Dictionary>>(); - - static HubDispatcher() - { - var type = typeof(T); - - foreach (var methodInfo in type.GetMethods().Where(x => x.DeclaringType == type && x.DeclaringType != typeof(object))) - { - _methodMap[methodInfo.Name] = CreateFunctionProxyFromInstanceMethod(methodInfo); - } - } - - public static Task InvokeAsync(string name, T thisArg, JToken[]? args) - { - return _methodMap[name](thisArg, args); - } - - public static bool CanInvoke(string name) - { - return _methodMap.ContainsKey(name); - } - - private static Func> CreateFunctionProxyFromInstanceMethod(MethodInfo methodInfo) - { - var methodInfoOfAsTask = (methodInfo.ReturnType.BaseType == typeof(Task)) - ? typeof(HubDispatcher).GetMethod(nameof(AsTaskOfObject), BindingFlags.Static | BindingFlags.NonPublic)!.MakeGenericMethod(methodInfo.ReturnType.GenericTypeArguments[0]) - : typeof(HubDispatcher).GetMethod(nameof(AsTaskFromResult), BindingFlags.Static | BindingFlags.NonPublic)!.MakeGenericMethod(methodInfo.ReturnType); - var methodInfoToObject = typeof(JObject).GetMethod(nameof(JObject.ToObject), new[] { typeof(Type) }); - - var thisType = typeof(T); - var methodParams = methodInfo.GetParameters(); - var thisArg = Expression.Parameter(typeof(object)); - var args = Expression.Parameter(typeof(JToken[])); - - var lambdaParams = methodParams.Select((x, i) => - Expression.Convert(Expression.Call(Expression.ArrayIndex(args, Expression.Constant(i)), methodInfoToObject, Expression.Constant(x.ParameterType)), x.ParameterType) - ); - - var expression = Expression.Lambda>>( - Expression.Call(methodInfoOfAsTask, Expression.Call(Expression.Convert(thisArg, thisType), methodInfo, lambdaParams)), - thisArg, args - ); - - return expression.Compile(); - } - - private static Task AsTaskFromResult(TValue value) - { - return Task.FromResult((object?)value); - } - - private static Task AsTaskOfObject(Task task) - { - return task.ContinueWith(x => x.Result); - } - } -} diff --git a/src/Rin/Channel/HubInvoker/HubInvocationResult.cs b/src/Rin/Channel/HubInvoker/HubInvocationResult.cs new file mode 100644 index 0000000..068f8f6 --- /dev/null +++ b/src/Rin/Channel/HubInvoker/HubInvocationResult.cs @@ -0,0 +1,17 @@ +using System.Diagnostics; + +namespace Rin.Channel.HubInvoker +{ + [DebuggerDisplay("HubInvocationResult: Value={Value,nq}; HasResult={HasResult,nq}")] + public readonly struct HubInvocationResult + { + public object? Value { get; } + public bool HasResult { get; } + + public HubInvocationResult(object? value) + { + Value = value; + HasResult = true; + } + } +} diff --git a/src/Rin/Channel/HubInvoker/HubInvokeMessage.cs b/src/Rin/Channel/HubInvoker/HubInvokeMessage.cs new file mode 100644 index 0000000..2e62000 --- /dev/null +++ b/src/Rin/Channel/HubInvoker/HubInvokeMessage.cs @@ -0,0 +1,20 @@ +using System; + +namespace Rin.Channel.HubInvoker +{ + public class HubInvokeMessage + { + public string OperationId { get; } + public string Method { get; } + public HubMethodDefinition MethodDefinition { get; } + public object[] Arguments { get; } + + public HubInvokeMessage(string operationId, string method, HubMethodDefinition methodDefinition, object[] arguments) + { + OperationId = operationId ?? throw new ArgumentNullException(nameof(operationId)); + Method = method ?? throw new ArgumentNullException(nameof(method)); + MethodDefinition = methodDefinition ?? throw new ArgumentNullException(nameof(methodDefinition)); + Arguments = arguments ?? throw new ArgumentNullException(nameof(arguments)); + } + } +} diff --git a/src/Rin/Channel/HubInvoker/HubInvoker.cs b/src/Rin/Channel/HubInvoker/HubInvoker.cs new file mode 100644 index 0000000..8c34e56 --- /dev/null +++ b/src/Rin/Channel/HubInvoker/HubInvoker.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace Rin.Channel.HubInvoker +{ + public interface IHubInvoker + { + IReadOnlyDictionary MethodMap { get; } + ValueTask InvokeAsync(object instance, HubInvokeMessage invokeMessage); + bool TryCreateMessage(string json, [NotNullWhen(true)] out HubInvokeMessage? invokeMessage); + } + + /// + /// Provides a mechanism for invoking Hub methods. + /// + /// + public class HubInvoker : IHubInvoker + where T : notnull + { + private static readonly Dictionary _methodMap; + private static readonly Dictionary>> _methodInvoker; + + public IReadOnlyDictionary MethodMap => _methodMap; + + static HubInvoker() + { + _methodMap = typeof(T).GetMethods(BindingFlags.Public | BindingFlags.Instance) + .Where(x => x.DeclaringType == typeof(T)) + .Select(x => new HubMethodDefinition(x)) + .ToDictionary(x => x.Method.Name); + + _methodInvoker = _methodMap.ToDictionary(k => k.Key, v => InvokerHelper.GetInvoker(v.Value)); + } + + ValueTask IHubInvoker.InvokeAsync(object instance, HubInvokeMessage invokeMessage) + { + return InvokeAsync((T)instance, invokeMessage); + } + + public async ValueTask InvokeAsync(T instance, HubInvokeMessage invokeMessage) + { + return await _methodInvoker[invokeMessage.Method](instance, invokeMessage); + } + + /// + /// Create a message for invoking method from a JSON string. + /// + /// + /// + /// + public bool TryCreateMessage(string json, [NotNullWhen(true)] out HubInvokeMessage? invokeMessage) + { + var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); + + var operationId = default(string); + var arguments = Array.Empty(); + var method = default(string); + var methodDef = default(HubMethodDefinition); + + var currentPropName = default(string); + while (reader.Read()) + { + switch (reader.TokenType) + { + case JsonTokenType.PropertyName: + currentPropName = reader.GetString(); + break; + case JsonTokenType.String: + if (currentPropName == "operationId" || currentPropName == "O") + { + operationId = reader.GetString(); + } + else if (currentPropName == "method" || currentPropName == "M") + { + method = reader.GetString(); + methodDef = _methodMap[method]; + } + + currentPropName = null; + break; + case JsonTokenType.StartArray: + if (currentPropName == "arguments" || currentPropName == "A") + { + if (methodDef == null) throw new InvalidOperationException(); + + arguments = new object[methodDef.ParameterTypes.Count]; + for (int i = 0; i < methodDef.ParameterTypes.Count; i++) + { + arguments[i] = ReadObject(ref reader, methodDef.ParameterTypes[i]); + } + + reader.Read(); + if (reader.TokenType != JsonTokenType.EndArray) + { + throw new InvalidOperationException(); + } + } + break; + } + } + + if (methodDef == null || method == null) + { + invokeMessage = null; + return false; + } + + invokeMessage = new HubInvokeMessage( + operationId ?? throw new InvalidOperationException("OperationId must not be null or empty"), + method, + methodDef, + arguments + ); + return true; + } + + private static object ReadObject(ref Utf8JsonReader reader, Type type) + { + reader.Read(); + + return JsonSerializer.Deserialize(ref reader, type); + } + } +} diff --git a/src/Rin/Channel/HubInvoker/HubMethodDefinition.cs b/src/Rin/Channel/HubInvoker/HubMethodDefinition.cs new file mode 100644 index 0000000..da35a9d --- /dev/null +++ b/src/Rin/Channel/HubInvoker/HubMethodDefinition.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Rin.Channel.HubInvoker +{ + /// + /// Provides the definition of the hub method. + /// + public class HubMethodDefinition + { + /// + /// Gets the of the hub method. + /// + public MethodInfo Method { get; } + + /// + /// Gets the return type of the hub method. + /// + public Type ReturnType { get; } + + /// + /// Gets the parameter types of the hub method. + /// + public IReadOnlyList ParameterTypes { get; } + + public HubMethodDefinition(MethodInfo method) + { + Method = method ?? throw new ArgumentNullException(nameof(method)); + ReturnType = method.ReturnType; + ParameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray(); + } + } +} diff --git a/src/Rin/Channel/HubInvoker/InvokerHelper.cs b/src/Rin/Channel/HubInvoker/InvokerHelper.cs new file mode 100644 index 0000000..2a0819a --- /dev/null +++ b/src/Rin/Channel/HubInvoker/InvokerHelper.cs @@ -0,0 +1,83 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; + +namespace Rin.Channel.HubInvoker +{ + internal static class InvokerHelper + { + private static readonly MethodInfo _methodInfoInvokeCoreTaskAsync = typeof(InvokerHelper).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).First(x => x.Name == "InvokeCoreTaskAsync" && x.GetGenericArguments().Length == 1)!; + private static readonly MethodInfo _methodInfoInvokeCoreValueTaskAsync = typeof(InvokerHelper).GetMethods(BindingFlags.NonPublic | BindingFlags.Static).First(x => x.Name == "InvokeCoreValueTaskAsync" && x.GetGenericArguments().Length == 1)!; + + public static Func> GetInvoker(HubMethodDefinition methodDefinition) + { + var func = GetInvokerCore(methodDefinition); + return (instance, invokeMessage) => func((T)instance, invokeMessage); + } + + private static Func> GetInvokerCore(HubMethodDefinition methodDefinition) + { + var returnType = methodDefinition.ReturnType; + if (returnType == typeof(Task)) + { + return InvokeCoreTaskAsync; + } + else if (returnType == typeof(ValueTask)) + { + return InvokeCoreValueTaskAsync; + } + else if (returnType.IsGenericType) + { + var openGenericType = returnType.GetGenericTypeDefinition(); + var actualReturnType = returnType.GetGenericArguments()[0]; + if (openGenericType == typeof(Task<>) || openGenericType == typeof(ValueTask<>)) + { + Expression>> expression = (instance, invokeMessage) => default; + + var methodCore = openGenericType == typeof(Task<>) ? _methodInfoInvokeCoreTaskAsync : _methodInfoInvokeCoreValueTaskAsync; + + expression = expression.Update( + Expression.Call(null, methodCore.MakeGenericMethod(actualReturnType), expression.Parameters), + expression.Parameters + ); + + return expression.Compile(); + } + } + + return InvokeCore; + } + + private static ValueTask InvokeCore(T instance, HubInvokeMessage invokeMessage) + { + var result = invokeMessage.MethodDefinition.Method.Invoke(instance, invokeMessage.Arguments); + return new ValueTask(new HubInvocationResult(result)); + } + + private static async ValueTask InvokeCoreValueTaskAsync(T instance, HubInvokeMessage invokeMessage) + { + var task = (ValueTask)invokeMessage.MethodDefinition.Method.Invoke(instance, invokeMessage.Arguments)!; + return new HubInvocationResult(await task); + } + + private static async ValueTask InvokeCoreValueTaskAsync(T instance, HubInvokeMessage invokeMessage) + { + await ((ValueTask)invokeMessage.MethodDefinition.Method.Invoke(instance, invokeMessage.Arguments)!); + return new HubInvocationResult(); + } + + private static async ValueTask InvokeCoreTaskAsync(T instance, HubInvokeMessage invokeMessage) + { + var task = ((Task)invokeMessage.MethodDefinition.Method.Invoke(instance, invokeMessage.Arguments)!); + return new HubInvocationResult(await task); + } + + private static async ValueTask InvokeCoreTaskAsync(T instance, HubInvokeMessage invokeMessage) + { + await ((Task)invokeMessage.MethodDefinition.Method.Invoke(instance, invokeMessage.Arguments)!); + return new HubInvocationResult(); + } + } +} diff --git a/src/Rin/Channel/RinChannel.cs b/src/Rin/Channel/RinChannel.cs index 7afd015..76a90c3 100644 --- a/src/Rin/Channel/RinChannel.cs +++ b/src/Rin/Channel/RinChannel.cs @@ -1,26 +1,24 @@ -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Rin.Core; +using Microsoft.Extensions.Logging; using System; using System.Buffers; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; using System.Net.WebSockets; using System.Reflection; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Rin.Channel.HubInvoker; namespace Rin.Channel { public class RinChannel : IDisposable { private static readonly Encoding _encoding = new UTF8Encoding(false); - private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private readonly ILogger _logger; public Dictionary> ConnectionsByHub { get; } = new Dictionary>(); @@ -75,6 +73,7 @@ private async Task EstablishConnectionAsync(WebSocket socket, THub hub, st { var memoryStream = new MemoryStream(); var result = await socket.ReceiveAsync(buffer, _cancellationTokenSource.Token); + var invoker = new HubInvoker(); while (!result.CloseStatus.HasValue && !_cancellationTokenSource.IsCancellationRequested) { @@ -89,24 +88,27 @@ private async Task EstablishConnectionAsync(WebSocket socket, THub hub, st try { - var operation = JsonConvert.DeserializeAnonymousType(messageString, new { M = "", O = "", A = default(JToken[]) }); - if (HubDispatcher.CanInvoke(operation.M)) + if (invoker.TryCreateMessage(messageString, out var invokeMessage)) { - try + if (invokeMessage.MethodDefinition != null) { - var methodResult = await HubDispatcher.InvokeAsync(operation.M, hub, operation.A); - await SendResponseAsync(connectionId, operation.O, methodResult); + try + { + var methodResult = await invoker.InvokeAsync(hub, invokeMessage!); + await SendResponseAsync(connectionId, invokeMessage.OperationId, methodResult.Value); + } + catch (Exception ex) + { + await SendResponseAsync(connectionId, invokeMessage.OperationId, new { E = ex.GetType().Name, Detail = ex }); + _logger.LogError(ex, "Exception was thrown until invoking a hub method: Method = {0}; Hub = {1}", invokeMessage.Method, typeof(THub)); + } } - catch (Exception ex) + else { - await SendResponseAsync(connectionId, operation.O, new { E = ex.GetType().Name, Detail = ex }); - _logger.LogError(ex, "Exception was thrown until invoking a hub method: Method = {0}; Hub = {1}", operation.M, typeof(THub)); + await SendResponseAsync(connectionId, invokeMessage.OperationId, new { E = "MethodNotFound" }); + _logger.LogWarning("Method not found: Method = {0}; Hub = {1}", invokeMessage.Method, typeof(THub)); } - } - else - { - await SendResponseAsync(connectionId, operation.O, new { E = "MethodNotFound" }); - _logger.LogWarning("Method not found: Method = {0}; Hub = {1}", operation.M, typeof(THub)); + } } catch (TaskCanceledException) @@ -138,13 +140,11 @@ public TClient GetClient() internal async Task SendAsync(string connectionId, object payload) { - var message = JsonConvert.SerializeObject(payload); - if (Connections.TryGetValue(connectionId, out var conn)) { try { - await conn.Item2.SendAsync(_encoding.GetBytes(message), WebSocketMessageType.Text, true, _cancellationTokenSource.Token); + await conn.Item2.SendAsync(JsonSerializer.SerializeToUtf8Bytes(payload), WebSocketMessageType.Text, true, _cancellationTokenSource.Token); } catch { @@ -155,6 +155,7 @@ internal async Task SendAsync(string connectionId, object payload) } } } + private Task SendAsync(object payload) { if (ConnectionsByHub.TryGetValue(typeof(THub), out var connectionsByTHub)) @@ -165,7 +166,7 @@ private Task SendAsync(object payload) return Task.CompletedTask; } - private Task SendResponseAsync(string connectionId, string operationId, object methodResult) + private Task SendResponseAsync(string connectionId, string operationId, object? methodResult) { return SendAsync(connectionId, new { R = operationId, V = methodResult }); } diff --git a/src/Rin/Middlewares/Api/GetDetailByIdMiddleware.cs b/src/Rin/Middlewares/Api/GetDetailByIdMiddleware.cs index 96aee49..8227439 100644 --- a/src/Rin/Middlewares/Api/GetDetailByIdMiddleware.cs +++ b/src/Rin/Middlewares/Api/GetDetailByIdMiddleware.cs @@ -1,10 +1,10 @@ -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; +using Microsoft.AspNetCore.Http; using Rin.Core.Record; using Rin.Hubs.Payloads; using System; using System.Collections.Generic; using System.Text; +using System.Text.Json; using System.Threading.Tasks; namespace Rin.Middlewares.Api @@ -36,7 +36,7 @@ public async Task InvokeAsync(HttpContext context) context.Response.StatusCode = 200; context.Response.ContentType = "application/json"; - await context.Response.WriteAsync(JsonConvert.SerializeObject(new RequestRecordDetailPayload(result.Value))); + await context.Response.WriteAsync(JsonSerializer.Serialize(new RequestRecordDetailPayload(result.Value))); } } } diff --git a/src/Rin/Rin.csproj b/src/Rin/Rin.csproj index 6daa377..57dfd01 100644 --- a/src/Rin/Rin.csproj +++ b/src/Rin/Rin.csproj @@ -20,7 +20,6 @@ -