~furry/mamoru-server

901ce1dda9b39316570ed00aad4c7123c28965cc — nora 2 months ago f272e30
finish route loading

refactor abstract route
M Connections/ServerLogConnection.cs => Connections/ServerLogConnection.cs +3 -1
@@ 1,9 1,11 @@
using WebSocketSharp;
using WebSocketSharp.Server;

namespace Mamoru.Routes;
namespace Mamoru.Connections;

public class ServerLogConnection : WebSocketBehavior {
  public static string Path = "/serverlog";
  
  protected override void OnMessage(MessageEventArgs e) {
    Send(e.Data);
    Log.Info(e.Data);

M Mamoru.cs => Mamoru.cs +3 -3
@@ 7,9 7,9 @@ using YamlDotNet.Serialization.NamingConventions;
namespace Mamoru;

public class Mamoru : Plugin<Config> {
  internal static Mamoru Instance { get; } = new();
  internal ConfigManager ConfigManager = null!;
  internal RoutesManager RoutesManager = null!;
  public static Mamoru Instance { get; } = new();
  public ConfigManager ConfigManager = null!;
  public RoutesManager RoutesManager = null!;

  public override string Name => "Mamoru";
  public override string Author => "furry";

M Mamoru.csproj => Mamoru.csproj +0 -1
@@ 11,7 11,6 @@
    <ItemGroup>
        <PackageReference Include="EXILED" Version="9.0.0-beta.2"/>
        <PackageReference Include="System.Text.Json" Version="9.0.0-rc.1.24431.7" />
        <PackageReference Include="Unity3D" Version="3.0.0"/>
        <PackageReference Include="WebSocketSharp" Version="1.0.3-rc11"/>
    </ItemGroup>


M Managers/RoutesManager.cs => Managers/RoutesManager.cs +58 -14
@@ 1,37 1,81 @@
using System.Net;
using System.Reflection;
using System.Text;
using Exiled.API.Extensions;
using Exiled.API.Features;
using Mamoru.Routes;
using Mamoru.Connections;
using Mamoru.Utils;
using WebSocketSharp.Server;

namespace Mamoru.Managers;

public class RoutesManager {
  private readonly HttpServer _httpServer = new(System.Net.IPAddress.Loopback, 4649);
  private readonly HttpServer _httpServer = new(IPAddress.Loopback, 4649);
  private readonly Dictionary<string, AbstractRoute> _httpRoutes = new();

  public RoutesManager() {
    StartHttpServer();
  }

  private void StartHttpServer() {
    Log.Info("Setting up HTTP server...");
    var types = Assembly.GetExecutingAssembly()
  private void HandleRequest(object sender, HttpRequestEventArgs ev) {
    // _httpServer.OnGet += (_, e) => {
    //   var req = e.Request;
    //   var res = e.Response;
    //
    //   var route = httpRoutes[req.RawUrl];
    //   if (route == null) {
    //     EmptyResponse.Create(ref res, HttpStatusCode.NotFound);
    //     return;
    //   }
    //
    //   route.Get(ref req, ref res);
    // };
    Log.Info($"{ev.Request.HttpMethod} {ev.Request.RawUrl}");
    Log.Info(string.Join(Environment.NewLine, _httpRoutes));
  }

  private void LoadRoutes() {
    Log.Info("Loading routes...");
    Assembly.GetExecutingAssembly()
      .GetTypes()
      .Where(type => type.IsClass && type is { IsAbstract: false, Namespace: "Mamoru.Routes" });
      .Where(type => type.IsClass && type is { IsAbstract: false, Namespace: "Mamoru.Routes" })
      .ToArray()
      .ForEach((type) => {
        try {
          var clazz = (AbstractRoute)Activator.CreateInstance(type);
          _httpRoutes[clazz.Path] = clazz;
          Log.Info($"Loaded http route {type.Name} at {clazz.Path}");
        }
        catch {
          Log.Warn($"Failed to load http route {type.Name}");
        }
      });

    foreach (var type in types) {
      Log.Info($"detected {type.Name}");
    }
    _httpServer.AddWebSocketService<ServerLogConnection>(ServerLogConnection.Path);
  }

    
    _httpServer.OnGet += (_, e) => { };
    _httpServer.AddWebSocketService<ServerLogConnection>("/");
  private void RegisterEvents() {
    var methodInfo = GetType().GetMethod("HandleRequest", BindingFlags.Instance | BindingFlags.NonPublic);
    _httpServer.GetType().GetEvents().ForEach(eventInfo => {
      eventInfo.AddEventHandler(_httpServer, Delegate.CreateDelegate(
        eventInfo.EventHandlerType,
        this,
        methodInfo!
      ));
    });
  }

  private void StartHttpServer() {
    Log.Info("Setting up HTTP server...");
    LoadRoutes();

    Log.Info("Registering HandleRequest events...");
    RegisterEvents();
    
    Log.Info("Starting HTTP server...");
    _httpServer.Start();
  }

  internal void StopHttpServer() {
  public void StopHttpServer() {
    Log.Info("Stopping HTTP server...");
    _httpServer.Stop();
  }

M Models.DedicatedServer/GameplayConfig.cs => Models.DedicatedServer/GameplayConfig.cs +1 -0
@@ 1,4 1,5 @@
using YamlDotNet.Serialization;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

namespace Mamoru.Models.DedicatedServer;


M Models.DedicatedServer/RemoteAdminConfig.cs => Models.DedicatedServer/RemoteAdminConfig.cs +1 -0
@@ 1,4 1,5 @@
using YamlDotNet.Serialization;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

namespace Mamoru.Models.DedicatedServer;


M Routes/StatusRoute.cs => Routes/StatusRoute.cs +2 -2
@@ 8,7 8,7 @@ public class StatusRoute : AbstractRoute {
  public override string Path => "/.well-known/mamoru/status";
  public override bool Cors => true;

  public override IResponse Get(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new JsonResponse(ref res, new Status(true, true, "1", "2"));
  public override void Get(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    JsonResponse.Create(ref res, new Status(true, true, "1", "2"));
  }
}

M Utils/AbstractRoute.cs => Utils/AbstractRoute.cs +18 -18
@@ 6,39 6,39 @@ public abstract class AbstractRoute {
  public abstract string Path { get; }
  public virtual bool Cors => false;

  public virtual IResponse Connect(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Connect(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Delete(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Delete(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Get(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Get(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Head(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Head(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Options(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Options(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Post(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Post(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Patch(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Patch(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Put(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Put(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }

  public virtual IResponse Trace(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    return new EmptyResponse(ref res, HttpStatusCode.MethodNotAllowed);
  public virtual void Trace(ref HttpListenerRequest req, ref HttpListenerResponse res) {
    EmptyResponse.Create(ref res, HttpStatusCode.MethodNotAllowed);
  }
}
\ No newline at end of file

M Utils/Response.cs => Utils/Response.cs +4 -6
@@ 4,10 4,8 @@ using WebSocketSharp.Net;

namespace Mamoru.Utils;

public interface IResponse;

public sealed class JsonResponse : IResponse {
  public JsonResponse(ref HttpListenerResponse res, object value, HttpStatusCode statusCode = HttpStatusCode.OK) {
public static class JsonResponse {
  public static void Create(ref HttpListenerResponse res, object value, HttpStatusCode statusCode = HttpStatusCode.OK) {
    var data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value));
    res.StatusCode = (int)statusCode;
    res.ContentType = "application/json";


@@ 17,8 15,8 @@ public sealed class JsonResponse : IResponse {
  }
}

public sealed class EmptyResponse : IResponse {
  public EmptyResponse(ref HttpListenerResponse res, HttpStatusCode statusCode = HttpStatusCode.OK) {
public static class EmptyResponse {
  public static void Create(ref HttpListenerResponse res, HttpStatusCode statusCode = HttpStatusCode.OK) {
    res.StatusCode = (int)statusCode;
    res.ContentLength64 = 0;
    res.Close();