using FluentValidation; using Newtonsoft.Json; using Shogi.Contracts.Socket; using Shogi.Contracts.Types; using Shogi.Api.Extensions; using Shogi.Api.Managers; using Shogi.Api.Repositories; using System.Net; using System.Net.WebSockets; namespace Shogi.Api.Services { public interface ISocketService { Task HandleSocketRequest(HttpContext context); } /// /// Services a single websocket connection. Authenticates the socket connection, accepts messages, and sends messages. /// public class SocketService : ISocketService { private readonly ILogger logger; private readonly ISocketConnectionManager communicationManager; private readonly ISocketTokenCache tokenManager; public SocketService( ILogger logger, ISocketConnectionManager communicationManager, ISocketTokenCache tokenManager) : base() { this.logger = logger; this.communicationManager = communicationManager; this.tokenManager = tokenManager; } public async Task HandleSocketRequest(HttpContext context) { if (!context.Request.Query.Keys.Contains("token")) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return; } var token = Guid.Parse(context.Request.Query["token"][0]); var userName = tokenManager.GetUsername(token); if (string.IsNullOrEmpty(userName)) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; return; } var socket = await context.WebSockets.AcceptWebSocketAsync(); communicationManager.Subscribe(socket, userName); while (socket.State == WebSocketState.Open) { try { var message = await socket.ReceiveTextAsync(); if (string.IsNullOrWhiteSpace(message)) continue; logger.LogInformation("Request \n{0}\n", message); var request = JsonConvert.DeserializeObject(message); if (request == null || !Enum.IsDefined(typeof(SocketAction), request.Action)) { await socket.SendTextAsync("Error: Action not recognized."); continue; } switch (request.Action) { default: await socket.SendTextAsync($"Received your message with action {request.Action}, but did no work."); break; } } catch (OperationCanceledException ex) { logger.LogError(ex.Message); } catch (WebSocketException ex) { logger.LogInformation($"{nameof(WebSocketException)} in {nameof(SocketConnectionManager)}."); logger.LogInformation("Probably tried writing to a closed socket."); logger.LogError(ex.Message); } communicationManager.Unsubscribe(userName); } } public async Task ValidateRequestAndReplyIfInvalid(WebSocket socket, IValidator validator, TRequest request) { var results = validator.Validate(request); if (!results.IsValid) { var errors = string.Join('\n', results.Errors.Select(_ => _.ErrorMessage)); await socket.SendTextAsync(errors); } return results.IsValid; } } }