/// <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, null, socketPath); } catch (AggregateException ae) when(ae.InnerException is IncompatibleVersionException) { _logger.LogError($"[{nameof(WebSocketController)}] Incompatible DCS version"); await CloseConnection(webSocket, WebSocketCloseStatus.InternalServerError, "Incompatible DCS version"); return; } catch (SocketException) { _logger.LogError($"[{nameof(WebSocketController)}] DCS is unavailable"); await CloseConnection(webSocket, WebSocketCloseStatus.EndpointUnavailable, "DCS is unavailable"); 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 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.GetSerializedMachineModel()) { await webSocket.SendAsync(json.ToArray(), WebSocketMessageType.Text, true, default); } // 4c. Deal with this connection in full-duplex mode using CancellationTokenSource cts = new CancellationTokenSource(); 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); cts.Cancel(); if (terminatedTask.IsFaulted) { throw terminatedTask.Exception; } } catch (Exception e) { _logger.LogError(e, "WebSocket from {0}:{1} terminated with an exception", ipAddress, port); await CloseConnection(webSocket, WebSocketCloseStatus.InternalServerError, e.Message); } finally { _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"); } } } }
public static async Task Main(string[] args) { // Parse the command line arguments string lastArg = null, codeToExecute = null, socketPath = Defaults.FullSocketPath; bool quiet = false; foreach (string arg in args) { if (lastArg == "-s" || lastArg == "--socket") { socketPath = arg; } else if (lastArg == "-c" || lastArg == "-c") { codeToExecute = arg; } else if (arg == "-q" || arg == "--quiet") { quiet = true; } else if (arg == "-h" || arg == "--help") { Console.WriteLine("Available command line arguments:"); Console.WriteLine("-s, --socket <socket>: UNIX socket to connect to"); Console.WriteLine("-c, --code <code>: Execute the given code(s), wait for the result and exit"); Console.WriteLine("-q, --quiet: Do not display when a connection has been established (only applicable in interactive mode)"); Console.WriteLine("-h, --help: Display this help text"); return; } lastArg = arg; } // Create a new connection and connect to DuetControlServer using CommandConnection connection = new CommandConnection(); await connection.Connect(socketPath); // Check if this is an interactive session if (codeToExecute == null) { if (!quiet) { // Notify the user that a connection has been established Console.WriteLine("Connected!"); } // Register an (interactive) user session int sessionId = await connection.AddUserSession(DuetAPI.Machine.AccessLevel.ReadWrite, DuetAPI.Machine.SessionType.Local, "console"); // Start reading lines from stdin and send them to DCS as simple codes. // When the code has finished, the result is printed to stdout string input = Console.ReadLine(); while (input != null && input != "exit" && input != "quit") { try { string output = await connection.PerformSimpleCode(input); if (output.EndsWith(Environment.NewLine)) { Console.Write(output); } else { Console.WriteLine(output); } } catch (SocketException) { Console.WriteLine("Server has closed the connection"); break; } catch (Exception e) { if (e is AggregateException ae) { e = ae.InnerException; } Console.WriteLine(e.Message); } input = Console.ReadLine(); } // Unregister this session again if (connection.IsConnected) { await connection.RemoveUserSession(sessionId); } } else { // Execute only the given code(s) and quit string output = await connection.PerformSimpleCode(codeToExecute); if (output.EndsWith('\n')) { Console.Write(output); } else { Console.WriteLine(output); } } }
public static async Task Main(string[] args) { // Parse the command line arguments string lastArg = null, codeToExecute = null, socketPath = Defaults.FullSocketPath; bool quiet = false; foreach (string arg in args) { if (lastArg == "-s" || lastArg == "--socket") { socketPath = arg; } else if (lastArg == "-c" || lastArg == "-c") { codeToExecute = arg; } else if (arg == "-q" || arg == "--quiet") { quiet = true; } else if (arg == "-h" || arg == "--help") { Console.WriteLine("Available command line arguments:"); Console.WriteLine("-s, --socket <socket>: UNIX socket to connect to"); Console.WriteLine("-c, --code <code>: Execute the given code(s), wait for the result and exit. Alternative codes: startUpdate (Set DSF to updating), endUpdate (End DSF updating state)"); Console.WriteLine("-q, --quiet: Do not output any messages (not applicable for code replies in interactive mode)"); Console.WriteLine("-h, --help: Display this help text"); return; } lastArg = arg; } // Create a new connection and connect to DuetControlServer using CommandConnection connection = new CommandConnection(); await connection.Connect(socketPath); // Check if this is an interactive session if (codeToExecute == null) { if (!quiet) { // Notify the user that a connection has been established Console.WriteLine("Connected!"); } // Register an (interactive) user session int sessionId = await connection.AddUserSession(DuetAPI.ObjectModel.AccessLevel.ReadWrite, DuetAPI.ObjectModel.SessionType.Local, "console"); // Start reading lines from stdin and send them to DCS as simple codes. // When the code has finished, the result is printed to stdout string input = Console.ReadLine(); while (input != null && input != "exit" && input != "quit") { try { if (input.Equals("startUpdate", StringComparison.InvariantCultureIgnoreCase)) { await connection.SetUpdateStatus(true); Console.WriteLine("DSF is now in update mode"); } else if (input.Equals("endUpdate", StringComparison.InvariantCultureIgnoreCase)) { await connection.SetUpdateStatus(false); Console.WriteLine("DSF is no longer in update mode"); } else if (input.StartsWith("eval ", StringComparison.InvariantCultureIgnoreCase)) { JsonElement result = await connection.EvaluateExpression <JsonElement>(input[5..].Trim());