/// <summary>Registers the client described by client.</summary> /// <param name="client">The client.</param> public static void RegisterClient(WebsocketClientInformation client) { if (client == null) { return; } // add this client to our dictionary if (!_instance._guidInfoDict.ContainsKey(client.Uid)) { _instance._guidInfoDict.Add(client.Uid, client); } if (!_instance._clientsAndTimeouts.ContainsKey(client.Uid)) { _instance._clientsAndTimeouts.TryAdd(client.Uid, DateTime.Now.Ticks + _keepaliveTimeoutTicks); } }
/// <summary>Expires and Removes the token described by tokenGuid.</summary> /// <param name="tokenGuid">Unique identifier for the token.</param> public static void ExpireToken(Guid tokenGuid) { if (!_instance._guidTokenDict.ContainsKey(tokenGuid)) { return; } Guid clientGuid = _instance._guidTokenDict[tokenGuid].BoundClient; if (clientGuid == Guid.Empty) { _instance._guidTokenDict.Remove(tokenGuid); return; } if (!_instance._guidInfoDict.ContainsKey(clientGuid)) { _instance._guidTokenDict.Remove(tokenGuid); return; } SubscriptionWsBindingToken token = _instance._guidTokenDict[tokenGuid]; WebsocketClientInformation clientInfo = _instance._guidInfoDict[clientGuid]; // if the client only has one token (this one), deactivate everything if (clientInfo.BoundTokenGuids.Count == 1) { foreach (string subscriptionId in token.SubscriptionIds) { RemoveSubscriptionFromClient(subscriptionId, clientGuid); } } // TODO: for now, just assume it has been replaced with a newer replica _instance._guidTokenDict.Remove(tokenGuid); _instance._guidInfoDict[clientGuid].BoundTokenGuids.Remove(tokenGuid); return; }
/// <summary>Attempts to get client a WebsocketClientInformation from the given GUID.</summary> /// <param name="guid"> Unique identifier.</param> /// <param name="client">[out] The client.</param> /// <returns>True if it succeeds, false if it fails.</returns> public static bool TryGetClient(Guid guid, out WebsocketClientInformation client) { if (guid == Guid.Empty) { client = null; return(false); } // check for this client existing if (_instance._guidInfoDict.ContainsKey(guid)) { // set our client object client = _instance._guidInfoDict[guid]; // success return(true); } // not found client = null; // failure return(false); }
/// <summary>Executes the asynchronous on a different thread, and waits for the result.</summary> /// <param name="context">The context.</param> /// <returns>An asynchronous result.</returns> public async Task InvokeAsync(HttpContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } // check for requests to our URL if (!context.Request.Path.Equals(_matchUrl, StringComparison.OrdinalIgnoreCase)) { // pass to next caller in chain await _nextDelegate.Invoke(context).ConfigureAwait(false); return; } // check for not being a WebSocket request if (!context.WebSockets.IsWebSocketRequest) { Console.WriteLine($" <<< Received non-websocket request at: {_matchUrl}"); context.Response.StatusCode = 400; return; } string payloadType = string.Empty; // check for specified payload type if (context.Request.Query.ContainsKey("payload-type")) { payloadType = context.Request.Query["payload-type"]; } // figure out the websocket payload type switch (payloadType) { case WebsocketClientInformation.WebsocketPayloadTypes.Empty: break; case WebsocketClientInformation.WebsocketPayloadTypes.IdOnly: break; case WebsocketClientInformation.WebsocketPayloadTypes.FullResource: break; case WebsocketClientInformation.WebsocketPayloadTypes.R4: break; default: // assume R4 payloadType = WebsocketClientInformation.WebsocketPayloadTypes.R4; break; } // create our management record WebsocketClientInformation client = new WebsocketClientInformation() { Uid = Guid.NewGuid(), PayloadType = payloadType, MessageQ = new ConcurrentQueue <string>(), SubscriptionIdSet = new HashSet <string>(), }; // accept this connection await AcceptAndProcessWebSocket(context, client).ConfigureAwait(false); }
/// <summary>Accept and process web socket.</summary> /// <param name="context">The context.</param> /// <param name="client"> The client.</param> /// <returns>An asynchronous result.</returns> private async Task AcceptAndProcessWebSocket( HttpContext context, WebsocketClientInformation client) { // prevent WebSocket errors from bubbling up try { // accept the connection using (var webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false)) { // register our client WebsocketManager.RegisterClient(client); // create a cancellation token source so we can cancel our read/write tasks CancellationTokenSource processCancelSource = new CancellationTokenSource(); // link our local close with the application lifetime close for simplicity CancellationToken cancelToken = CancellationTokenSource.CreateLinkedTokenSource( _applicationStopping, processCancelSource.Token).Token; Task[] webSocketTasks = new Task[2]; // create send task webSocketTasks[0] = Task.Run(async() => { try { await WriteClientMessages(webSocket, client.Uid, cancelToken).ConfigureAwait(false); } finally { // **** cancel read if write task has exited *** processCancelSource.Cancel(); } }); // create receive task webSocketTasks[1] = Task.Run(async() => { try { await ReadClientMessages(webSocket, client.Uid, cancelToken).ConfigureAwait(false); } finally { // cancel write if read task has exited processCancelSource.Cancel(); } }); // start tasks and wait for them to complete await Task.WhenAll(webSocketTasks).ConfigureAwait(false); // close our web socket (do not allow cancel) await webSocket.CloseAsync( WebSocketCloseStatus.EndpointUnavailable, "Connection closing", CancellationToken.None).ConfigureAwait(false); } } catch (WebSocketException wsEx) { // just log for now Console.WriteLine($" <<< caught exception: {wsEx.Message}"); } finally { WebsocketManager.UnregisterClient(client.Uid); } return; }