コード例 #1
0
        /// <summary>
        /// Keep writing object model updates to the client
        /// </summary>
        /// <param name="webSocket">WebSocket to write to</param>
        /// <param name="subscribeConnection">IPC connection to supply model updates</param>
        /// <param name="dataAcknowledged">Event that is triggered when the client has acknowledged data</param>
        /// <param name="cancellationToken">Cancellation token</param>
        /// <returns>Asynchronous task</returns>
        private async Task WriteToClient(WebSocket webSocket, SubscribeConnection subscribeConnection, AsyncAutoResetEvent dataAcknowledged, CancellationToken cancellationToken)
        {
            do
            {
                // Wait for the client to acknowledge the receipt of the last JSON object
                await dataAcknowledged.WaitAsync(cancellationToken);

                if (cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                // Wait for another object model update and send it to the client
                using MemoryStream objectModelPatch = await subscribeConnection.GetSerializedObjectModel(cancellationToken);

                await webSocket.SendAsync(objectModelPatch.ToArray(), WebSocketMessageType.Text, true, cancellationToken);
            }while (webSocket.State == WebSocketState.Open);
        }
コード例 #2
0
        /// <summary>
        /// Deal with a newly opened WebSocket.
        /// A client may receive one of the WS codes: (1001) Endpoint unavailable (1003) Invalid command (1011) Internal error
        /// </summary>
        /// <param name="webSocket">WebSocket connection</param>
        /// <returns>Asynchronous task</returns>
        public async Task Process(WebSocket webSocket)
        {
            string socketPath = _configuration.GetValue("SocketPath", Defaults.FullSocketPath);

            // 1. Authentification. This will require an extra API command
            using CommandConnection commandConnection = new CommandConnection();
            // TODO

            // 2. Connect to DCS
            using SubscribeConnection subscribeConnection = new SubscribeConnection();
            try
            {
                // Subscribe to object model updates
                await subscribeConnection.Connect(SubscriptionMode.Patch, Array.Empty <string>(), socketPath);
            }
            catch (Exception e)
            {
                if (e is AggregateException ae)
                {
                    e = ae.InnerException;
                }
                if (e is IncompatibleVersionException)
                {
                    _logger.LogError($"[{nameof(WebSocketController)}] Incompatible DCS version");
                    await CloseConnection(webSocket, WebSocketCloseStatus.InternalServerError, "Incompatible DCS version");

                    return;
                }
                if (e is SocketException)
                {
                    _logger.LogError($"[{nameof(WebSocketController)}] DCS is not started");
                    await CloseConnection(webSocket, WebSocketCloseStatus.EndpointUnavailable, "Failed to connect to Duet, please check your connection (DCS is not started)");

                    return;
                }
                _logger.LogError(e, $"[{nameof(WebSocketController)}] Failed to connect to DCS");
                await CloseConnection(webSocket, WebSocketCloseStatus.EndpointUnavailable, e.Message);

                return;
            }

            // 3. Log this event
            string ipAddress = HttpContext.Connection.RemoteIpAddress.ToString();
            int    port      = HttpContext.Connection.RemotePort;

            _logger.LogInformation("WebSocket connected from {0}:{1}", ipAddress, port);

            // 4. Register this client and keep it up-to-date
            using CancellationTokenSource cts = new CancellationTokenSource();
            int sessionId = -1;

            try
            {
                // 4a. Register this user session. Once authentification has been implemented, the access level may vary
                await commandConnection.Connect(socketPath);

                sessionId = await commandConnection.AddUserSession(AccessLevel.ReadWrite, SessionType.HTTP, ipAddress, port);

                // 4b. Fetch full model copy and send it over initially
                using (MemoryStream json = await subscribeConnection.GetSerializedObjectModel())
                {
                    await webSocket.SendAsync(json.ToArray(), WebSocketMessageType.Text, true, default);
                }

                // 4c. Deal with this connection in full-duplex mode
                AsyncAutoResetEvent dataAcknowledged = new AsyncAutoResetEvent();
                Task rxTask = ReadFromClient(webSocket, dataAcknowledged, cts.Token);
                Task txTask = WriteToClient(webSocket, subscribeConnection, dataAcknowledged, cts.Token);

                // 4d. Deal with the tasks' lifecycles
                Task terminatedTask = await Task.WhenAny(rxTask, txTask);

                if (terminatedTask.IsFaulted)
                {
                    throw terminatedTask.Exception;
                }
            }
            catch (Exception e)
            {
                if (e is AggregateException ae)
                {
                    e = ae.InnerException;
                }
                if (e is SocketException)
                {
                    _logger.LogError($"[{nameof(WebSocketController)}] DCS has been stopped");
                    await CloseConnection(webSocket, WebSocketCloseStatus.EndpointUnavailable, "DCS has been stopped");
                }
                else if (e is OperationCanceledException)
                {
                    await CloseConnection(webSocket, WebSocketCloseStatus.EndpointUnavailable, "DWS is shutting down");
                }
                else
                {
                    _logger.LogError(e, $"[{nameof(WebSocketController)}] Connection from {ipAddress}:{port} terminated with an exception");
                    await CloseConnection(webSocket, WebSocketCloseStatus.InternalServerError, e.Message);
                }
            }
            finally
            {
                cts.Cancel();
                _logger.LogInformation("WebSocket disconnected from {0}:{1}", ipAddress, port);
                try
                {
                    // Try to remove this user session again
                    await commandConnection.RemoveUserSession(sessionId);
                }
                catch (Exception e)
                {
                    if (!(e is SocketException))
                    {
                        _logger.LogError(e, "Failed to unregister user session");
                    }
                }
            }
        }