99 lines
3.1 KiB
C#
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;
|
|
}
|
|
} |