Created new project: DTLib.Web
This commit is contained in:
parent
f9e4e533bb
commit
9082d7a4d0
@ -1 +1 @@
|
|||||||
Subproject commit b19c39b68463ae15c1f63051583da9f74d97baac
|
Subproject commit bb96774c37ef7b636139872b972e5252076bc913
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<!--compilation properties-->
|
<!--compilation properties-->
|
||||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||||
<!--language features-->
|
<!--language features-->
|
||||||
<LangVersion>12</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
30
DTLib.Web/DTLib.Web.csproj
Normal file
30
DTLib.Web/DTLib.Web.csproj
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<!--package info-->
|
||||||
|
<PackageId>DTLib.Web</PackageId>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<Authors>Timerix</Authors>
|
||||||
|
<Description>HTTP Server with simple routing</Description>
|
||||||
|
<RepositoryType>GIT</RepositoryType>
|
||||||
|
<RepositoryUrl>https://timerix.ddns.net:3322/Timerix/DTLib</RepositoryUrl>
|
||||||
|
<PackageProjectUrl>https://timerix.ddns.net:3322/Timerix/DTLib</PackageProjectUrl>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<!--compilation properties-->
|
||||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||||
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
|
<!--language features-->
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!--DTLib dependencies-->
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<ProjectReference Include="..\DTLib\DTLib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
|
||||||
|
<PackageReference Include="DTLib" Version="1.6.*" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
6
DTLib.Web/Routes/DelegateRoute.cs
Normal file
6
DTLib.Web/Routes/DelegateRoute.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace DTLib.Web.Routes;
|
||||||
|
|
||||||
|
public class DelegateRoute(Func<HttpListenerContext, Task<HttpStatusCode>> routeHandler) : Route
|
||||||
|
{
|
||||||
|
public override Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx) => routeHandler(ctx);
|
||||||
|
}
|
||||||
6
DTLib.Web/Routes/Route.cs
Normal file
6
DTLib.Web/Routes/Route.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace DTLib.Web.Routes;
|
||||||
|
|
||||||
|
public abstract class Route
|
||||||
|
{
|
||||||
|
public abstract Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx);
|
||||||
|
}
|
||||||
33
DTLib.Web/Routes/ServeFilesRoute.cs
Normal file
33
DTLib.Web/Routes/ServeFilesRoute.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
namespace DTLib.Web.Routes;
|
||||||
|
|
||||||
|
public class ServeFilesRoute(IOPath _publicDir, string _homePageUrl = "index.html") : Route
|
||||||
|
{
|
||||||
|
public override async Task<HttpStatusCode> HandleRequest(HttpListenerContext ctx)
|
||||||
|
{
|
||||||
|
if (ctx.Request.HttpMethod != "GET")
|
||||||
|
return HttpStatusCode.BadRequest;
|
||||||
|
|
||||||
|
string requestPath = ctx.Request.Url?.AbsolutePath ?? "/";
|
||||||
|
if (requestPath == "/")
|
||||||
|
requestPath = _homePageUrl;
|
||||||
|
string ext = Path.Extension(requestPath).Str;
|
||||||
|
IOPath filePath = Path.Concat(_publicDir, requestPath);
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
return HttpStatusCode.NotFound;
|
||||||
|
|
||||||
|
string contentType = ext switch
|
||||||
|
{
|
||||||
|
"html" => "text/html",
|
||||||
|
"css" => "text/css",
|
||||||
|
"js" or "jsx" or "ts" or "tsx" or "map" => "text/javascript",
|
||||||
|
_ => "binary/octet-stream"
|
||||||
|
};
|
||||||
|
ctx.Response.Headers.Set("Content-Type", contentType);
|
||||||
|
ctx.Response.Headers.Set("Content-Disposition", "attachment filename=" + filePath.LastName());
|
||||||
|
|
||||||
|
var fileStream = File.OpenRead(filePath);
|
||||||
|
ctx.Response.ContentLength64 = fileStream.Length;
|
||||||
|
await fileStream.CopyToAsync(ctx.Response.OutputStream);
|
||||||
|
return HttpStatusCode.OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
99
DTLib.Web/WebApp.cs
Normal file
99
DTLib.Web/WebApp.cs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
global using System;
|
||||||
|
global using System.Collections.Generic;
|
||||||
|
global using System.Text;
|
||||||
|
global using System.Threading;
|
||||||
|
global using System.Threading.Tasks;
|
||||||
|
global using DTLib;
|
||||||
|
global using DTLib.Demystifier;
|
||||||
|
global using DTLib.Filesystem;
|
||||||
|
global using DTLib.Logging;
|
||||||
|
global using System.Net;
|
||||||
|
using DTLib.Web.Routes;
|
||||||
|
|
||||||
|
namespace DTLib.Web;
|
||||||
|
|
||||||
|
internal class WebApp
|
||||||
|
{
|
||||||
|
/// route for base url
|
||||||
|
public Route? HomePageRoute = null;
|
||||||
|
/// route for any url that doesn't have its own handler
|
||||||
|
public Route? DefaultRoute = null;
|
||||||
|
|
||||||
|
private ContextLogger _logger;
|
||||||
|
private string _baseUrl;
|
||||||
|
private CancellationToken _stopToken;
|
||||||
|
private Dictionary<string, Route> routes = new();
|
||||||
|
|
||||||
|
public WebApp(ILogger logger, string baseUrl, CancellationToken stopToken)
|
||||||
|
{
|
||||||
|
_logger = new ContextLogger(nameof(WebApp), logger);
|
||||||
|
_baseUrl = baseUrl;
|
||||||
|
_stopToken = stopToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Run()
|
||||||
|
{
|
||||||
|
_logger.LogInfo($"starting webserver at {_baseUrl} ...");
|
||||||
|
HttpListener server = new HttpListener();
|
||||||
|
server.Prefixes.Add(_baseUrl);
|
||||||
|
server.Start();
|
||||||
|
_logger.LogInfo("server started");
|
||||||
|
long requestId = 0;
|
||||||
|
while (!_stopToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var ctx = await server.GetContextAsync();
|
||||||
|
HandleRequestAsync(ctx, requestId);
|
||||||
|
requestId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop
|
||||||
|
server.Stop();
|
||||||
|
_logger.LogInfo("server stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once AsyncVoidMethod
|
||||||
|
private async void HandleRequestAsync(HttpListenerContext ctx, long requestId)
|
||||||
|
{
|
||||||
|
string logContext = $"Request {requestId}";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInfo(logContext, $"[{ctx.Request.HttpMethod}] {ctx.Request.RawUrl} from {ctx.Request.RemoteEndPoint} ...");
|
||||||
|
var status = await Resolve(ctx);
|
||||||
|
_logger.LogInfo(logContext, status);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarn(logContext, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1 @@
|
|||||||
Subproject commit 895d53d362c83b114c9ca06467d2e24d407190c3
|
Subproject commit 9360dfe30549732bb95d16268218a6458d2229b3
|
||||||
@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Logging.Microsoft", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.XXHash", "DTLib.XXHash\DTLib.XXHash\DTLib.XXHash.csproj", "{C7029741-816D-41B2-A2C4-E20565B1739D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.XXHash", "DTLib.XXHash\DTLib.XXHash\DTLib.XXHash.csproj", "{C7029741-816D-41B2-A2C4-E20565B1739D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Web", "DTLib.Web\DTLib.Web.csproj", "{9A3220EB-CCED-4172-9BD4-C3700FF36539}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -44,6 +46,10 @@ Global
|
|||||||
{C7029741-816D-41B2-A2C4-E20565B1739D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C7029741-816D-41B2-A2C4-E20565B1739D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C7029741-816D-41B2-A2C4-E20565B1739D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9A3220EB-CCED-4172-9BD4-C3700FF36539}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||||
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
<!--language features-->
|
<!--language features-->
|
||||||
<LangVersion>12</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<ImplicitUsings>disable</ImplicitUsings>
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
|
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow>
|
||||||
|
|||||||
2
pack.sh
2
pack.sh
@ -16,7 +16,7 @@ function build_package() {
|
|||||||
|
|
||||||
packages_to_build="$@"
|
packages_to_build="$@"
|
||||||
if [ -z "$packages_to_build" ]; then
|
if [ -z "$packages_to_build" ]; then
|
||||||
packages_to_build='DTLib.Demystifier DTLib.XXHash DTLib DTLib.Logging.Microsoft'
|
packages_to_build='DTLib.Demystifier DTLib.XXHash DTLib DTLib.Logging.Microsoft DTLib.Web'
|
||||||
fi
|
fi
|
||||||
echo "building packages $packages_to_build"
|
echo "building packages $packages_to_build"
|
||||||
for p in $packages_to_build; do
|
for p in $packages_to_build; do
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user