This commit is contained in:
Timerix 2025-03-23 00:07:06 +05:00
parent 9082d7a4d0
commit 7d814ee4cb
7 changed files with 67 additions and 48 deletions

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!--package info--> <!--package info-->
<PackageId>DTLib.Web</PackageId> <PackageId>DTLib.Web</PackageId>
<Version>1.0.0</Version> <Version>1.1.0</Version>
<Authors>Timerix</Authors> <Authors>Timerix</Authors>
<Description>HTTP Server with simple routing</Description> <Description>HTTP Server with simple routing</Description>
<RepositoryType>GIT</RepositoryType> <RepositoryType>GIT</RepositoryType>

View File

@ -1,6 +1,6 @@
namespace DTLib.Web.Routes; namespace DTLib.Web.Routes;
public class DelegateRoute(Func<HttpListenerContext, Task<HttpStatusCode>> routeHandler) : Route public class DelegateRouteHandler(Func<HttpListenerContext, Task<HttpStatusCode>> routeHandler) : RouteHandler
{ {
public override Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx) => routeHandler(ctx); public override Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx) => routeHandler(ctx);
} }

View File

@ -0,0 +1,6 @@
namespace DTLib.Web.Routes;
public interface IRouter
{
Task<HttpStatusCode> Resolve(HttpListenerContext ctx);
}

View File

@ -1,6 +1,6 @@
namespace DTLib.Web.Routes; namespace DTLib.Web.Routes;
public abstract class Route public abstract class RouteHandler
{ {
public abstract Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx); public abstract Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx);
} }

View File

@ -1,6 +1,6 @@
namespace DTLib.Web.Routes; namespace DTLib.Web.Routes;
public class ServeFilesRoute(IOPath _publicDir, string _homePageUrl = "index.html") : Route public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : RouteHandler
{ {
public override async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx) public override async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx)
{ {

View File

@ -0,0 +1,48 @@
namespace DTLib.Web.Routes;
public class SimpleRouter : IRouter
{
/// route for base url
public RouteHandler? HomePageRoute = null;
/// route for any url that doesn't have its own handler
public RouteHandler? DefaultRoute = null;
private readonly Dictionary<string, RouteHandler> _routes = new();
private readonly ILogger _logger;
public SimpleRouter(ILogger logger)
{
_logger = logger;
}
public void MapRoute(string url, Func<HttpListenerContext, Task<HttpStatusCode>> route)
=> MapRoute(url, new DelegateRouteHandler(route));
public void MapRoute(string url, RouteHandler route) => _routes.Add(url, route);
public async Task<HttpStatusCode> Resolve(HttpListenerContext ctx)
{
string requestPath = ctx.Request.Url?.AbsolutePath ?? "/";
RouteHandler? 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(nameof(SimpleRouter), $"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();
ctx.Response.OutputStream.Close();
return status;
}
}

View File

@ -3,8 +3,6 @@ global using System.Collections.Generic;
global using System.Text; global using System.Text;
global using System.Threading; global using System.Threading;
global using System.Threading.Tasks; global using System.Threading.Tasks;
global using DTLib;
global using DTLib.Demystifier;
global using DTLib.Filesystem; global using DTLib.Filesystem;
global using DTLib.Logging; global using DTLib.Logging;
global using System.Net; global using System.Net;
@ -12,23 +10,19 @@ using DTLib.Web.Routes;
namespace DTLib.Web; namespace DTLib.Web;
internal class WebApp public class WebApp
{ {
/// route for base url private readonly string _baseUrl;
public Route? HomePageRoute = null; private readonly ContextLogger _logger;
/// route for any url that doesn't have its own handler private readonly IRouter _router;
public Route? DefaultRoute = null; private readonly CancellationToken _stopToken;
private ContextLogger _logger;
private string _baseUrl;
private CancellationToken _stopToken;
private Dictionary<string, Route> routes = new();
public WebApp(ILogger logger, string baseUrl, CancellationToken stopToken) public WebApp(string baseUrl, ILogger logger, IRouter router, CancellationToken stopToken = default)
{ {
_logger = new ContextLogger(nameof(WebApp), logger); _logger = new ContextLogger(nameof(WebApp), logger);
_baseUrl = baseUrl; _baseUrl = baseUrl;
_stopToken = stopToken; _stopToken = stopToken;
_router = router;
} }
public async Task Run() public async Task Run()
@ -38,7 +32,7 @@ internal class WebApp
server.Prefixes.Add(_baseUrl); server.Prefixes.Add(_baseUrl);
server.Start(); server.Start();
_logger.LogInfo("server started"); _logger.LogInfo("server started");
long requestId = 0; long requestId = 1;
while (!_stopToken.IsCancellationRequested) while (!_stopToken.IsCancellationRequested)
{ {
var ctx = await server.GetContextAsync(); var ctx = await server.GetContextAsync();
@ -58,7 +52,7 @@ internal class WebApp
try try
{ {
_logger.LogInfo(logContext, $"[{ctx.Request.HttpMethod}] {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint} ..."); _logger.LogInfo(logContext, $"[{ctx.Request.HttpMethod}] {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint} ...");
var status = await Resolve(ctx); var status = await _router.Resolve(ctx);
_logger.LogInfo(logContext, status); _logger.LogInfo(logContext, status);
} }
catch (Exception ex) catch (Exception ex)
@ -67,33 +61,4 @@ internal class WebApp
} }
} }
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;
}
} }