diff --git a/LinkRouter.sln b/LinkRouter.sln deleted file mode 100644 index 5659e00..0000000 --- a/LinkRouter.sln +++ /dev/null @@ -1,16 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinkRouter", "LinkRouter\LinkRouter.csproj", "{0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0DEC18EE-615C-4E89-BA5C-B8B4AE83FE94}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/LinkRouter.slnx b/LinkRouter.slnx new file mode 100644 index 0000000..aaaca24 --- /dev/null +++ b/LinkRouter.slnx @@ -0,0 +1,3 @@ + + + diff --git a/LinkRouter/App/Http/Controllers/RedirectController.cs b/LinkRouter/App/Http/Controllers/RedirectController.cs index 6a940ad..0a62b0f 100644 --- a/LinkRouter/App/Http/Controllers/RedirectController.cs +++ b/LinkRouter/App/Http/Controllers/RedirectController.cs @@ -1,4 +1,4 @@ -using LinkRouter.App.Configuration; +using LinkRouter.App.Models; using LinkRouter.App.Services; using Microsoft.AspNetCore.Mvc; @@ -9,22 +9,42 @@ public class RedirectController : Controller { private readonly Config Config; private readonly RedirectionService RedirectionService; + private readonly MetricsService MetricsService; - public RedirectController(Config config, RedirectionService redirectionService) + public RedirectController(Config config, RedirectionService redirectionService, MetricsService metricsService) { Config = config; RedirectionService = redirectionService; + MetricsService = metricsService; } - [HttpGet("/{*path}")] - public async Task RedirectToExternalUrl(string path) + [HttpGet("{*path}")] + public async Task RedirectTo(string? path) { - return await RedirectionService.GetRedirect(path); - } + Console.WriteLine(path); - [HttpGet("/")] - public async Task GetRootRoute() - { - return await RedirectionService.GetRedirect(string.Empty); + path = string.IsNullOrWhiteSpace(path) + ? "/" + : $"/{path.Trim('/')}/"; + + if (!RedirectionService.TryGetRedirect(path, out var rawRedirect) || rawRedirect == null) + { + if (string.IsNullOrEmpty(rawRedirect)) + return NotFound(); + + if (RedirectionService.TryGetStatusCode(rawRedirect, out var notFoundStatusCode)) + return StatusCode(notFoundStatusCode); + + await MetricsService.IncrementNotFound(path); + + return Redirect(rawRedirect); + } + + if (RedirectionService.TryGetStatusCode(path.Trim('/'), out var code)) + return StatusCode(code); + + await MetricsService.IncrementFound(path); + + return Redirect(rawRedirect); } } \ No newline at end of file diff --git a/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs b/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs new file mode 100644 index 0000000..bff7cfc --- /dev/null +++ b/LinkRouter/App/Implemlementations/LoggingConsoleFormatter.cs @@ -0,0 +1,58 @@ +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Logging.Console; + +namespace LinkRouter.App.Implemlementations; + +public class LoggingConsoleFormatter : ConsoleFormatter +{ + public LoggingConsoleFormatter() : base(nameof(LoggingConsoleFormatter)) + { + } + + public override void Write( + in LogEntry logEntry, + IExternalScopeProvider? scopeProvider, + TextWriter textWriter) + { + var message = logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception) + ?? logEntry.State?.ToString(); + + // Timestamp + textWriter.Write(DateTime.Now.ToString("dd.MM.yy HH:mm:ss")); + textWriter.Write(' '); + + // Log level + textWriter.Write(GetLevelText(logEntry.LogLevel)); + textWriter.Write(' '); + + // Category + textWriter.Write(logEntry.Category); + textWriter.Write(": "); + + // Message + textWriter.Write(message); + + // Exception (if any) + if (logEntry.Exception != null) + { + textWriter.Write(" | "); + textWriter.Write(logEntry.Exception); + } + + textWriter.WriteLine(); + } + + private static string GetLevelText(LogLevel logLevel) + { + return logLevel switch + { + LogLevel.Critical => "CRIT", + LogLevel.Error => "ERRO", + LogLevel.Warning => "WARN", + LogLevel.Information => "INFO", + LogLevel.Debug => "DEBG", + LogLevel.Trace => "TRCE", + _ => "NONE" + }; + } +} \ No newline at end of file diff --git a/LinkRouter/App/Configuration/Config.cs b/LinkRouter/App/Models/Config.cs similarity index 81% rename from LinkRouter/App/Configuration/Config.cs rename to LinkRouter/App/Models/Config.cs index 5166264..c9410b1 100644 --- a/LinkRouter/App/Configuration/Config.cs +++ b/LinkRouter/App/Models/Config.cs @@ -1,13 +1,28 @@ using System.Text; using System.Text.Json.Serialization; using System.Text.RegularExpressions; -using LinkRouter.App.Models; -namespace LinkRouter.App.Configuration; +namespace LinkRouter.App.Models; public class Config { - public string RootRoute { get; set; } = "https://example.com"; + [JsonPropertyName("RootRedirect")] public string? RootRedirect { get; set; } = "https://example.com"; + + // Legacy property, only used during deserialization + [Obsolete] + [JsonPropertyName("RootRoute")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string? LegacyRootRoute + { + get => null; // never serialize + set + { + if (!string.IsNullOrEmpty(value) && string.IsNullOrEmpty(RootRedirect)) + { + RootRedirect = value; + } + } + } public NotFoundBehaviorConfig NotFoundBehavior { get; set; } = new(); @@ -39,11 +54,7 @@ public void CompileRoutes() foreach (var route in Routes) { - if (!route.Route.StartsWith("/")) - route.Route = "/" + route.Route; - - if (!route.Route.EndsWith("/")) - route.Route += "/"; + route.Route = "/" + route.Route.Trim('/') + "/"; var compiled = new CompiledRoute { @@ -55,9 +66,9 @@ public void CompileRoutes() var escaped = Regex.Escape(route.Route); - var pattern = new Regex(@"\\\{(\d|\w+)\}", RegexOptions.CultureInvariant); - var matches = pattern.Matches(escaped); + + var matches = Patterns.PlaceholderPattern.Matches(escaped); foreach (var match in matches.Select(x => x)) { @@ -102,7 +113,5 @@ public void CompileRoutes() CompiledRoutes = compiledRoutes .ToArray(); } - - [JsonIgnore] public static Regex ErrorCodePattern = new(@"\s*\-\>\s*(\d+)\s*$", RegexOptions.Compiled | RegexOptions.CultureInvariant); } \ No newline at end of file diff --git a/LinkRouter/App/Models/Patterns.cs b/LinkRouter/App/Models/Patterns.cs new file mode 100644 index 0000000..859e109 --- /dev/null +++ b/LinkRouter/App/Models/Patterns.cs @@ -0,0 +1,10 @@ +using System.Text.RegularExpressions; + +namespace LinkRouter.App.Models; + +public static class Patterns +{ + public static Regex ErrorCodePattern = new(@"\s*\-\>\s*(\d+)\s*$", RegexOptions.Compiled | RegexOptions.CultureInvariant); + + public static Regex PlaceholderPattern = new (@"\\\{(\d|\w+)\}", RegexOptions.Compiled | RegexOptions.CultureInvariant); +} \ No newline at end of file diff --git a/LinkRouter/App/Services/ConfigWatcher.cs b/LinkRouter/App/Services/ConfigWatcher.cs index a78570d..cf7079e 100644 --- a/LinkRouter/App/Services/ConfigWatcher.cs +++ b/LinkRouter/App/Services/ConfigWatcher.cs @@ -1,5 +1,5 @@ using System.Text.Json; -using LinkRouter.App.Configuration; +using LinkRouter.App.Models; namespace LinkRouter.App.Services; @@ -47,7 +47,7 @@ private void OnChanged(object sender, FileSystemEventArgs e) var config = JsonSerializer.Deserialize(content); Config.Routes = config?.Routes ?? []; - Config.RootRoute = config?.RootRoute ?? "https://example.com"; + Config.RootRedirect = config?.RootRedirect ?? "https://example.com"; Logger.LogInformation("Config file changed."); diff --git a/LinkRouter/App/Services/MetricsService.cs b/LinkRouter/App/Services/MetricsService.cs index 56a182a..c56ae0c 100644 --- a/LinkRouter/App/Services/MetricsService.cs +++ b/LinkRouter/App/Services/MetricsService.cs @@ -1,9 +1,8 @@ -using MoonCore.Attributes; using Prometheus; namespace LinkRouter.App.Services; -[Singleton] + public class MetricsService { private readonly Counter RouteCounter = Metrics.CreateCounter( diff --git a/LinkRouter/App/Services/RedirectionService.cs b/LinkRouter/App/Services/RedirectionService.cs index b6dfefc..336911c 100644 --- a/LinkRouter/App/Services/RedirectionService.cs +++ b/LinkRouter/App/Services/RedirectionService.cs @@ -1,81 +1,66 @@ -using LinkRouter.App.Configuration; -using Microsoft.AspNetCore.Mvc; -using MoonCore.Attributes; +using LinkRouter.App.Models; namespace LinkRouter.App.Services; -[Singleton] public class RedirectionService { private readonly Config Config; - private readonly MetricsService MetricsService; - public RedirectionService(Config config, MetricsService metricsService) + public RedirectionService(Config config) { Config = config; - MetricsService = metricsService; } - public async Task GetRedirect(string path) + public bool TryGetRedirect(string path, out string? redirectPath) { - if (path == "") + redirectPath = null; + + if (path == "/") { - var url = Config.RootRoute; + var url = Config.RootRedirect; - if (TryGetErrorCode(url, out var notFoundStatusCode)) - return new StatusCodeResult(notFoundStatusCode); + if (string.IsNullOrEmpty(url)) + return false; - await MetricsService.IncrementFound("/"); + redirectPath = url; - return new RedirectResult(url); + return true; } - if (!path.EndsWith("/")) - path += "/"; - - path = "/" + path; - - var redirectRoute = Config.CompiledRoutes?.FirstOrDefault(x => x.CompiledPattern.IsMatch(path)); - if (redirectRoute == null) { - await MetricsService.IncrementNotFound(path); - if (!Config.NotFoundBehavior.RedirectOn404) - return new NotFoundResult(); - + { + return false; + } - if (TryGetErrorCode(Config.NotFoundBehavior.RedirectUrl, out var notFoundStatusCode)) - return new StatusCodeResult(notFoundStatusCode); + redirectPath = Config.NotFoundBehavior.RedirectUrl; - return new RedirectResult(Config.NotFoundBehavior.RedirectUrl); + return true; } var match = redirectRoute.CompiledPattern.Match(path); - if (TryGetErrorCode(redirectRoute.RedirectUrl, out var statusCode)) - return new StatusCodeResult(statusCode); - - foreach (var placeholder in redirectRoute.Placeholders) { var value = match.Groups[placeholder.Value].Value; redirectRoute.RedirectUrl = redirectRoute.RedirectUrl.Replace("{" + placeholder.Key + "}", value); } - await MetricsService.IncrementFound(path); + redirectPath = redirectRoute.RedirectUrl; - return new RedirectResult(redirectRoute.RedirectUrl); + return true; } - private bool TryGetErrorCode(string url, out int code) + public bool TryGetStatusCode(string path, out int code) { - if (Config.ErrorCodePattern.IsMatch(url)) + var match = Patterns.ErrorCodePattern.Match(path); + + if (match.Success) { - var errorCodeMatch = Config.ErrorCodePattern.Match(url); - code = int.Parse(errorCodeMatch.Groups[1].Value); + code = int.Parse(match.Groups[1].Value); return true; } diff --git a/LinkRouter/LinkRouter.csproj b/LinkRouter/LinkRouter.csproj index 07143a2..ff702c5 100644 --- a/LinkRouter/LinkRouter.csproj +++ b/LinkRouter/LinkRouter.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 enable enable Linux @@ -9,7 +9,6 @@ - diff --git a/LinkRouter/Program.cs b/LinkRouter/Program.cs index 846359f..47033a6 100644 --- a/LinkRouter/Program.cs +++ b/LinkRouter/Program.cs @@ -1,9 +1,8 @@ using System.Text.Json; -using LinkRouter.App.Configuration; +using LinkRouter.App.Implemlementations; +using LinkRouter.App.Models; using LinkRouter.App.Services; -using MoonCore.Extensions; -using MoonCore.Helpers; -using MoonCore.Logging; +using Microsoft.Extensions.Logging.Console; using Prometheus; namespace LinkRouter; @@ -19,8 +18,8 @@ public static void Main(string[] args) builder.Services.AddControllers(); builder.Logging.ClearProviders(); - - builder.Logging.AddAnsiConsole(); + builder.Logging.AddConsole(options => { options.FormatterName = nameof(LoggingConsoleFormatter); }); + builder.Logging.AddConsoleFormatter(); builder.Services.AddHostedService(); @@ -39,7 +38,9 @@ public static void Main(string[] args) builder.Services.AddSingleton(config); - builder.Services.AutoAddServices(); + builder.Services.AddSingleton(); + + builder.Services.AddSingleton(); builder.Services.AddMetricServer(options => { options.Port = 5000; }); diff --git a/global.json b/global.json deleted file mode 100644 index 93681ff..0000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "9.0.0", - "rollForward": "latestMinor", - "allowPrerelease": false - } -} \ No newline at end of file