using FluentValidation;
using Shogi.Contracts.Socket;
using Shogi.Contracts.Types;
using Shogi.Api.Extensions;
using Shogi.Api.Managers;
using System.Net;
using System.Net.WebSockets;
using System.Text.Json;
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 = JsonSerializer.Deserialize(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;
}
}
}