DTLib/DTLib.Web/WebApp.cs

99 lines
3.1 KiB
C#

global using System;
global using System.Collections.Generic;
global using System.Text;
global using System.Threading;
global using System.Threading.Tasks;
global using DTLib;
global using DTLib.Demystifier;
global using DTLib.Filesystem;
global using DTLib.Logging;
global using System.Net;
using DTLib.Web.Routes;
namespace DTLib.Web;
internal class WebApp
{
/// route for base url
public Route? HomePageRoute = null;
/// route for any url that doesn't have its own handler
public Route? DefaultRoute = null;
private ContextLogger _logger;
private string _baseUrl;
private CancellationToken _stopToken;
private Dictionary<string, Route> routes = new();
public WebApp(ILogger logger, string baseUrl, CancellationToken stopToken)
{
_logger = new ContextLogger(nameof(WebApp), logger);
_baseUrl = baseUrl;
_stopToken = stopToken;
}
public async Task Run()
{
_logger.LogInfo($"starting webserver at {_baseUrl} ...");
HttpListener server = new HttpListener();
server.Prefixes.Add(_baseUrl);
server.Start();
_logger.LogInfo("server started");
long requestId = 0;
while (!_stopToken.IsCancellationRequested)
{
var ctx = await server.GetContextAsync();
HandleRequestAsync(ctx, requestId);
requestId++;
}
// stop
server.Stop();
_logger.LogInfo("server stopped");
}
// ReSharper disable once AsyncVoidMethod
private async void HandleRequestAsync(HttpListenerContext ctx, long requestId)
{
string logContext = $"Request {requestId}";
try
{
_logger.LogInfo(logContext, $"[{ctx.Request.HttpMethod}] {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint} ...");
var status = await Resolve(ctx);
_logger.LogInfo(logContext, status);
}
catch (Exception ex)
{
_logger.LogWarn(logContext, ex);
}
}
public void MapRoute(string url, Func<HttpListenerContext, Task<HttpStatusCode>> route)
=> MapRoute(url, new DelegateRoute(route));
public void MapRoute(string url, Route route) => routes.Add(url, route);
public async Task<HttpStatusCode> Resolve(HttpListenerContext ctx)
{
string requestPath = ctx.Request.Url?.AbsolutePath ?? "/";
Route? route;
if(HomePageRoute != null && requestPath == "/")
route = HomePageRoute;
else if (routes.TryGetValue(requestPath, out var routeDelegate))
route = routeDelegate;
else route = DefaultRoute;
HttpStatusCode status;
if (route == null)
{
_logger.LogWarn("couldn't resolve request path {requestPath}");
status = HttpStatusCode.NotFound;
}
else status = await route.HandleRequest(ctx);
ctx.Response.StatusCode = (int)status;
await ctx.Response.OutputStream.FlushAsync(_stopToken);
ctx.Response.OutputStream.Close();
return status;
}
}