global using System; global using System.Collections.Generic; global using System.Text; global using System.Threading; global using System.Threading.Tasks; global using DTLib.Filesystem; global using DTLib.Logging; global using System.Net; using System.Diagnostics; using DTLib.Extensions; using DTLib.Web.Routes; namespace DTLib.Web; public class WebApp { private readonly string _baseUrl; private readonly ContextLogger _logger; private readonly IRouter _router; private readonly CancellationToken _stopToken; public WebApp(string baseUrl, ILogger logger, IRouter router, CancellationToken stopToken = default) { _logger = new ContextLogger(nameof(WebApp), logger); _baseUrl = baseUrl; _stopToken = stopToken; _router = router; } public async Task Run() { _logger.LogInfo($"starting server at '{_baseUrl}'..."); HttpListener server = new HttpListener(); server.Prefixes.Add(_baseUrl); server.Start(); _logger.LogInfo("server started"); long requestId = 1; try { while (!_stopToken.IsCancellationRequested) { var ctx = await server.GetContextAsync().AsCancellable(_stopToken); HandleRequestAsync(ctx, requestId++); } } catch (OperationCanceledException) {} 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 stopwatch = new Stopwatch(); stopwatch.Start(); var status = await _router.Resolve(ctx); stopwatch.Stop(); _logger.LogInfo(logContext, $"responded {(int)status} ({status}) in {stopwatch.ElapsedMilliseconds}ms"); } catch (Exception ex) { _logger.LogWarn(logContext, ex); } } }