Esempio n. 1
0
        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}'");
            }
        }