forked from datalust/seqcli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRouter.cs
More file actions
96 lines (81 loc) · 3.71 KB
/
Router.cs
File metadata and controls
96 lines (81 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Serilog;
using Serilog.Context;
namespace Roastery.Web;
class Router : HttpServer
{
delegate Task<HttpResponse> ActionMethod(HttpRequest request);
class RouteBinding
{
// ReSharper disable once UnusedAutoPropertyAccessor.Local
public HttpMethod Method { get; }
public string Template { get; }
public string Controller { get; }
public string Action { get; }
public ActionMethod Binding { get; }
public RouteBinding(HttpMethod method, string template, string controller, string action, ActionMethod binding)
{
Method = method;
Template = template;
Controller = controller;
Action = action;
Binding = binding;
}
}
readonly IList<(HttpMethod, Regex, RouteBinding)> _routes = new List<(HttpMethod, Regex, RouteBinding)>();
readonly ILogger _logger;
public Router(IEnumerable<Controller> controllers, ILogger logger)
{
_logger = logger.ForContext<Router>();
_logger.Debug("Building route table from controller metadata");
foreach (var controller in controllers)
{
var actionMethods = controller.GetType().GetTypeInfo().DeclaredMethods
.Select(m => (method: m, route: m.GetCustomAttribute<RouteAttribute>()))
.Where(mr => mr.route != null);
foreach (var (method, route) in actionMethods)
{
var controllerName = controller.GetType().Name;
var actionName = method.Name;
var httpMethod = new HttpMethod(route!.Method.ToUpperInvariant());
var binding = new RouteBinding(
httpMethod,
route.Path,
controllerName,
actionName,
r =>
{
using var _ = LogContext.PushProperty("Controller", controllerName);
using var __ = LogContext.PushProperty("Action", actionName);
return (Task<HttpResponse>) method.Invoke(controller, [r])!;
});
_logger.Debug("Binding route HTTP {HttpMethod} {RouteTemplate} to action method {Controller}.{Action}()",
httpMethod, route.Path, binding.Controller, binding.Action);
var rx = new Regex("^" + route.Path.Replace("{id}", "[^/]+") + "$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
_routes.Add((httpMethod, rx, binding));
}
}
}
public override async Task<HttpResponse> InvokeAsync(HttpRequest request)
{
_logger.Debug("Resolving route for HTTP {RequestMethod} {RequestPath}", request.Method, request.Path);
var requestPath = request.Path.TrimStart('/');
var (_, _, route) = _routes.FirstOrDefault(r => r.Item1 == request.Method && r.Item2.IsMatch(requestPath));
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (route == null)
{
_logger.Debug("No action method is bound for this route");
return new HttpResponse(HttpStatusCode.NotFound,
$"The resource {request.Path} was not found on this server.");
}
_logger.Debug("Matched route template {RequestMethod} {RouteTemplate}", request.Method, route.Template);
_logger.Debug("Invoking action method {Controller}.{Action}()", route.Controller, route.Action);
return await route.Binding(request);
}
}