diff --git a/DTLib.Web/DTLib.Web.csproj b/DTLib.Web/DTLib.Web.csproj index 703035c..80f3bfe 100644 --- a/DTLib.Web/DTLib.Web.csproj +++ b/DTLib.Web/DTLib.Web.csproj @@ -2,7 +2,7 @@ DTLib.Web - 1.3.1 + 1.4.0 Timerix HTTP Server with simple routing GIT diff --git a/DTLib.Web/HttpMethod.cs b/DTLib.Web/HttpMethod.cs index 475f999..ce48b50 100644 --- a/DTLib.Web/HttpMethod.cs +++ b/DTLib.Web/HttpMethod.cs @@ -1,15 +1,18 @@ namespace DTLib.Web; /// -public enum HttpMethod +[Flags] +public enum HttpMethod : ushort { - GET, - POST, - PUT, - DELETE, - PATCH, - HEAD, - OPTIONS, - TRACE, - CONNECT + NONE = 0, + GET = 1, + POST = 2, + PUT = 4, + DELETE = 8, + PATCH = 16, + HEAD = 32, + OPTIONS = 64, + TRACE = 128, + CONNECT = 256, + ANY = 65535 } \ No newline at end of file diff --git a/DTLib.Web/Routes/IRouter.cs b/DTLib.Web/Routes/IRouter.cs index b3ae250..49aaa0b 100644 --- a/DTLib.Web/Routes/IRouter.cs +++ b/DTLib.Web/Routes/IRouter.cs @@ -2,5 +2,5 @@ namespace DTLib.Web.Routes; public interface IRouter { - Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger); + Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger); } \ No newline at end of file diff --git a/DTLib.Web/Routes/SimpleRouter.cs b/DTLib.Web/Routes/SimpleRouter.cs index 838c45b..883d2e5 100644 --- a/DTLib.Web/Routes/SimpleRouter.cs +++ b/DTLib.Web/Routes/SimpleRouter.cs @@ -3,42 +3,63 @@ namespace DTLib.Web.Routes; public class SimpleRouter : IRouter { /// route for any url that doesn't have its own handler - public IRouteHandler? DefaultRoute { get; set; } - - private readonly Dictionary _routes = new(); + public record RouteWithMethod(HttpMethod method, IRouteHandler routeHandler) + { + public bool CheckMethod(HttpMethod requestMethod) => (requestMethod & method) != 0; + + public bool CheckMethod(string requestMethodStr) + => Enum.TryParse(requestMethodStr, out var requestMethod) + && CheckMethod(requestMethod); + } + + private readonly Dictionary _routes = new(); private readonly ILogger _logger; + public RouteWithMethod? DefaultRoute { get; set; } + public SimpleRouter(ILogger logger) { _logger = new ContextLogger(nameof(SimpleRouter), logger); } - public void MapRoute(string url, HttpMethod method, IRouteHandler route) => _routes.Add($"{url}:{method}", route); + public void MapRoute(string url, HttpMethod method, IRouteHandler route) + => _routes.Add(url, new RouteWithMethod(method, route)); public void MapRoute(string url, HttpMethod method, Func> route) => MapRoute(url, method, new DelegateRouteHandler(route)); - - public async Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger) + + public async Task Resolve(HttpListenerContext ctx, ContextLogger requestLogger) { - - string? requestPath = ctx.Request.Url?.AbsolutePath; - if (string.IsNullOrEmpty(requestPath)) - requestPath = "/"; - if (!_routes.TryGetValue($"{requestPath}:{ctx.Request.HttpMethod}", out var route)) - route = DefaultRoute; - - HttpStatusCode status; - if (route == null) + HttpStatusCode status = HttpStatusCode.InternalServerError; + try { - _logger.LogWarn(nameof(SimpleRouter), $"couldn't resolve request path {requestPath}"); - status = HttpStatusCode.NotFound; - } - else status = await route.HandleRequest(ctx, requestLogger); + string? requestPath = ctx.Request.Url?.AbsolutePath; + if (string.IsNullOrEmpty(requestPath)) + requestPath = "/"; + + if(!_routes.TryGetValue(requestPath!, out var routeWithMethod)) + routeWithMethod = DefaultRoute; - ctx.Response.StatusCode = (int)status; - await ctx.Response.OutputStream.FlushAsync(); - ctx.Response.OutputStream.Close(); - return status; + if (routeWithMethod is null) + { + _logger.LogWarn(nameof(SimpleRouter), + $"couldn't resolve request path {ctx.Request.HttpMethod} {requestPath}"); + status = HttpStatusCode.NotFound; + } + else if (!routeWithMethod.CheckMethod(ctx.Request.HttpMethod)) + { + _logger.LogWarn(nameof(SimpleRouter), + $"received request with invalid method {ctx.Request.HttpMethod} {requestPath}"); + status = HttpStatusCode.MethodNotAllowed; + } + else status = await routeWithMethod.routeHandler.HandleRequest(ctx, requestLogger); + } + finally + { + ctx.Response.StatusCode = (int)status; + await ctx.Response.OutputStream.FlushAsync(); + ctx.Response.OutputStream.Close(); + } } } \ No newline at end of file diff --git a/DTLib.Web/WebApp.cs b/DTLib.Web/WebApp.cs index 37fca5d..63334dd 100644 --- a/DTLib.Web/WebApp.cs +++ b/DTLib.Web/WebApp.cs @@ -58,9 +58,11 @@ public class WebApp requestLogger.LogInfo($"{ctx.Request.HttpMethod} {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint}..."); var stopwatch = new Stopwatch(); stopwatch.Start(); - var status = await _router.Resolve(ctx, requestLogger); + await _router.Resolve(ctx, requestLogger); stopwatch.Stop(); - requestLogger.LogInfo($"responded {(int)status} ({status}) in {stopwatch.ElapsedMilliseconds}ms"); + requestLogger.LogInfo($"responded {ctx.Response.StatusCode}" + + $" ({(HttpStatusCode)ctx.Response.StatusCode})" + + $" in {stopwatch.ElapsedMilliseconds}ms"); } catch (Exception ex) {