using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Reflection; using System.Text; using HarmonyLib; using Svelto.DataStructures; namespace CLre_server.WebStatus { public class WebServer { private const uint DEFAULT_PORT = 5030; private const string DEFAULT_IP = "localhost"; private readonly HttpListener _httpListener; public delegate void RequestHandler(HttpListenerContext ctx); private Dictionary _handlers = new Dictionary(); public bool IsRunning { get => _httpListener.IsListening; } private string _ip_addr; private uint _port; public static WebServer MainInstance { get; internal set; } internal static List _assembliesToCheck = new List(new []{typeof(CLre).Assembly}); public WebServer() : this(DEFAULT_IP, DEFAULT_PORT) { } public WebServer(string ip, uint port) { if (!HttpListener.IsSupported) { API.Utility.Logging.LogWarning("HTTP Server is unsupported on earlier Windows versions. It will fail to start."); } _httpListener = new HttpListener(); _httpListener.Prefixes.Add($"http://{ip}:{port}/"); _ip_addr = ip; _port = port; } internal static void Init() { if (CLre.Config.web_server || Environment.GetCommandLineArgs().Contains("-web")) { API.Utility.Logging.Log("Starting status web server"); StatusEndpoints.Init(); MainInstance = new WebServer(); MainInstance.Start(); } else { API.Utility.Logging.Log("Not starting web server (use CLI argument -web to enable)"); } } internal static void Deinit() { if (MainInstance != null) MainInstance.Stop(); MainInstance = null; } public void Start() { LoadHandlers(); try { _httpListener.Start(); } catch (Exception e) { API.Utility.Logging.LogWarning(e); return; } HandleAllRequests().Run(); } public void Stop() { try { _httpListener.Stop(); _httpListener.Close(); } catch (Exception e) { API.Utility.Logging.LogWarning(e); return; } } private IEnumerator HandleAllRequests() { API.Utility.Logging.MetaLog($"Started HTTP web server at http://{_ip_addr}:{_port}/"); while (_httpListener.IsListening) { var awaiter = _httpListener.GetContextAsync(); awaiter.GetAwaiter().OnCompleted(() => DoRequest(awaiter.Result)); yield return null; } API.Utility.Logging.MetaLog("Terminated HTTP web server"); } private void DoRequest(HttpListenerContext ctx) { string endpoint = ctx.Request.Url.LocalPath.ToLower(); #if DEBUG API.Utility.Logging.LogWarning($"Handling HTTP request {endpoint}"); #endif bool handled = false; foreach (string path in _handlers.Keys) { if (endpoint == path) { handled = true; _handlers[path](ctx); break; } } if (!handled) { AssetEndpoints.Asset404(ctx); } //byte[] output = Encoding.UTF8.GetBytes(endpoint); //ctx.Response.OutputStream.Write(output, 0, output.Length); ctx.Response.Close(); } private void LoadHandlers() { _handlers = new Dictionary(); foreach (Assembly asm in _assembliesToCheck.ToArray()) { foreach (Type t in asm.GetTypes()) { foreach (MethodInfo m in t.GetMethods(AccessTools.all)) { WebEndpointAttribute attr = m.GetCustomAttribute(); if (attr != null) { // TODO validate that method signature matches that of RequestHandler string key = attr.GetPath().ToLower(); API.Utility.Logging.MetaLog($"{t.FullName}:{m.Name} is handling {key}"); _handlers.Add(key, (RequestHandler) Delegate.CreateDelegate(typeof(RequestHandler), m)); } } } } } } }