public async Task <TRootMessage> ProcessRequest(IBroker broker, THttpContext context, TRootMessage request) { ASSERT(Broker == null, $"Property '{nameof(Broker)}' is NOT supposed to be set here"); // Has this method already been invoked ?? ASSERT(broker != null, $"Missing parameter '{nameof(broker)}'"); ASSERT(context != null, $"Missing parameter '{nameof(context)}'"); ASSERT(request != null, $"Missing parameter '{nameof(request)}'"); Broker = broker; // Check message type var messageType = (string)request.TryGet(RootMessageKeys.KeyType); LOG($"{nameof(ProcessRequest)} - Received message of type '" + messageType + "'"); switch (messageType) { case RootMessageKeys.TypeInit: { // Create an ID for this connection and send it to client ID = await ValidateInit(context, request); if (string.IsNullOrWhiteSpace(ID)) { throw new ArgumentNullException("ID"); } LOG($"ReceiveRequest() - Respond with 'init' message: {ID}"); var response = new CRootMessage { { RootMessageKeys.KeyType, RootMessageKeys.TypeInit }, { RootMessageKeys.KeySenderID, ID } }; return(response); } case RootMessageKeys.TypePoll: { // Validate ID for this connection ID = request.TryGetString(RootMessageKeys.KeySenderID); if (string.IsNullOrWhiteSpace(ID)) { throw new ArgumentNullException($"Missing '{RootMessageKeys.KeySenderID}' parameter from message"); } await ValidateInboundMessages(context, new List <TMessage>()); // This object will receive the 'RootMessage' to send to the client as response var completionSource = new TaskCompletionSource <TRootMessage>(); CompletionSource = completionSource; // Create a timeout to close this connection and avoid keeping it open too long var staleCancellation = new System.Threading.CancellationTokenSource(); var staleTimeout = Task.Run(async() => // nb: Run asynchroneously { await Task.Delay(StaleTimeoutMilisec, staleCancellation.Token); await ResetNow(); }); // Register against the broker try { await Broker.RegisterEndpoint(this); } catch (BrokerBase.EndpointAlreadyRegistered ex) { // This connection has already been registered? // CAN happen when the browser resets the polling connection (e.g. when typing 'ctrl+s' to save the page, all active requests are interrupted) // and the client restart the connection immediately. // But this should not happen often ; If it does, something's wrong FAIL($"Connection '{this.ID}' has already been registered"); // Unregister previous one await((HttpConnection <THttpContext>)ex.EndPoint).ResetNow(); // Retry register (ie. should succeed ...) await Broker.RegisterEndpoint(this); } // Wait for any messages to send through this connection var response = await completionSource.Task; // Cancel the stale timeout if still running staleCancellation.Cancel(); return(response); } case RootMessageKeys.TypeMessages: { // Validate ID for this connection ID = request.TryGetString(RootMessageKeys.KeySenderID); if (string.IsNullOrWhiteSpace(ID)) { throw new ArgumentNullException($"Missing '{RootMessageKeys.KeySenderID}' parameter from message"); } // Validate messages list var messages = ((IEnumerable)request[RootMessageKeys.KeyMessageMessages]) .Cast <IDictionary <string, object> >() .Select(v => { var message = Broker.NewMessage(); message.AddRange(v); if (string.IsNullOrWhiteSpace(message.TryGetString(MessageKeys.KeySenderID))) { message[MessageKeys.KeySenderID] = ID; } return(message); }) .ToList(); await ValidateInboundMessages(context, messages); foreach (var message in messages) { if (string.IsNullOrWhiteSpace(message.TryGetString(MessageKeys.KeyReceiverID))) { // There is no receiver defined for this message => Set the default one message[MessageKeys.KeyReceiverID] = DefaultReceiverID; if (string.IsNullOrWhiteSpace(message.TryGetString(MessageKeys.KeyReceiverID))) { // No default defined => Throw fatal exception ... throw new MissingFieldException($"The message has no '{MessageKeys.KeyReceiverID}' defined"); } } } // Give the messages to the Broker await Broker.ReceiveMessages(messages); // NB: The first idea was to send pending messages (if there were any available) through the "messages" request instead of waiting for the "polling" request to send them. // But since both requests could be sending concurrently, it would be hard for the client-side to receive all messages IN THE RIGHT ORDER // => Always reply with an empty response message list ; Don't send the pending messages var response = new CRootMessage { { RootMessageKeys.KeyType, RootMessageKeys.TypeMessages }, { RootMessageKeys.TypeMessages, new TMessage[] {} } }; return(response); } default: throw new NotImplementedException($"Unsupported root message type '{messageType}'"); } }