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)); } }
internal async Task <GenericCommand> SendRequest(GenericCommand request) { if (IsIdempotentCommand(request)) { GenericCommand sendingReq = requestToResponses.Keys.FirstOrDefault(item => { // TRICK 除了 I 其他字段相等 request.I = item.I; return(Equals(request, item)); }); if (sendingReq != null) { LCLogger.Warn("duplicated request"); if (requestToResponses.TryGetValue(sendingReq, out TaskCompletionSource <GenericCommand> waitingTcs)) { return(await waitingTcs.Task); } LCLogger.Error($"error request: {request}"); } } TaskCompletionSource <GenericCommand> tcs = new TaskCompletionSource <GenericCommand>(); request.I = requestI++; requestToResponses.Add(request, tcs); try { await SendCommand(request); } catch (Exception e) { tcs.TrySetException(e); } return(await tcs.Task); }
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"); } }
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 async Task SaveToLocal() { try { string json = currentUser.ToString(); await LCCore.PersistenceController.WriteText(USER_DATA, json); } catch (Exception e) { LCLogger.Error(e.Message); } }
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 HandleExceptionClose() { try { ws.Abort(); ws.Dispose(); } catch (Exception e) { LCLogger.Error(e); } finally { OnClose?.Invoke(); } }
/// <summary> /// Signing in /// </summary> /// <param name="force">If this is ture (default value), and single device sign-on is enabled, users already logged in on another device with the same tag will be logged out.</param> /// <returns></returns> public async Task Open(bool force = true) { try { // 打开 Session await SessionController.Open(force); } catch (Exception e) { LCLogger.Error(e); // 如果 session 阶段异常,则关闭连接 throw e; } }
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"); } }
protected virtual void SendPing() { // 发送 ping 包 GenericCommand command = new GenericCommand { Cmd = CommandType.Echo, AppId = LCCore.AppId, PeerId = connection.id }; try { _ = connection.SendCommand(command); } catch (Exception e) { LCLogger.Error(e.Message); } }
LCException HandleErrorResponse(HttpStatusCode statusCode, string responseContent) { int code = (int)statusCode; string message = responseContent; try { // 尝试获取 LeanCloud 返回错误信息 Dictionary <string, object> error = JsonConvert.DeserializeObject <Dictionary <string, object> >(responseContent, LCJsonConverter.Default); code = (int)error["code"]; message = error["error"].ToString(); } catch (Exception e) { LCLogger.Error(e); } return(new LCException(code, message)); }
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 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()); }
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()); }
/// <summary> /// Gets the currently logged in LCUser with a valid session, from /// memory or disk if necessary. /// </summary> /// <returns></returns> public static async Task <LCUser> GetCurrent() { if (currentUser != null) { return(currentUser); } string data = await LCCore.PersistenceController.ReadText(USER_DATA); if (!string.IsNullOrEmpty(data)) { try { currentUser = ParseObject(data) as LCUser; } catch (Exception e) { LCLogger.Error(e); await LCCore.PersistenceController.Delete(USER_DATA); } } return(currentUser); }
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)); } }
public async Task Send(byte[] data, WebSocketMessageType messageType = WebSocketMessageType.Binary) { ArraySegment <byte> bytes = new ArraySegment <byte>(data); if (ws.State == WebSocketState.Open) { try { await ws.SendAsync(bytes, messageType, true, default); } catch (Exception e) { LCLogger.Error(e); throw e; } } else { string message = $"Error Websocket state: {ws.State}"; LCLogger.Error(message); throw new Exception(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(); } } }
async Task <LCAppServer> FetchAppServer() { // 判断节点地区 if (!IsInternalApp(appId)) { // 国内节点必须配置自定义域名 throw new Exception("Please init with your server url."); } // 向 App Router 请求地址 if (appServer == null || !appServer.IsValid) { try { HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri($"https://app-router.com/2/route?appId={appId}"), Method = HttpMethod.Get }; HttpClient client = new HttpClient(); LCHttpUtils.PrintRequest(client, request); HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); request.Dispose(); string resultString = await response.Content.ReadAsStringAsync(); response.Dispose(); LCHttpUtils.PrintResponse(response, resultString); Dictionary <string, object> data = JsonConvert.DeserializeObject <Dictionary <string, object> >(resultString); appServer = new LCAppServer(data); } catch (Exception e) { LCLogger.Error(e); // 拉取服务地址失败后,使用国际节点的默认服务地址 appServer = LCAppServer.GetInternalFallbackAppServer(appId); } } return(appServer); }
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(); } } }
internal async Task <ReadOnlyCollection <LCIMConversation> > Find(LCIMConversationQuery query) { GenericCommand command = new GenericCommand { Cmd = CommandType.Conv, Op = OpType.Query, AppId = LCCore.AppId, PeerId = Client.Id, }; ConvCommand convMessage = new ConvCommand(); string where = query.Condition.BuildWhere(); if (!string.IsNullOrEmpty(where)) { try { convMessage.Where = new JsonObjectMessage { Data = where }; } catch (Exception e) { LCLogger.Error(e); } } int flag = 0; if (query.Compact) { flag += LCIMConversationQuery.CompactFlag; } if (query.WithLastMessageRefreshed) { flag += LCIMConversationQuery.WithLastMessageFlag; } if (flag > 0) { convMessage.Flag = flag; } convMessage.Skip = query.Condition.Skip; convMessage.Limit = query.Condition.Limit; string orders = query.Condition.BuildOrders(); if (!string.IsNullOrEmpty(orders)) { convMessage.Sort = orders; } command.ConvMessage = convMessage; GenericCommand response = await Connection.SendRequest(command); JsonObjectMessage results = response.ConvMessage.Results; List <object> convs = JsonConvert.DeserializeObject <List <object> >(results.Data, LCJsonConverter.Default); return(convs.Select(item => { Dictionary <string, object> conv = item as Dictionary <string, object>; string convId = conv["objectId"] as string; if (!Client.ConversationDict.TryGetValue(convId, out LCIMConversation conversation)) { // 解析是哪种类型的对话 if (conv.TryGetValue("tr", out object transient) && (bool)transient == true) { conversation = new LCIMChatRoom(Client); } else if (conv.ContainsKey("tempConv") && conv.ContainsKey("tempConvTTL")) { conversation = new LCIMTemporaryConversation(Client); } else if (conv.TryGetValue("sys", out object sys) && (bool)sys == true) { conversation = new LCIMServiceConversation(Client); } else { conversation = new LCIMConversation(Client); } Client.ConversationDict[convId] = conversation; } conversation.MergeFrom(conv); return conversation; }).ToList().AsReadOnly()); }