Added Socket support

This commit is contained in:
2024-12-18 23:58:13 +02:00
parent 424bf2196f
commit c79c792c43
14 changed files with 251 additions and 11 deletions

View File

@@ -0,0 +1,16 @@
using DiscordBotCore.Interfaces.API;
namespace DiscordBotCore.API;
public class ConnectionDetails : IConnectionDetails
{
public string Host { get; }
public int Port { get; }
public ConnectionDetails(string host, int port)
{
Host = host;
Port = port;
}
}

View File

@@ -7,7 +7,7 @@ using DiscordBotCore.Interfaces.API;
using DiscordBotCore.Others;
using Microsoft.AspNetCore.Builder;
namespace DiscordBotCore.API;
namespace DiscordBotCore.API.Endpoints;
public class ApiManager
{
@@ -52,7 +52,7 @@ public class ApiManager
return this.ApiEndpoints.Exists(endpoint => endpoint.Path == endpointPath);
}
public async Task InitializeApi()
public async void InitializeApi()
{
if (IsRunning)
return;

View File

@@ -1,7 +1,7 @@
using System.Threading.Tasks;
using DiscordBotCore.Others;
namespace DiscordBotCore.API;
namespace DiscordBotCore.API.Endpoints;
public class ApiResponse
{

View File

@@ -4,7 +4,7 @@ using DiscordBotCore.Interfaces.API;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace DiscordBotCore.API;
namespace DiscordBotCore.API.Endpoints;
internal sealed class EndpointManager
{

View File

@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using DiscordBotCore.API.Sockets.Sockets;
using DiscordBotCore.Interfaces.API;
namespace DiscordBotCore.API.Sockets;
internal class SocketManager
{
private readonly IConnectionDetails _ConnectionDetails;
private List<ISocket> _Sockets = new List<ISocket>();
public SocketManager(IConnectionDetails connectionDetails)
{
_ConnectionDetails = connectionDetails;
}
public void RegisterBaseSockets()
{
Register(new PluginDownloadProgressSocket());
}
public bool Register(ISocket socket)
{
if (_Sockets.Any(s => s.Path == socket.Path))
{
return false;
}
_Sockets.Add(socket);
return true;
}
public void Start()
{
Console.WriteLine("Starting sockets ...");
foreach (var socket in _Sockets)
{
Thread thread = new Thread(() => StartSocket(socket));
thread.Start();
}
}
private async void StartSocket(ISocket socket)
{
if (!socket.Path.StartsWith("/"))
{
throw new ArgumentException($"Socket path '{socket.Path}' must start with '/'.");
}
string prefix = $"http://{_ConnectionDetails.Host}:{_ConnectionDetails.Port}{socket.Path}/";
Console.WriteLine($"Starting socket with prefix: {prefix}");
HttpListener listener = new HttpListener();
listener.Prefixes.Add(prefix);
listener.Start();
await ConnectionHandler(listener, socket.HandleRequest);
}
private async Task ConnectionHandler(HttpListener listener, Func<byte[], int, Task<SocketResponse>> handler)
{
while (true)
{
var context = await listener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
WebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
Application.CurrentApplication.Logger.Log("WebSocket connection established.");
await HandleSocket(webSocketContext.WebSocket, handler);
}
else { break; }
}
}
private async Task HandleSocket(WebSocket socket, Func<byte[], int, Task<SocketResponse>> handler)
{
if (socket.State != WebSocketState.Open)
{
return;
}
byte[] buffer = new byte[1024 * 4];
var receivedData = new List<byte>();
WebSocketReceiveResult result;
do
{
result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
receivedData.AddRange(buffer.Take(result.Count));
} while (!result.EndOfMessage);
if (result.MessageType == WebSocketMessageType.Close)
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection ...", CancellationToken.None);
Application.CurrentApplication.Logger.Log("WebSocket connection closed.");
return;
}
Application.CurrentApplication.Logger.Log("WebSocket message received. Length: " + receivedData.Count);
SocketResponse socketResponse = await handler(receivedData.ToArray(), receivedData.Count);
ArraySegment<byte> response = new ArraySegment<byte>(socketResponse.Data, 0, socketResponse.Data.Length);
byte[]? lastResponse = null;
while (!socketResponse.CloseConnectionAfterResponse)
{
if (lastResponse == null || !socketResponse.Data.SequenceEqual(lastResponse))
{
await socket.SendAsync(response, WebSocketMessageType.Text, socketResponse.EndOfMessage, CancellationToken.None);
lastResponse = socketResponse.Data;
}
socketResponse = await handler(receivedData.ToArray(), receivedData.Count);
response = new ArraySegment<byte>(socketResponse.Data, 0, socketResponse.Data.Length);
}
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection ...", CancellationToken.None);
}
}

View File

@@ -0,0 +1,38 @@
namespace DiscordBotCore.API.Sockets;
internal class SocketResponse
{
public byte[] Data { get;}
public bool EndOfMessage { get; }
public bool Success { get; }
public bool CloseConnectionAfterResponse { get; set; }
private SocketResponse(byte[] data, bool endOfMessage, bool success, bool closeConnectionAfterResponse)
{
Data = data;
EndOfMessage = endOfMessage;
Success = success;
CloseConnectionAfterResponse = closeConnectionAfterResponse;
}
internal static SocketResponse From(byte[] data, bool endOfMessage, bool success, bool closeConnectionAfterResponse)
{
return new SocketResponse(data, endOfMessage, success, closeConnectionAfterResponse);
}
internal static SocketResponse From(byte[] data, bool endOfMessage)
{
return new SocketResponse(data, endOfMessage, true, false);
}
internal static SocketResponse From(byte[] data)
{
return new SocketResponse(data, true, true, false);
}
internal static SocketResponse Fail()
{
return new SocketResponse(new byte[0], true, false, false);
}
}

View File

@@ -0,0 +1,20 @@
using System.Text;
using System.Threading.Tasks;
using DiscordBotCore.Interfaces.API;
namespace DiscordBotCore.API.Sockets.Sockets;
internal class PluginDownloadProgressSocket : ISocket
{
private float value = 0.0f;
public string Path => "/plugin/download/progress";
public Task<SocketResponse> HandleRequest(byte[] request, int count)
{
value += 0.1f;
string pluginName = Encoding.UTF8.GetString(request, 0, count);
Application.CurrentApplication.Logger.Log($"Received plugin download progress for {pluginName}.");
SocketResponse response = SocketResponse.From(Encoding.UTF8.GetBytes(value.ToString()));
response.CloseConnectionAfterResponse = value > 1.0f;
return Task.FromResult(response);
}
}

View File

@@ -4,6 +4,8 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using DiscordBotCore.API;
using DiscordBotCore.API.Endpoints;
using DiscordBotCore.API.Sockets;
using DiscordBotCore.Bot;
using DiscordBotCore.Interfaces.Logger;
using DiscordBotCore.Online;
@@ -46,7 +48,8 @@ namespace DiscordBotCore
public InternalActionManager InternalActionManager { get; private set; } = null!;
public PluginManager PluginManager { get; private set; } = null!;
public ILogger Logger { get; private set; } = null!;
public ApiManager? ApiManager { get; private set; }
internal ApiManager? ApiManager { get; private set; }
internal SocketManager? SocketManager { get; private set; }
/// <summary>
/// Create the application. This method is used to initialize the application. Can not initialize multiple times.
@@ -113,9 +116,24 @@ namespace DiscordBotCore
CurrentApplication.ApiManager = new ApiManager();
CurrentApplication.ApiManager.AddBaseEndpoints();
Thread apiThread = new Thread(() => _ = CurrentApplication.ApiManager.InitializeApi());
apiThread.Start();
CurrentApplication.ApiManager.InitializeApi();
}
public static void InitializeThreadedSockets()
{
if (CurrentApplication is null)
{
return;
}
if(CurrentApplication.SocketManager is not null)
{
return;
}
CurrentApplication.SocketManager = new SocketManager(new ConnectionDetails("localhost", 5055));
CurrentApplication.SocketManager.RegisterBaseSockets();
CurrentApplication.SocketManager.Start();
}
public static string GetResourceFullPath(string path)

View File

@@ -0,0 +1,7 @@
namespace DiscordBotCore.Interfaces.API;
public interface IConnectionDetails
{
public string Host { get; }
public int Port { get; }
}

View File

@@ -1,6 +1,5 @@
using System.Threading.Tasks;
using DiscordBotCore.API;
using DiscordBotCore.Others;
using DiscordBotCore.API.Endpoints;
namespace DiscordBotCore.Interfaces.API;

View File

@@ -0,0 +1,11 @@
using System.Net.WebSockets;
using System.Threading.Tasks;
using DiscordBotCore.API.Sockets;
namespace DiscordBotCore.Interfaces.API;
internal interface ISocket
{
public string Path { get; }
public Task<SocketResponse> HandleRequest(byte[] request, int count);
}