using cmonitor.api.websocket; using cmonitor.config; using common.libs; using common.libs.extends; using Microsoft.Extensions.DependencyInjection; using System.Buffers; using System.Collections.Concurrent; using System.Reflection; using System.Text.Json; namespace cmonitor.api { /// /// 前段接口服务 /// public sealed class ApiServer : IApiServer { private readonly Dictionary plugins = new(); private readonly ConcurrentDictionary connectionTimes = new(); public uint OnlineNum = 0; private readonly ServiceProvider serviceProvider; private WebSocketServer server; private readonly Config config; public ApiServer(ServiceProvider serviceProvider, Config config) { this.serviceProvider = serviceProvider; this.config = config; } /// /// 加载插件 /// /// public void LoadPlugins(Assembly[] assemblys) { Type voidType = typeof(void); IEnumerable types = assemblys.SelectMany(c => c.GetTypes()).Where(c => c.GetInterfaces().Contains(typeof(IApiController))); if (config.Data.Common.PluginNames.Length > 0) { types = types.Where(c => config.Data.Common.PluginNames.Any(d => c.FullName.Contains(d))); } foreach (Type item in types) { object obj = serviceProvider.GetService(item); if(obj == null) { continue; } Logger.Instance.Warning($"load server api:{item.Name}"); string path = item.Name.Replace("ApiController", ""); foreach (MethodInfo method in item.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) { string key = $"{path}/{method.Name}".ToLower(); if (!plugins.ContainsKey(key)) { bool istask = method.ReturnType.GetProperty("IsCompleted") != null && method.ReturnType.GetMethod("GetAwaiter") != null; bool isTaskResult = method.ReturnType.GetProperty("Result") != null; plugins.TryAdd(key, new PluginPathCacheInfo { IsVoid = method.ReturnType == voidType, Method = method, Target = obj, IsTask = istask, IsTaskResult = isTaskResult }); } } } } /// /// 开启websockt /// public void Websocket() { server = new WebSocketServer(); try { server.Start(System.Net.IPAddress.Any, config.Data.Server.ApiPort); } catch (Exception ex) { Logger.Instance.Error(ex); } server.OnOpen = (connection) => { Interlocked.Increment(ref OnlineNum); connectionTimes.TryAdd(connection.Id, new ConnectionTimeInfo()); }; server.OnDisConnectd = (connection) => { Interlocked.Decrement(ref OnlineNum); if (OnlineNum < 0) Interlocked.Exchange(ref OnlineNum, 0); connectionTimes.TryRemove(connection.Id, out _); }; server.OnMessage = (connection, frame, message) => { if (connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo)) { timeInfo.DateTime = DateTime.Now; } var req = message.DeJson(); req.Connection = connection; OnMessage(req).ContinueWith((result) => { var resp = result.Result.ToJson().ToBytes(); connection.SendFrameText(resp); }); }; } /// /// 收到消息 /// /// /// public async Task OnMessage(ApiControllerRequestInfo model) { model.Path = model.Path.ToLower(); if (plugins.TryGetValue(model.Path, out PluginPathCacheInfo plugin) == false) { return new ApiControllerResponseInfo { Content = "not exists this path", RequestId = model.RequestId, Path = model.Path, Code = ApiControllerResponseCodes.NotFound }; } try { ApiControllerParamsInfo param = new ApiControllerParamsInfo { RequestId = model.RequestId, Content = model.Content, Connection = model.Connection }; dynamic resultAsync = plugin.Method.Invoke(plugin.Target, new object[] { param }); object resultObject = null; if (plugin.IsVoid == false) { if (plugin.IsTask) { await resultAsync.ConfigureAwait(false); if (plugin.IsTaskResult) { resultObject = resultAsync.Result; } } else { resultObject = resultAsync; } } return new ApiControllerResponseInfo { Code = param.Code, Content = param.Code != ApiControllerResponseCodes.Error ? resultObject : param.ErrorMessage, RequestId = model.RequestId, Path = model.Path, }; } catch (Exception ex) { Logger.Instance.Error(ex); return new ApiControllerResponseInfo { Content = ex.Message, RequestId = model.RequestId, Path = model.Path, Code = ApiControllerResponseCodes.Error }; } } public void Notify(string path, object content) { if (server.Connections.Any()) { try { byte[] bytes = JsonSerializer.Serialize(new ApiControllerResponseInfo { Code = ApiControllerResponseCodes.Success, Content = content, Path = path, RequestId = 0 }).ToBytes(); foreach (WebsocketConnection connection in server.Connections) { if (connection.Connected && connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo) && (DateTime.Now - timeInfo.DateTime).TotalMilliseconds < 1000) { try { connection.SendFrameText(bytes); } catch (Exception) { } } } } catch (Exception ex) { Logger.Instance.Error(ex); } } } public void Notify(string path, string name, Memory content) { if (server.Connections.Any()) { try { Memory headMemory = JsonSerializer.Serialize(new ApiControllerResponseInfo { Code = ApiControllerResponseCodes.Success, Content = name, Path = path, RequestId = 0 }).ToBytes(); int length = 4 + headMemory.Length + content.Length; byte[] result = ArrayPool.Shared.Rent(length); int index = 0; headMemory.Length.ToBytes(result); index += 4; headMemory.CopyTo(result.AsMemory(index)); index += headMemory.Length; content.CopyTo(result.AsMemory(index)); index += content.Length; foreach (WebsocketConnection connection in server.Connections) { if (connection.Connected && connectionTimes.TryGetValue(connection.Id, out ConnectionTimeInfo timeInfo) && (DateTime.Now - timeInfo.DateTime).TotalMilliseconds < 1000) { try { connection.SendFrameBinary(result.AsMemory(0, length)); } catch (Exception) { } } } ArrayPool.Shared.Return(result); } catch (Exception) { //Logger.Instance.Error(ex); } } } public void Notify(string path, object content, WebsocketConnection connection) { try { if (connection.Connected == false) return; byte[] bytes = JsonSerializer.Serialize(new ApiControllerResponseInfo { Code = ApiControllerResponseCodes.Success, Content = content, Path = path, RequestId = 0 }).ToBytes(); try { connection.SendFrameText(bytes); } catch (Exception) { } } catch (Exception) { //Logger.Instance.Error(ex); } } } public sealed class ConnectionTimeInfo { public DateTime DateTime { get; set; } = DateTime.Now; } /// /// 前段接口缓存 /// public struct PluginPathCacheInfo { /// /// 对象 /// public object Target { get; set; } /// /// 方法 /// public MethodInfo Method { get; set; } /// /// 是否void /// public bool IsVoid { get; set; } /// /// 是否task /// public bool IsTask { get; set; } /// /// 是否task result /// public bool IsTaskResult { get; set; } } }