From a03fafebcf87a1331b5a510a63eee69a0fcc9381 Mon Sep 17 00:00:00 2001 From: GHXX Date: Tue, 9 Jan 2024 06:05:15 +0100 Subject: [PATCH] wip refactor --- .../{HttpEndpoint.cs => HttpRoute.cs} | 10 +-- SimpleHttpServer/HttpServer.cs | 69 +++++++++++++++++-- SimpleHttpServer/Logger.cs | 47 +++++++++++++ SimpleHttpServer/RequestContext.cs | 15 ++++ 4 files changed, 132 insertions(+), 9 deletions(-) rename SimpleHttpServer/{HttpEndpoint.cs => HttpRoute.cs} (59%) create mode 100644 SimpleHttpServer/Logger.cs create mode 100644 SimpleHttpServer/RequestContext.cs diff --git a/SimpleHttpServer/HttpEndpoint.cs b/SimpleHttpServer/HttpRoute.cs similarity index 59% rename from SimpleHttpServer/HttpEndpoint.cs rename to SimpleHttpServer/HttpRoute.cs index 8e673a9..56cda41 100644 --- a/SimpleHttpServer/HttpEndpoint.cs +++ b/SimpleHttpServer/HttpRoute.cs @@ -3,20 +3,20 @@ namespace SimpleHttpServer; [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -public class HttpEndpoint : Attribute where T : IAuthorizer { +public class HttpRoute : Attribute where T : IAuthorizer { - public HttpRequestType Type { get; private set; } + public HttpRequestType RequestMethod { get; private set; } public string Location { get; private set; } public Type Authorizer { get; private set; } - public HttpEndpoint(HttpRequestType type, string location) { - Type = type; + public HttpRoute(HttpRequestType requestMethod, string location) { + RequestMethod = requestMethod; Location = location; Authorizer = typeof(T); } } [AttributeUsage(AttributeTargets.Method)] -public class HttpEndpoint : HttpEndpoint { +public class HttpEndpoint : HttpRoute { public HttpEndpoint(HttpRequestType type, string location) : base(type, location) { } } diff --git a/SimpleHttpServer/HttpServer.cs b/SimpleHttpServer/HttpServer.cs index 6c98a26..b6fc876 100644 --- a/SimpleHttpServer/HttpServer.cs +++ b/SimpleHttpServer/HttpServer.cs @@ -6,7 +6,60 @@ namespace SimpleHttpServer; public sealed class HttpServer { - private Thread? _listenerThread; + public int Port { get; } + + private readonly CancellationTokenSource ctokSrc; + + private readonly HttpListener listener; + private Task listenerTask; + private Logger logger; + + public HttpServer(int port, TextWriter? logRedirect = null) { + ctokSrc = new(); + Port = port; + listener = new HttpListener(); + listener.Prefixes.Add($"http://localhost:{port}/"); + logger = new("HttpServer", logRedirect); + } + + public async Task StartAsync() { + logger.Information($"Starting on port {Port}..."); + listener.Start(); + listenerTask = Task.Run(GetContextLoop); + logger.Information($"Ready to handle requests!"); + + await Task.Yield(); + } + + public async Task GetContextLoop() { + while (true) { + try { + var ctx = await listener.GetContextAsync(); + _ = ProcessRequestAsync(ctx); + } catch (Exception ex) { + + } finally { + + } + } + } + + private async Task ProcessRequestAsync(HttpListenerContext ctx) { + + } + + + private readonly Dictionary<(string path, HttpRequestType rType), Action> simpleEndpoints = new(); + public void RegisterRoutesFromType() { + var t = typeof(T); + foreach (var (mi, attrib) in t.GetMethods() + .ToDictionary(x => x, x => x.GetCustomAttributes(typeof(HttpRoute<>))) + .Where(x => x.Value.Any()).ToDictionary(x => x.Key, x => x.Value.Single() as HttpRoute ?? throw new InvalidCastException())) + { + simpleEndpoints.Add((attrib.Location, attrib.RequestMethod), mi.CreateDelegate>()); + } + } + private readonly HttpListener _listener; private readonly Dictionary<(string path, HttpRequestType rType), HttpEndpointHandler> _plainEndpoints = new(); private readonly Dictionary<(string path, HttpRequestType rType), HttpEndpointHandler> _pparamEndpoints = new(); @@ -22,8 +75,8 @@ public sealed class HttpServer { foreach (var definition in apiDefinitions) { foreach (var endpoint in definition.GetMethods()) { var attrib = endpoint.GetCustomAttributes() - .Where(x => x.GetType().IsAssignableTo(typeof(HttpEndpoint<>))) - .Select(x => (HttpEndpoint) x) + .Where(x => x.GetType().IsAssignableTo(typeof(HttpRoute<>))) + .Select(x => (HttpRoute) x) .SingleOrDefault(); if (attrib == null) { @@ -64,6 +117,14 @@ public sealed class HttpServer { Shutdown(-1); } + public bool Shutdown() { + if (_listenerThread == null) + throw new InvalidOperationException("Cannot shut down HttpServer that has not been started"); + + + + } + public bool Shutdown(int timeout) { if (_listenerThread == null) { throw new InvalidOperationException("Cannot shutdown HttpServer that has not been started"); @@ -89,7 +150,7 @@ public sealed class HttpServer { private void RunServer() { try { - for (; ; ) { + while (true) { var ctx = _listener.GetContext(); ThreadPool.QueueUserWorkItem((localCtx) => { diff --git a/SimpleHttpServer/Logger.cs b/SimpleHttpServer/Logger.cs new file mode 100644 index 0000000..84da54d --- /dev/null +++ b/SimpleHttpServer/Logger.cs @@ -0,0 +1,47 @@ +using System.Diagnostics; + +namespace SimpleHttpServer; +public class Logger { + private readonly string topic; + private readonly TextWriter outWriter; + + internal Logger(string topic) : this(topic, Console.Out) { } + internal Logger(string topic, TextWriter? outWriter) { + this.topic = topic; + this.outWriter = outWriter ?? Console.Out; + } + + private readonly object writeLock = new object(); + public void Log(string message, LogOutputLevel level) { + var fgColor = level switch { + LogOutputLevel.Debug => ConsoleColor.Gray, + LogOutputLevel.Information => ConsoleColor.White, + LogOutputLevel.Warning => ConsoleColor.Yellow, + LogOutputLevel.Error => ConsoleColor.Red, + LogOutputLevel.Fatal => ConsoleColor.Magenta, + _ => throw new NotImplementedException(), + }; + + lock (writeLock) { + var origColor = Console.ForegroundColor; + Console.ForegroundColor = fgColor; + outWriter.WriteLine($"[{topic}] {message}"); + Console.ForegroundColor = origColor; + } + } + + [Conditional("DEBUG")] + public void Debug(string message) => Log(message, LogOutputLevel.Debug); + public void Information(string message) => Log(message, LogOutputLevel.Information); + public void Warning(string message) => Log(message, LogOutputLevel.Warning); + public void Error(string message) => Log(message, LogOutputLevel.Error); + public void Fatal(string message) => Log(message, LogOutputLevel.Fatal); +} + +public enum LogOutputLevel { + Debug, + Information, + Warning, + Error, + Fatal +} \ No newline at end of file diff --git a/SimpleHttpServer/RequestContext.cs b/SimpleHttpServer/RequestContext.cs new file mode 100644 index 0000000..8e3f7de --- /dev/null +++ b/SimpleHttpServer/RequestContext.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace SimpleHttpServer; +internal class RequestContext { + public RequestContext(HttpListenerContext listenerContext) { + ListenerContext = listenerContext; + } + + public HttpListenerContext ListenerContext { get; } +}