Example #1
0
        protected async Task ReceiveMessages(TRootMessage rootMessage)
        {
            ASSERT((rootMessage != null) && (rootMessage.Count > 0), "Missing parameter 'rootMessage'");
            ASSERT((rootMessage[RootMessageKeys.KeyType] as string) == RootMessageKeys.TypeMessages, $"Invalid root message type '{rootMessage[RootMessageKeys.KeyType]}' ; expected '{RootMessageKeys.TypeMessages}'");
            ASSERT((rootMessage[RootMessageKeys.TypeMessages] as IEnumerable) != null, "Invalid content of root message");

            TMessage[] messages;
            try
            {
                var enumerable = (IEnumerable)rootMessage[RootMessageKeys.TypeMessages];
                messages = enumerable.Cast <TMessage>().ToArray();
            }
            catch (System.Exception ex)
            {
                await OnInternalError.Invoke("Could not parse messages list received from server", ex);

                return;
            }

            foreach (var message in messages)
            {
                try
                {
                    await MessageReceiver.ReceiveMessage(message);
                }
                catch (System.Exception ex)
                {
                    await OnHandlerException.Invoke(message, ex);
                }
            }
        }
Example #2
0
        private async Task <IDictionary <string, object> > SendHttpRequest(TRootMessage rootMessage, bool isPollConnection)
        {
            ASSERT((rootMessage != null) && (rootMessage.Count > 0), "Missing parameter 'rootMessage'");
            ASSERT(!string.IsNullOrWhiteSpace(HandlerUrl), "Property 'HandlerUrl' is supposed to be set here");

            string strResponse;

            try
            {
                using (var handler = new System.Net.Http.HttpClientHandler()
                {
                    CookieContainer = Cookies
                })
                    using (var client = new System.Net.Http.HttpClient(handler)
                    {
                        Timeout = System.Threading.Timeout.InfiniteTimeSpan
                    })
                    {
                        if (isPollConnection)
                        {
                            ASSERT(PollClient == null, "Property 'PollClient' is not supposed to be set here");
                            PollClient = client;

                            var status = Status;
                            switch (status)
                            {
                            case ConnectionStatus.Closing:
                            case ConnectionStatus.Disconnected:
                                throw new ArgumentException("Cannot create new connection while status is '" + status + "'");
                            }
                        }

                        var strMessage = rootMessage.ToJSON();
                        var content    = new System.Net.Http.StringContent(strMessage, Encoding.UTF8, "application/json");
                        var response   = await client.PostAsync(HandlerUrl, content);

                        LOG("Receive response content");
                        strResponse = await response.Content.ReadAsStringAsync();
                    }
            }
            finally
            {
                if (isPollConnection)
                {
                    ASSERT(PollClient != null, "Property 'PollClient' is supposed to be set here");
                    PollClient = null;
                }
            }
            var responseMessage = strResponse.FromJSONDictionary();

            return(responseMessage);
        }
Example #3
0
 internal protected abstract Task SendRootMessage(TRootMessage rootMessage);
Example #4
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}'");
            }
        }
Example #5
0
 /// <summary>Override to implement security ; Invoked when an 'init' message is received</summary>
 /// <remarks>Can throw an exception for invalid requests</remarks>
 /// <returns>A unique identifier for this connection</returns>
 protected virtual Task <string> ValidateInit(THttpContext context, TRootMessage requestMessage)
 {
     return(Task.FromResult(Guid.NewGuid().ToString()));
 }