/// <summary> /// Add a handler for a subscription /// </summary> /// <typeparam name="T">The type of data the subscription expects</typeparam> /// <param name="request">The request of the subscription</param> /// <param name="identifier">The identifier of the subscription (can be null if request param is used)</param> /// <param name="userSubscription">Whether or not this is a user subscription (counts towards the max amount of handlers on a socket)</param> /// <param name="connection">The socket connection the handler is on</param> /// <param name="dataHandler">The handler of the data received</param> /// <returns></returns> protected virtual SocketSubscription AddHandler <T>(object request, string identifier, bool userSubscription, SocketConnection connection, Action <T> dataHandler) { Action <SocketConnection, JToken> internalHandler = (socketWrapper, data) => { if (typeof(T) == typeof(string)) { dataHandler((T)Convert.ChangeType(data.ToString(), typeof(T))); return; } var desResult = Deserialize <T>(data, false); if (!desResult.Success) { log.Write(LogVerbosity.Warning, $"Failed to deserialize data into type {typeof(T)}: {desResult.Error}"); return; } dataHandler(desResult.Data); }; if (request != null) { return(connection.AddHandler(request, userSubscription, internalHandler)); } return(connection.AddHandler(identifier, userSubscription, internalHandler)); }
public void SocketMessages_Should_BeProcessedInDataHandlers() { // arrange var client = new TestSocketClient(new SocketClientOptions("") { ReconnectInterval = TimeSpan.Zero, LogVerbosity = LogVerbosity.Debug }); var socket = client.CreateSocket(); socket.ShouldReconnect = true; socket.CanConnect = true; socket.DisconnectTime = DateTime.UtcNow; var sub = new SocketConnection(client, socket); var rstEvent = new ManualResetEvent(false); JToken result = null; sub.AddHandler(SocketSubscription.CreateForIdentifier("TestHandler", true, (connection, data) => { result = data; rstEvent.Set(); })); client.ConnectSocketSub(sub); // act socket.InvokeMessage("{\"property\": 123}"); rstEvent.WaitOne(1000); // assert Assert.IsTrue((int)result["property"] == 123); }
protected virtual SocketConnection BitMaxGetWebsocket(string address, bool authenticated) { address = address.TrimEnd('/'); var socketResult = sockets.Where(s => s.Value.Socket.Url.TrimEnd('/') == address.TrimEnd('/') && (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.HandlerCount).FirstOrDefault(); var result = socketResult.Equals(default(KeyValuePair <int, SocketConnection>)) ? null : socketResult.Value; if (result != null) { if (result.HandlerCount < SocketCombineTarget || (sockets.Count >= MaxSocketConnections && sockets.All(s => s.Value.HandlerCount >= SocketCombineTarget))) { // Use existing socket if it has less than target connections OR it has the least connections and we can't make new return(result); } } // Create new socket var socket = CreateSocket(address); var socketWrapper = new SocketConnection(this, socket); foreach (var kvp in genericHandlers) { var handler = SocketSubscription.CreateForIdentifier(kvp.Key, false, kvp.Value); socketWrapper.AddHandler(handler); } return(socketWrapper); }
/// <inheritdoc /> protected override SocketConnection GetWebsocket(string address, bool authenticated) { // Override because signalr puts `/signalr/` add the end of the url var socketResult = sockets.Where(s => s.Value.Socket.Url == address + "/signalr/" && (s.Value.Authenticated == authenticated || !authenticated) && s.Value.Connected).OrderBy(s => s.Value.HandlerCount).FirstOrDefault(); var result = socketResult.Equals(default(KeyValuePair <int, SocketConnection>)) ? null : socketResult.Value; if (result != null) { if (result.HandlerCount < SocketCombineTarget || (sockets.Count >= MaxSocketConnections && sockets.All(s => s.Value.HandlerCount >= SocketCombineTarget))) { // Use existing socket if it has less than target connections OR it has the least connections and we can't make new return(result); } } // Create new socket var socket = CreateSocket(address); var socketWrapper = new SocketConnection(this, socket); foreach (var kvp in genericHandlers) { socketWrapper.AddHandler(SocketSubscription.CreateForIdentifier(kvp.Key, false, kvp.Value)); } return(socketWrapper); }
/// <inheritdoc/> protected override SocketSubscription AddHandler <T>(object?request, string?identifier, bool userSubscription, SocketConnection connection, Action <T> dataHandler) { void InternalHandler(SocketConnection socketWrapper, JToken data) { T response = GetWsMessage <T>(data); if (response == null) { var m = GetWsResponse(data); log.Write(LogVerbosity.Warning, $"Failed to deserialize data into type {typeof(T)} ResponseType:{m?.Meta?.ResponseType}, Token:{m?.Meta?.Token} Msg.Length:{m?.Msg?.Length}"); return; } dataHandler(response); } var handler = request == null ? SocketSubscription.CreateForIdentifier(identifier !, userSubscription, InternalHandler) : SocketSubscription.CreateForRequest(request, userSubscription, InternalHandler); connection.AddHandler(handler); return(handler); }
/// <inheritdoc /> protected override async Task <CallResult <UpdateSubscription> > Subscribe <T>(string url, object?request, string?identifier, bool authenticated, Action <T> dataHandler) { SocketConnection? socket; SocketSubscription handler; var released = false; await semaphoreSlim.WaitAsync().ConfigureAwait(false); try { socket = GetWebsocket(url, authenticated); if (socket == null) { KucoinToken token; var clientOptions = KucoinClient.DefaultOptions.Copy(); KucoinApiCredentials?thisCredentials = (KucoinApiCredentials?)authProvider?.Credentials; if (thisCredentials != null) { clientOptions.ApiCredentials = new KucoinApiCredentials(thisCredentials.Key !.GetString(), thisCredentials.Secret !.GetString(), thisCredentials.PassPhrase.GetString()); } using (var restClient = new KucoinClient(clientOptions)) { var tokenResult = restClient.GetWebsocketToken(authenticated).Result; if (!tokenResult) { return(new CallResult <UpdateSubscription>(null, tokenResult.Error)); } token = tokenResult.Data; } // Create new socket var s = CreateSocket(token.Servers.First().Endpoint + "?token=" + token.Token); socket = new SocketConnection(this, s); foreach (var kvp in genericHandlers) { socket.AddHandler(SocketSubscription.CreateForIdentifier(kvp.Key, false, kvp.Value)); } } handler = AddHandler(request, identifier, true, socket, dataHandler); if (SocketCombineTarget == 1) { // Can release early when only a single sub per connection semaphoreSlim.Release(); released = true; } var connectResult = await ConnectIfNeeded(socket, authenticated).ConfigureAwait(false); if (!connectResult) { return(new CallResult <UpdateSubscription>(null, connectResult.Error)); } } finally { //When the task is ready, release the semaphore. It is vital to ALWAYS release the semaphore when we are ready, or else we will end up with a Semaphore that is forever locked. //This is why it is important to do the Release within a try...finally clause; program execution may crash or take a different path, this way you are guaranteed execution if (!released) { semaphoreSlim.Release(); } } if (request != null) { var subResult = await SubscribeAndWait(socket, request, handler).ConfigureAwait(false); if (!subResult) { await socket.Close(handler).ConfigureAwait(false); return(new CallResult <UpdateSubscription>(null, subResult.Error)); } } else { handler.Confirmed = true; } socket.ShouldReconnect = true; return(new CallResult <UpdateSubscription>(new UpdateSubscription(socket, handler), null)); }