public async Task Connect(string server, string subProtocol = null) { LCLogger.Debug($"Connecting WebSocket: {server}"); Task timeoutTask = Task.Delay(CONNECT_TIMEOUT); ws = new ClientWebSocket(); ws.Options.SetBuffer(RECV_BUFFER_SIZE, SEND_BUFFER_SIZE); if (!string.IsNullOrEmpty(subProtocol)) { ws.Options.AddSubProtocol(subProtocol); } Task connectTask = ws.ConnectAsync(new Uri(server), default); if (await Task.WhenAny(connectTask, timeoutTask) == connectTask) { LCLogger.Debug($"Connected WebSocket: {server}"); await connectTask; // 接收 _ = StartReceive(); } else { throw new TimeoutException("Connect timeout"); } }
public async Task <object> RPC(string funcName, JsonElement body) { try { LCLogger.Debug($"RPC: {funcName}"); LCLogger.Debug(body.ToString()); if (Functions.TryGetValue(funcName, out MethodInfo mi)) { LCEngine.InitRequestContext(Request); object[] ps = ParseParameters(mi, body); object result = await LCEngine.Invoke(mi, ps); if (result != null) { return(new Dictionary <string, object> { { "result", LCCloud.Encode(result) } }); } } return(body); } catch (Exception e) { return(StatusCode(500, e.Message)); } }
private void OnMessage(byte[] bytes, int length) { try { GenericCommand command = GenericCommand.Parser.ParseFrom(bytes, 0, length); LCLogger.Debug($"{id} <= {FormatCommand(command)}"); if (command.HasI) { // 应答 int requestIndex = command.I; if (requestToResponses.TryGetValue(command, out TaskCompletionSource <GenericCommand> tcs)) { requestToResponses.Remove(command); if (command.HasErrorMessage) { // 错误 ErrorCommand error = command.ErrorMessage; int code = error.Code; string detail = error.Detail; // 包装成异常抛出 LCException exception = new LCException(code, detail); tcs.TrySetException(exception); } else { tcs.TrySetResult(command); } } else { LCLogger.Error($"No request for {requestIndex}"); } } else { if (command.Cmd == CommandType.Echo) { // 心跳应答 heartBeat.Pong(); } else if (command.Cmd == CommandType.Goaway) { // 针对连接的消息 Reset(); } else { // 通知 string peerId = command.HasPeerId ? command.PeerId : defaultClientId; if (idToClients.TryGetValue(peerId, out LCIMClient client)) { // 通知具体客户端 client.HandleNotification(command); } } } } catch (Exception e) { LCLogger.Error(e); } }
public object Get() { LCLogger.Debug("Ping ~~~"); return(new Dictionary <string, string> { { "runtime", $"dotnet-{Environment.Version}" }, { "version", LCCore.SDKVersion } }); }
private static void OnNotification(Dictionary <string, object> notification) { if (!notification.TryGetValue("cmd", out object cmd) || !"data".Equals(cmd)) { return; } if (!notification.TryGetValue("msg", out object msg) || !(msg is IEnumerable <object> list)) { return; } foreach (object item in list) { if (item is Dictionary <string, object> dict) { if (!dict.TryGetValue("op", out object op)) { continue; } switch (op as string) { case "create": OnCreateNotification(dict); break; case "update": OnUpdateNotification(dict); break; case "enter": OnEnterNotification(dict); break; case "leave": OnLeaveNotification(dict); break; case "delete": OnDeleteNotification(dict); break; case "login": OnLoginNotification(dict); break; default: LCLogger.Debug($"Not support: {op}"); break; } } } }
private void OnClientMessage(byte[] bytes, int length) { try { string json = Encoding.UTF8.GetString(bytes, 0, length); Dictionary <string, object> msg = JsonConvert.DeserializeObject <Dictionary <string, object> >(json, LCJsonConverter.Default); LCLogger.Debug($"{id} <= {json}"); if (msg.TryGetValue("i", out object i)) { int requestIndex = Convert.ToInt32(i); if (responses.TryGetValue(requestIndex, out TaskCompletionSource <Dictionary <string, object> > tcs)) { if (msg.TryGetValue("error", out object error)) { // 错误 if (error is Dictionary <string, object> dict) { int code = Convert.ToInt32(dict["code"]); string detail = dict["detail"] as string; tcs.SetException(new LCException(code, detail)); } else { tcs.SetException(new Exception(error as string)); } } else { tcs.SetResult(msg); } responses.Remove(requestIndex); } else { LCLogger.Error($"No request for {requestIndex}"); } } else { if (json == "{}") { heartBeat.Pong(); } else { // 通知 OnNotification?.Invoke(msg); } } } catch (Exception e) { LCLogger.Error(e); } }
private async Task StartReceive() { byte[] recvBuffer = new byte[RECV_BUFFER_SIZE]; byte[] msgBuffer = new byte[MSG_BUFFER_SIZE]; int offset = 0; try { while (ws.State == WebSocketState.Open) { WebSocketReceiveResult result = await ws.ReceiveAsync(new ArraySegment <byte>(recvBuffer), default); if (result.MessageType == WebSocketMessageType.Close) { LCLogger.Debug($"Receive Closed: {result.CloseStatus}"); if (ws.State == WebSocketState.CloseReceived) { // 如果是服务端主动关闭,则挥手关闭,并认为是断线 try { Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default); await Task.WhenAny(closeTask, Task.Delay(CLOSE_TIMEOUT)); } catch (Exception e) { LCLogger.Error(e); } finally { HandleExceptionClose(); } } } else { // 拼合 WebSocket Message int length = result.Count; if (offset + length > msgBuffer.Length) { // 反序列化数组大小不够,则以 2x 扩充 byte[] newBuffer = new byte[msgBuffer.Length * 2]; Array.Copy(msgBuffer, newBuffer, msgBuffer.Length); msgBuffer = newBuffer; } Array.Copy(recvBuffer, 0, msgBuffer, offset, length); offset += length; if (result.EndOfMessage) { OnMessage?.Invoke(msgBuffer, offset); offset = 0; } } } } catch (Exception e) { // 客户端网络异常 LCLogger.Error(e); HandleExceptionClose(); } }
private async void StartPing() { while (running) { try { await Task.Delay(PING_INTERVAL, heartBeatCTS.Token); } catch (TaskCanceledException) { return; } LCLogger.Debug("Ping ~~~"); SendPing(); } }
public async Task <object> Hook(string className, string hookName, JsonElement body) { try { LCLogger.Debug($"Hook: {className}#{hookName}"); LCLogger.Debug(body.ToString()); LCEngine.CheckHookKey(Request); string classHookName = GetClassHookName(className, hookName); if (ClassHooks.TryGetValue(classHookName, out MethodInfo mi)) { Dictionary <string, object> data = LCEngine.Decode(body); LCObjectData objectData = LCObjectData.Decode(data["object"] as Dictionary <string, object>); objectData.ClassName = className; LCObject obj = LCObject.Create(className); obj.Merge(objectData); // 避免死循环 if (hookName.StartsWith("before")) { obj.DisableBeforeHook(); } else { obj.DisableAfterHook(); } LCEngine.InitRequestContext(Request); LCUser user = null; if (data.TryGetValue("user", out object userObj) && userObj != null) { user = new LCUser(); user.Merge(LCObjectData.Decode(userObj as Dictionary <string, object>)); LCEngineRequestContext.CurrentUser = user; } LCObject result = await LCEngine.Invoke(mi, new object[] { obj }) as LCObject; if (result != null) { return(LCCloud.Encode(result)); } } return(body); } catch (Exception e) { return(StatusCode(500, e.Message)); } }
/// <summary> /// Sends text message. /// </summary> /// <param name="text"></param> /// <returns></returns> internal async Task SendText(string text) { LCLogger.Debug($"{id} => {text}"); Task sendTask = client.Send(text); if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) { await sendTask; } else { throw new TimeoutException("Send request time out"); } }
internal async Task SendCommand(GenericCommand command) { LCLogger.Debug($"{id} => {FormatCommand(command)}"); byte[] bytes = command.ToByteArray(); Task sendTask = ws.Send(bytes); if (await Task.WhenAny(sendTask, Task.Delay(SEND_TIMEOUT)) == sendTask) { await sendTask; } else { throw new TimeoutException("Send request"); } }
public static void PrintResponse(HttpResponseMessage response, string content = null) { if (LCLogger.LogDelegate == null) { return; } StringBuilder sb = new StringBuilder(); sb.AppendLine("=== HTTP Response Start ==="); sb.AppendLine($"URL: {response.RequestMessage.RequestUri}"); sb.AppendLine($"Status Code: {response.StatusCode}"); if (!string.IsNullOrEmpty(content)) { sb.AppendLine($"Content: {content}"); } sb.AppendLine("=== HTTP Response End ==="); LCLogger.Debug(sb.ToString()); }
internal static object GetFunctions(HttpRequest request) { CheckMasterKey(request); List <string> functions = new List <string>(); functions.AddRange(Functions.Keys); functions.AddRange(ClassHooks.Keys); functions.AddRange(UserHooks.Keys); foreach (string func in functions) { LCLogger.Debug(func); } return(new Dictionary <string, List <string> > { { "result", functions } }); }
public async Task Close() { LCLogger.Debug("Closing WebSocket"); OnMessage = null; OnClose = null; try { // 发送关闭帧可能会很久,所以增加超时 // 主动挥手关闭,不会再收到 Close Frame Task closeTask = ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, default); Task delayTask = Task.Delay(CLOSE_TIMEOUT); await Task.WhenAny(closeTask, delayTask); } catch (Exception e) { LCLogger.Error(e); } finally { ws.Abort(); ws.Dispose(); LCLogger.Debug("Closed WebSocket"); } }
public async Task Connect() { try { LCRTMServer rtmServer = await router.GetServer(); try { LCLogger.Debug($"Primary Server"); await client.Connect(rtmServer.Primary, SUB_PROTOCOL); } catch (Exception e) { LCLogger.Error(e); LCLogger.Debug($"Secondary Server"); await client.Connect(rtmServer.Secondary, SUB_PROTOCOL); } // 启动心跳 heartBeat.Start(); } catch (Exception e) { throw e; } }
public static void PrintRequest(HttpClient client, HttpRequestMessage request, string content = null) { if (LCLogger.LogDelegate == null) { return; } if (client == null) { return; } if (request == null) { return; } StringBuilder sb = new StringBuilder(); sb.AppendLine("=== HTTP Request Start ==="); sb.AppendLine($"URL: {request.RequestUri}"); sb.AppendLine($"Method: {request.Method}"); sb.AppendLine($"Headers: "); foreach (var header in client.DefaultRequestHeaders) { sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); } foreach (var header in request.Headers) { sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); } if (request.Content != null) { foreach (var header in request.Content.Headers) { sb.AppendLine($"\t{header.Key}: {string.Join(",", header.Value.ToArray())}"); } } if (!string.IsNullOrEmpty(content)) { sb.AppendLine($"Content: {content}"); } sb.AppendLine("=== HTTP Request End ==="); LCLogger.Debug(sb.ToString()); }
public async Task <object> HookLogin(JsonElement body) { try { LCLogger.Debug(LCEngine.OnLogin); LCLogger.Debug(body.ToString()); LCEngine.CheckHookKey(Request); if (UserHooks.TryGetValue(LCEngine.OnLogin, out MethodInfo mi)) { LCEngine.InitRequestContext(Request); Dictionary <string, object> dict = LCEngine.Decode(body); return(await Invoke(mi, dict)); } return(body); } catch (Exception e) { return(StatusCode(500, e.Message)); } }
internal async Task ConnectInternal() { state = State.Connecting; try { LCRTMServer rtmServer = await router.GetServer(); try { LCLogger.Debug($"Primary Server"); await ws.Connect(rtmServer.Primary, SUB_PROTOCOL); } catch (Exception e) { LCLogger.Error(e); LCLogger.Debug($"Secondary Server"); await ws.Connect(rtmServer.Secondary, SUB_PROTOCOL); } // 启动心跳 heartBeat.Start(); state = State.Open; } catch (Exception e) { state = State.Closed; throw e; } }
private async Task Reconnect() { while (true) { int reconnectCount = 0; // 重连策略 while (reconnectCount < MAX_RECONNECT_TIMES) { try { LCLogger.Debug($"Reconnecting... {reconnectCount}"); await Connect(); break; } catch (Exception e) { reconnectCount++; LCLogger.Error(e); LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms"); await Task.Delay(RECONNECT_INTERVAL); } } if (reconnectCount < MAX_RECONNECT_TIMES) { // 重连成功 LCLogger.Debug("Reconnected"); ws.OnMessage = OnMessage; ws.OnClose = OnDisconnect; foreach (LCIMClient client in idToClients.Values) { client.HandleReconnected(); } break; } else { // 重置 Router,继续尝试重连 router = new LCRTMRouter(); } } }
private async Task Reconnect() { while (true) { int reconnectCount = 0; // 重连策略 while (reconnectCount < MAX_RECONNECT_TIMES) { try { LCLogger.Debug($"Reconnecting... {reconnectCount}"); await Connect(); break; } catch (Exception e) { reconnectCount++; LCLogger.Error(e); LCLogger.Debug($"Reconnect after {RECONNECT_INTERVAL}ms"); await Task.Delay(RECONNECT_INTERVAL); } } if (reconnectCount < MAX_RECONNECT_TIMES) { // 重连成功 LCLogger.Debug("Reconnected"); client.OnMessage = OnClientMessage; client.OnClose = OnClientDisconnect; OnReconnected?.Invoke(); break; } else { // 重置 Router,继续尝试重连 router = new LCRTMRouter(); } } }
private static void PrintEnvironmentVar(string key) { LCLogger.Debug($"{key} : {Environment.GetEnvironmentVariable(key)}"); }
/// <summary> /// Initializes the engine with the given services. /// </summary> /// <param name="services"></param> public static void Initialize(IServiceCollection services) { // 获取环境变量 LCLogger.Debug("-------------------------------------------------"); PrintEnvironmentVar("LEANCLOUD_APP_ID"); PrintEnvironmentVar("LEANCLOUD_APP_KEY"); PrintEnvironmentVar("LEANCLOUD_APP_MASTER_KEY"); PrintEnvironmentVar("LEANCLOUD_APP_HOOK_KEY"); PrintEnvironmentVar("LEANCLOUD_API_SERVER"); PrintEnvironmentVar("LEANCLOUD_APP_PROD"); PrintEnvironmentVar("LEANCLOUD_APP_ENV"); PrintEnvironmentVar("LEANCLOUD_APP_INSTANCE"); PrintEnvironmentVar("LEANCLOUD_REGION"); PrintEnvironmentVar("LEANCLOUD_APP_ID"); PrintEnvironmentVar("LEANCLOUD_APP_DOMAIN"); PrintEnvironmentVar("LEANCLOUD_APP_PORT"); LCLogger.Debug("-------------------------------------------------"); LCApplication.Initialize(Environment.GetEnvironmentVariable("LEANCLOUD_APP_ID"), Environment.GetEnvironmentVariable("LEANCLOUD_APP_KEY"), Environment.GetEnvironmentVariable("LEANCLOUD_API_SERVER"), Environment.GetEnvironmentVariable("LEANCLOUD_APP_MASTER_KEY")); LCCore.HttpClient.AddAddtionalHeader(LCHookKeyName, Environment.GetEnvironmentVariable("LEANCLOUD_APP_HOOK_KEY")); Assembly assembly = Assembly.GetCallingAssembly(); ClassHooks = assembly.GetTypes() .SelectMany(t => t.GetMethods()) .Where(m => m.GetCustomAttribute <LCEngineClassHookAttribute>() != null) .ToDictionary(mi => { LCEngineClassHookAttribute attr = mi.GetCustomAttribute <LCEngineClassHookAttribute>(); switch (attr.HookType) { case LCEngineObjectHookType.BeforeSave: return($"{BeforeSave}{attr.ClassName}"); case LCEngineObjectHookType.AfterSave: return($"{AfterSave}{attr.ClassName}"); case LCEngineObjectHookType.BeforeUpdate: return($"{BeforeUpdate}{attr.ClassName}"); case LCEngineObjectHookType.AfterUpdate: return($"{AfterUpdate}{attr.ClassName}"); case LCEngineObjectHookType.BeforeDelete: return($"{BeforeDelete}{attr.ClassName}"); case LCEngineObjectHookType.AfterDelete: return($"{AfterDelete}{attr.ClassName}"); default: throw new Exception($"Error hook type: {attr.HookType}"); } }); UserHooks = assembly.GetTypes() .SelectMany(t => t.GetMethods()) .Where(m => m.GetCustomAttribute <LCEngineUserHookAttribute>() != null) .ToDictionary(mi => { LCEngineUserHookAttribute attr = mi.GetCustomAttribute <LCEngineUserHookAttribute>(); switch (attr.HookType) { case LCEngineUserHookType.OnSMSVerified: return(OnSMSVerified); case LCEngineUserHookType.OnEmailVerified: return(OnEmailVerified); case LCEngineUserHookType.OnLogin: return(OnLogin); default: throw new Exception($"Error hook type: {attr.HookType}"); } }); Functions = assembly.GetTypes() .SelectMany(t => t.GetMethods()) .Where(m => m.GetCustomAttribute <LCEngineFunctionAttribute>() != null) .ToDictionary(mi => mi.GetCustomAttribute <LCEngineFunctionAttribute>().FunctionName); assembly.GetTypes() .SelectMany(t => t.GetMethods()) .Where(m => m.GetCustomAttribute <LCEngineRealtimeHookAttribute>() != null) .ToDictionary(mi => { LCEngineRealtimeHookAttribute attr = mi.GetCustomAttribute <LCEngineRealtimeHookAttribute>(); switch (attr.HookType) { case LCEngineRealtimeHookType.ClientOnline: return(ClientOnline); case LCEngineRealtimeHookType.ClientOffline: return(ClientOffline); case LCEngineRealtimeHookType.MessageSent: return(MessageSent); case LCEngineRealtimeHookType.MessageReceived: return(MessageReceived); case LCEngineRealtimeHookType.ReceiversOffline: return(ReceiversOffline); case LCEngineRealtimeHookType.MessageUpdate: return(MessageUpdate); case LCEngineRealtimeHookType.ConversationStart: return(ConversationStart); case LCEngineRealtimeHookType.ConversationStarted: return(ConversationStarted); case LCEngineRealtimeHookType.ConversationAdd: return(ConversationAdd); case LCEngineRealtimeHookType.ConversationAdded: return(ConversationAdded); case LCEngineRealtimeHookType.ConversationRemove: return(ConversationRemove); case LCEngineRealtimeHookType.ConversationRemoved: return(ConversationRemoved); case LCEngineRealtimeHookType.ConversationUpdate: return(ConversationUpdate); default: throw new Exception($"Error hook type: {attr.HookType}"); } }) .ToList() .ForEach(item => { Functions.TryAdd(item.Key, item.Value); }); services.AddCors(options => { options.AddPolicy(LCEngineCORS, builder => { builder.AllowAnyOrigin() .WithMethods(LCEngineCORSMethods) .WithHeaders(LCEngineCORSHeaders) .SetPreflightMaxAge(TimeSpan.FromSeconds(86400)); }); }); }
public void Pong() { LCLogger.Debug("Pong ~~~"); // 刷新最近 pong 时间戳 lastPongTime = DateTimeOffset.Now; }