Compare commits

...

5 Commits

13 changed files with 147 additions and 154 deletions

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!--package info--> <!--package info-->
<PackageId>DTLib.Web</PackageId> <PackageId>DTLib.Web</PackageId>
<Version>1.2.2</Version> <Version>1.3.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>
@ -25,6 +25,6 @@
<ProjectReference Include="..\DTLib\DTLib.csproj" /> <ProjectReference Include="..\DTLib\DTLib.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' "> <ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.6.5" /> <PackageReference Include="DTLib" Version="1.7.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

@ -2,5 +2,5 @@ namespace DTLib.Web.Routes;
public interface IRouter public interface IRouter
{ {
Task<HttpStatusCode> Resolve(HttpListenerContext ctx); Task<HttpStatusCode> Resolve(HttpListenerContext ctx, ContextLogger requestLogger);
} }

View File

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

View File

@ -1,8 +1,8 @@
namespace DTLib.Web.Routes; namespace DTLib.Web.Routes;
public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : RouteHandler public class ServeFilesRouteHandler(IOPath _publicDir, string _homePageUrl = "index.html") : IRouteHandler
{ {
public override async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx) public async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx, ContextLogger requestLogger)
{ {
if (ctx.Request.HttpMethod != "GET") if (ctx.Request.HttpMethod != "GET")
return HttpStatusCode.BadRequest; return HttpStatusCode.BadRequest;

View File

@ -3,22 +3,23 @@ namespace DTLib.Web.Routes;
public class SimpleRouter : IRouter public class SimpleRouter : IRouter
{ {
/// route for any url that doesn't have its own handler /// route for any url that doesn't have its own handler
public RouteHandler? DefaultRoute { get; set; } public IRouteHandler? DefaultRoute { get; set; }
private readonly Dictionary<string, RouteHandler> _routes = new(); private readonly Dictionary<string, IRouteHandler> _routes = new();
private readonly ILogger _logger; private readonly ILogger _logger;
public SimpleRouter(ILogger logger) public SimpleRouter(ILogger logger)
{ {
_logger = logger; _logger = new ContextLogger(nameof(SimpleRouter), logger);
} }
public void MapRoute(string url, HttpMethod method, RouteHandler route) => _routes.Add($"{url}:{method}", route); public void MapRoute(string url, HttpMethod method, IRouteHandler route) => _routes.Add($"{url}:{method}", route);
public void MapRoute(string url, HttpMethod method, Func<HttpListenerContext, Task<HttpStatusCode>> route) public void MapRoute(string url, HttpMethod method,
Func<HttpListenerContext, ContextLogger, Task<HttpStatusCode>> route)
=> MapRoute(url, method, new DelegateRouteHandler(route)); => MapRoute(url, method, new DelegateRouteHandler(route));
public async Task<HttpStatusCode> Resolve(HttpListenerContext ctx) public async Task<HttpStatusCode> Resolve(HttpListenerContext ctx, ContextLogger requestLogger)
{ {
string? requestPath = ctx.Request.Url?.AbsolutePath; string? requestPath = ctx.Request.Url?.AbsolutePath;
@ -33,7 +34,7 @@ public class SimpleRouter : IRouter
_logger.LogWarn(nameof(SimpleRouter), $"couldn't resolve request path {requestPath}"); _logger.LogWarn(nameof(SimpleRouter), $"couldn't resolve request path {requestPath}");
status = HttpStatusCode.NotFound; status = HttpStatusCode.NotFound;
} }
else status = await route.HandleRequest(ctx); else status = await route.HandleRequest(ctx, requestLogger);
ctx.Response.StatusCode = (int)status; ctx.Response.StatusCode = (int)status;
await ctx.Response.OutputStream.FlushAsync(); await ctx.Response.OutputStream.FlushAsync();

View File

@ -41,7 +41,7 @@ public class WebApp
while (!_stopToken.IsCancellationRequested) while (!_stopToken.IsCancellationRequested)
{ {
var ctx = await server.GetContextAsync().AsCancellable(_stopToken); var ctx = await server.GetContextAsync().AsCancellable(_stopToken);
HandleRequestAsync(ctx, requestId++); HandleRequestAsync(ctx, new ContextLogger($"Request-{requestId++}", _logger));
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@ -51,22 +51,20 @@ public class WebApp
_logger.LogInfo("server stopped"); _logger.LogInfo("server stopped");
} }
// ReSharper disable once AsyncVoidMethod private async void HandleRequestAsync(HttpListenerContext ctx, ContextLogger requestLogger)
private async void HandleRequestAsync(HttpListenerContext ctx, long requestId)
{ {
string logContext = $"Request-{requestId}";
try try
{ {
_logger.LogInfo(logContext, $"[{ctx.Request.HttpMethod}] {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint}..."); requestLogger.LogInfo($"{ctx.Request.HttpMethod} {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint}...");
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
var status = await _router.Resolve(ctx); var status = await _router.Resolve(ctx, requestLogger);
stopwatch.Stop(); stopwatch.Stop();
_logger.LogInfo(logContext, $"responded {(int)status} ({status}) in {stopwatch.ElapsedMilliseconds}ms"); requestLogger.LogInfo($"responded {(int)status} ({status}) in {stopwatch.ElapsedMilliseconds}ms");
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarn(logContext, ex); requestLogger.LogWarn(ex);
} }
} }

View File

@ -14,6 +14,9 @@ public static class ColoredConsole
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ResetColor() => System.Console.ResetColor(); public static void ResetColor() => System.Console.ResetColor();
/// <summary>
/// Clears console buffer and resets color
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Clear() public static void Clear()
{ {
@ -21,26 +24,15 @@ public static class ColoredConsole
System.Console.Clear(); System.Console.Clear();
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(char c) => System.Console.Write(c);
public static void Write(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null) public static void Write(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
{ {
if(fg != null) Fg(fg.Value); if(fg != null) Fg(fg.Value);
if(bg != null) Bg(bg.Value); if(bg != null) Bg(bg.Value);
#if NETSTANDARD2_0 System.Console.Write(msg);
var chars = msg.ToCharArray();
#else
var chars = msg.AsSpan();
#endif
for (int i = 0; i < chars.Length; ++i)
{
Write(chars[i]);
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteLine() => Write('\n'); public static void WriteLine() => System.Console.Write('\n');
public static void WriteLine(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null) public static void WriteLine(string msg, ConsoleColor? fg = null, ConsoleColor? bg = null)
{ {
@ -48,18 +40,15 @@ public static class ColoredConsole
WriteLine(); WriteLine();
} }
public static string? ReadLine(string query, ConsoleColor? fg = null, ConsoleColor? bg = null) public static string ReadLine(ConsoleColor? fg = null, ConsoleColor? bg = null)
{ {
Write(query, fg, bg); if(fg != null) Fg(fg.Value);
Write(':'); if(bg != null) Bg(bg.Value);
Write(' '); return System.Console.ReadLine() ?? string.Empty;
return System.Console.ReadLine();
} }
public static void WriteHLine(char c, ConsoleColor? fg = null, ConsoleColor? bg = null) public static void WriteHLine(char c, ConsoleColor? fg = null, ConsoleColor? bg = null) =>
{
WriteLine(c.Multiply(Width - 1), fg, bg); WriteLine(c.Multiply(Width - 1), fg, bg);
}
public static void WriteTitle(string title, char spacing = '-', public static void WriteTitle(string title, char spacing = '-',
string left_framing = "[", string right_framing = "]", string left_framing = "[", string right_framing = "]",
@ -83,7 +72,5 @@ public static class ColoredConsole
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void WriteCentred(string title, ConsoleColor? fg = null, ConsoleColor? bg = null) public static void WriteCentred(string title, ConsoleColor? fg = null, ConsoleColor? bg = null)
{ => WriteTitle(title, ' ', "", "", fg, bg);
WriteTitle(title, ' ', "", "", fg, bg);
}
} }

View File

@ -1,64 +1,96 @@
namespace DTLib.Console; namespace DTLib.Console;
#nullable enable public record LaunchArgument
public class LaunchArgument
{ {
public string[] Aliases; public struct Param
public string Description; {
protected string? ParamName1; public readonly string Name;
protected string? ParamName2; public string? Value { get; internal set; } = null;
public Action? Handler;
public Action<string>? HandlerWithArg1;
public Action<string, string>? HandlerWithArg2;
public int RequiredArgsCount;
public int Priority;
private LaunchArgument(string[] aliases, string description, int priority) public Param(string name)
{
Name = name;
}
}
public readonly string[] Aliases;
public readonly string Description;
public readonly int Priority;
public readonly Param[] Params;
private readonly Action? Handler;
private readonly Action<string>? Handler1Param;
private readonly Action<string, string>? Handler2Params;
public LaunchArgument(string[] aliases, string description,
Action handler, int priority = 0)
{ {
Aliases = aliases; Aliases = aliases;
Description = description; Description = description;
Priority = priority; Priority = priority;
}
public LaunchArgument(string[] aliases, string description,
Action handler, int priority = 0)
: this(aliases, description, priority)
{
Handler = handler; Handler = handler;
RequiredArgsCount = 0; Params = [];
} }
public LaunchArgument(string[] aliases, string description, public LaunchArgument(string[] aliases, string description,
Action<string> handler, string paramName1, int priority=0) Action<string> handler, string paramName1, int priority=0)
: this(aliases, description, priority)
{ {
HandlerWithArg1 = handler; Aliases = aliases;
ParamName1 = paramName1; Description = description;
RequiredArgsCount = 1; Priority = priority;
Handler1Param = handler;
Params = [
new Param(paramName1)
];
} }
public LaunchArgument(string[] aliases, string description, public LaunchArgument(string[] aliases, string description,
Action<string, string> handler, string paramName1, string paramName2, int priority=0) Action<string, string> handler, string paramName1, string paramName2, int priority=0)
: this(aliases, description, priority)
{ {
HandlerWithArg2 = handler; Aliases = aliases;
ParamName1 = paramName1; Description = description;
ParamName2 = paramName2; Priority = priority;
RequiredArgsCount = 2;
Handler2Params = handler;
Params = [
new Param(paramName1),
new Param(paramName2),
];
} }
public StringBuilder AppendHelpInfo(StringBuilder b) internal StringBuilder AppendHelpInfo(StringBuilder b)
{ {
b.Append(Aliases[0]); b.Append(Aliases[0]);
for (int i = 1; i < Aliases.Length; i++) for (int i = 1; i < Aliases.Length; i++)
b.Append(", ").Append(Aliases[i]); b.Append(", ").Append(Aliases[i]);
if (!string.IsNullOrEmpty(ParamName1)) foreach (var param in Params)
b.Append(" [").Append(ParamName1).Append("] "); b.Append(" [").Append(param.Name).Append("]");
if (!string.IsNullOrEmpty(ParamName2))
b.Append(" [").Append(ParamName2).Append("] ");
b.Append(" - ").Append(Description); b.Append(" - ").Append(Description);
return b; return b;
} }
public override string ToString() => internal void Handle()
$"{{{{{Aliases.MergeToString(", ")}}}, Handler: {Handler is null}, HandlerWithArg: {HandlerWithArg1 is null}}}"; {
switch (Params.Length)
{
default:
throw new ArgumentOutOfRangeException(Params.Length.ToString());
case 0:
Handler!.Invoke();
break;
case 1:
if (Params[0].Value is null)
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
Handler1Param!.Invoke(Params[0].Value!);
break;
case 2:
if (Params[0].Value is null)
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[0] value");
if (Params[1].Value is null)
throw new NullReferenceException($"Argument '{Aliases[0]}' hasnt got Param[1] value");
Handler2Params!.Invoke(Params[0].Value!, Params[1].Value!);
break;
}
}
} }

View File

@ -2,9 +2,10 @@ namespace DTLib.Console;
public class LaunchArgumentParser public class LaunchArgumentParser
{ {
private Dictionary<string, LaunchArgument> argDict = new(); public bool IsAllowedNoArguments;
private List<LaunchArgument> argList = new();
public bool ExitIfNoArgs = true; private readonly Dictionary<string, LaunchArgument> argDict = new();
private readonly List<LaunchArgument> argList = new();
public class ExitAfterHelpException : Exception public class ExitAfterHelpException : Exception
{ {
@ -51,9 +52,9 @@ public class LaunchArgumentParser
Add(helpArg); Add(helpArg);
} }
public LaunchArgumentParser WithNoExit() public LaunchArgumentParser AllowNoArguments()
{ {
ExitIfNoArgs = false; IsAllowedNoArguments = true;
return this; return this;
} }
@ -64,15 +65,15 @@ public class LaunchArgumentParser
} }
public LaunchArgumentParser(params LaunchArgument[] arguments) : this() public LaunchArgumentParser(params LaunchArgument[] arguments) : this()
{ {
for (var i = 0; i < arguments.Length; i++) foreach (var arg in arguments)
Add(arguments[i]); Add(arg);
} }
public void Add(LaunchArgument arg) public void Add(LaunchArgument arg)
{ {
argList.Add(arg); argList.Add(arg);
for(int a=0; a<arg.Aliases.Length; a++) foreach (string alias in arg.Aliases)
argDict.Add(arg.Aliases[a], arg); argDict.Add(alias, arg);
} }
public LaunchArgument Parse(string argAlias) public LaunchArgument Parse(string argAlias)
@ -93,8 +94,8 @@ public class LaunchArgumentParser
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception> /// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
public void ParseAndHandle(string[] args) public void ParseAndHandle(string[] args)
{ {
// show help and throw // show help message and throw ExitAfterHelpException
if (args.Length == 0 && ExitIfNoArgs) if (args.Length == 0 && !IsAllowedNoArguments)
HelpHandler(); HelpHandler();
List<LaunchArgument> execQueue = new(); List<LaunchArgument> execQueue = new();
@ -102,34 +103,13 @@ public class LaunchArgumentParser
for (int i = 0; i < args.Length; i++) for (int i = 0; i < args.Length; i++)
{ {
LaunchArgument arg = Parse(args[i]); LaunchArgument arg = Parse(args[i]);
for (int j = 0; j < arg.Params.Length; j++)
{
if (++i >= args.Length)
throw new Exception($"argument '{arg.Aliases[0]}' should have parameter '{arg.Params[j]}' after it");
arg.Params[j].Value = args[i];
}
switch (arg.RequiredArgsCount)
{
case 0:
if (arg.Handler is null)
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
break;
case 1:
{
if (arg.HandlerWithArg1 is null)
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
if (i + 1 >= args.Length)
throw new Exception($"argument <{args[i]}> should have a parameter after it");
string arg1 = args[++i];
arg.Handler = () => arg.HandlerWithArg1(arg1);
break;
}
case 2:
{
if (arg.HandlerWithArg2 is null)
throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
if (i + 2 >= args.Length)
throw new Exception($"argument <{args[i]}> should have two params after it");
string arg1 = args[++i], arg2 = args[++i];
arg.Handler = () => arg.HandlerWithArg2(arg1, arg2);
break;
}
}
execQueue.Add(arg); execQueue.Add(arg);
} }
@ -137,6 +117,6 @@ public class LaunchArgumentParser
execQueue.Sort((a0, a1) => a0.Priority-a1.Priority); execQueue.Sort((a0, a1) => a0.Priority-a1.Priority);
// finally executing handlers // finally executing handlers
foreach (var a in execQueue) foreach (var a in execQueue)
a.Handler!(); a.Handle();
} }
} }

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<!--package info--> <!--package info-->
<PackageId>DTLib</PackageId> <PackageId>DTLib</PackageId>
<Version>1.6.5</Version> <Version>1.7.1</Version>
<Authors>Timerix</Authors> <Authors>Timerix</Authors>
<Description>Library for all my C# projects</Description> <Description>Library for all my C# projects</Description>
<RepositoryType>GIT</RepositoryType> <RepositoryType>GIT</RepositoryType>

View File

@ -11,8 +11,8 @@ public class CompositeLogger : ILogger
set set
{ {
_debugLogEnabled = value; _debugLogEnabled = value;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].DebugLogEnabled = value; childLogger.DebugLogEnabled = value;
} }
} }
@ -22,8 +22,8 @@ public class CompositeLogger : ILogger
set set
{ {
_infoLogEnabled = true; _infoLogEnabled = true;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].InfoLogEnabled = value; childLogger.InfoLogEnabled = value;
} }
} }
@ -33,8 +33,8 @@ public class CompositeLogger : ILogger
set set
{ {
_warnLogEnabled = value; _warnLogEnabled = value;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].WarnLogEnabled = value; childLogger.WarnLogEnabled = value;
} }
} }
@ -44,46 +44,38 @@ public class CompositeLogger : ILogger
set set
{ {
_errorLogenabled = value; _errorLogenabled = value;
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].ErrorLogEnabled = value; childLogger.ErrorLogEnabled = value;
} }
} }
public ILogFormat Format { get; set; } public ILogFormat Format { get; set; }
public readonly List<ILogger> ChildLoggers;
protected ILogger[] _loggers; private bool _debugLogEnabled;
private bool _debugLogEnabled =
#if DEBUG
true;
#else
false;
#endif
private bool _infoLogEnabled = true; private bool _infoLogEnabled = true;
private bool _warnLogEnabled = true; private bool _warnLogEnabled = true;
private bool _errorLogenabled = true; private bool _errorLogenabled = true;
public CompositeLogger(ILogFormat format, params ILogger[] loggers) public CompositeLogger(ILogFormat format, params ILogger[] childLoggers)
{ {
Format = format; Format = format;
_loggers = loggers; ChildLoggers = new List<ILogger>(childLoggers);
} }
public CompositeLogger(params ILogger[] loggers) : this(new DefaultLogFormat(), loggers) public CompositeLogger(params ILogger[] childLoggers) : this(new DefaultLogFormat(), childLoggers)
{} {}
public void Log(string context, LogSeverity severity, object message, ILogFormat format) public void Log(string context, LogSeverity severity, object message, ILogFormat format)
{ {
if(!this.CheckSeverity(severity)) foreach (var childLogger in ChildLoggers)
return; childLogger.Log(context, severity, message, format);
for (int i = 0; i < _loggers.Length; i++)
_loggers[i].Log(context, severity, message, format);
} }
public void Dispose() public void Dispose()
{ {
for (int i = 0; i < _loggers.Length; i++) foreach (var childLogger in ChildLoggers)
_loggers[i].Dispose(); childLogger.Dispose();
} }
} }