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); } } }
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); }
internal protected abstract Task SendRootMessage(TRootMessage rootMessage);
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}'"); } }
/// <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())); }