105 lines
4.1 KiB
C#
105 lines
4.1 KiB
C#
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Services a single websocket connection. Authenticates the socket connection, accepts messages, and sends messages.
|
|
/// </summary>
|
|
public class SocketService : ISocketService
|
|
{
|
|
private readonly ILogger<SocketService> logger;
|
|
private readonly ISocketConnectionManager communicationManager;
|
|
private readonly ISocketTokenCache tokenManager;
|
|
|
|
public SocketService(
|
|
ILogger<SocketService> 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] ?? throw new InvalidOperationException("Token expected during socket connection request, but was not sent."));
|
|
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);
|
|
// TODO: I probably don't need this while-loop anymore? Perhaps unsubscribe when a disconnect is detected instead.
|
|
while (socket.State.HasFlag(WebSocketState.Open))
|
|
{
|
|
try
|
|
{
|
|
var message = await socket.ReceiveTextAsync();
|
|
if (string.IsNullOrWhiteSpace(message)) continue;
|
|
logger.LogInformation("Request \n{0}\n", message);
|
|
var request = JsonSerializer.Deserialize<ISocketMessage>(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);
|
|
|
|
if (!socket.State.HasFlag(WebSocketState.Closed) && !socket.State.HasFlag(WebSocketState.Aborted))
|
|
{
|
|
try
|
|
{
|
|
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure,
|
|
"Socket closed",
|
|
CancellationToken.None);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Ignored exception during socket closing. {ex.Message}");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|