Add support for /_err* error handler pages, include default error template, some cleanups

master
Harald Wolff 2022-06-07 22:43:14 +02:00
parent 787e0c860c
commit b169066235
21 changed files with 209 additions and 60 deletions

View File

@ -1,6 +1,9 @@
using System.Threading;
using System.IO;
using System.Reflection;
using System.Threading;
using ln.bootstrap;
using ln.http.router;
using ln.templates.html;
namespace ln.http.service
{
@ -8,9 +11,49 @@ namespace ln.http.service
{
static void Main(string[] args)
{
Bootstrap
.DefaultInstance
.ServiceContainer.RegisterService<HttpServiceHelper>();
Bootstrap
.DefaultInstance
.Start();
}
}
class HttpServiceHelper : HttpRouter
{
private TemplateDocument _templateDocument;
public HttpServiceHelper(HttpServer httpServer)
:base(httpServer)
{
using (StreamReader reader = new StreamReader(
Assembly
.GetExecutingAssembly()
.GetManifestResourceStream("ln.http.service.error.html")
)
)
{
_templateDocument = TemplateReader.ReadTemplate(reader);
}
Map(HttpMethod.ANY, "/_err.html", HttpRoutePriority.LOW, this.HttpError);
}
bool HttpError(HttpContext httpContext)
{
HttpResponse httpResponse =
new HttpResponse(httpContext.HttpException?.HttpStatusCode ?? HttpStatusCode.InternalServerError);
RenderContext renderContext = new RenderContext(httpResponse.ContentWriter);
renderContext
.GetEngine()
.SetValue("httpContext", httpContext);
_templateDocument.RenderTemplate(renderContext);
httpContext.Response = httpResponse;
return true;
}
}
}

View File

@ -1,5 +1,5 @@
{
"ln.http.HTTPServer, ln.http": {
"ln.http.HttpServer, ln.http": {
"services": [
],
"properties": {
@ -20,5 +20,12 @@
"properties": {
"DefaultPort": 8443
}
},
"ln.http.FileSystemRouter, ln.http": {
"services": [
],
"parameters": {
"path": "."
}
}
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sorry, we had some trouble serving your request</title>
</head>
<body>
<h1>{{ httpContext.HttpException.HttpStatusCode }} {{ httpContext.HttpException.Message }}</h1>
<p>The request URI that caused this was <em>{{ httpContext.SourceContext.Request.RequestUri }}</em></p>
</body>
</html>

View File

@ -2,8 +2,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>9</LangVersion>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
@ -11,7 +11,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ln.bootstrap" Version="1.2.3" />
<PackageReference Include="ln.bootstrap" Version="1.2.4" />
<PackageReference Include="ln.http" Version="0.6.5" />
<PackageReference Include="ln.http.templates" Version="0.1.0" />
<PackageReference Include="ln.templates" Version="0.3.0" />
</ItemGroup>
<ItemGroup>
@ -20,4 +23,11 @@
</None>
</ItemGroup>
<ItemGroup>
<None Remove="error.html" />
<EmbeddedResource Include="error.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -14,11 +14,7 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -60,6 +56,7 @@ Global
{FE139A5A-A388-4656-AD15-149012EDB9D0}.Release|x64.Build.0 = Release|Any CPU
{FE139A5A-A388-4656-AD15-149012EDB9D0}.Release|x86.ActiveCfg = Release|Any CPU
{FE139A5A-A388-4656-AD15-149012EDB9D0}.Release|x86.Build.0 = Release|Any CPU
{FE139A5A-A388-4656-AD15-149012EDB9D0}.Release|Any CPU.Deploy.0 = Release|Any CPU
{1C1D3A17-A615-4686-90BD-F0E221EAC89C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C1D3A17-A615-4686-90BD-F0E221EAC89C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C1D3A17-A615-4686-90BD-F0E221EAC89C}.Debug|x64.ActiveCfg = Debug|Any CPU

View File

@ -30,7 +30,9 @@ namespace ln.http
public String[] IndexNames => indexNames.ToArray();
private HttpServer _httpServer;
private HttpRouter _parentRouter;
private HttpRouter.HttpMapping _parentMapping;
public FileSystemRouter(HttpServer httpServer, string path)
{
_httpServer = httpServer;
@ -50,6 +52,13 @@ namespace ln.http
{
}
public FileSystemRouter(HttpRouter parentRouter, string mappingPath, string path)
:this(null, path)
{
_parentRouter = parentRouter;
_parentMapping = _parentRouter.Map(HttpMethod.ANY, mappingPath, this.Route);
}
public void AddIndex(string indexName) => indexNames.Add(indexName);
public void RemoveIndex(string indexName) => indexNames.Remove(indexName);
@ -86,6 +95,9 @@ namespace ln.http
{
_httpServer?.RemoveRouter(this.Route);
_httpServer = null;
_parentRouter?.RemoveHttpMapping(_parentMapping);
_parentMapping = null;
_parentRouter = null;
}
}

View File

@ -1,3 +1,5 @@
using System;
using ln.http.exceptions;
using ln.http.router;
namespace ln.http
@ -5,6 +7,8 @@ namespace ln.http
public class HttpContext
{
public HttpServer HttpServer { get; }
public HttpContext SourceContext { get; }
public HttpException HttpException { get; }
public HttpRequest Request { get; set; }
public HttpResponse Response { get; set; }
public HttpPrincipal AuthenticatedPrincipal { get; private set; }
@ -20,6 +24,16 @@ namespace ln.http
RoutableUri = httpRequest.RequestUri.AbsolutePath;
}
public HttpContext(HttpContext sourceContext, HttpException httpException)
{
SourceContext = sourceContext;
HttpException = httpException;
HttpServer = sourceContext.HttpServer;
RoutableUri = String.Format("/_err/{0:3}.html", (int)httpException.HttpStatusCode);
AuthenticatedPrincipal = SourceContext.AuthenticatedPrincipal;
}
public string RoutableUri { get; set; }
public bool Authenticate()

View File

@ -18,6 +18,8 @@ namespace ln.http
private List<HttpMapping>[] _mappings = new List<HttpMapping>[5];
private HttpServer _httpServer;
private HttpRouter _parentRouter;
private HttpMapping _parentMapping;
public HttpRouter()
{
@ -32,6 +34,12 @@ namespace ln.http
httpServer.AddRouter(this);
}
public HttpRouter(HttpRouter parentRouter, string mappingPath)
{
_parentRouter = parentRouter;
_parentMapping = _parentRouter.Map(HttpMethod.ANY, mappingPath, this.Route);
}
public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRouterDelegate routerDelegate) =>
Map(httpMethod, uri, HttpRoutePriority.NORMAL, routerDelegate);
@ -42,6 +50,17 @@ namespace ln.http
return httpMapping;
}
public bool RemoveHttpMapping(HttpMapping httpMapping)
{
foreach (var mappings in _mappings)
{
if (mappings.Contains(httpMapping))
return mappings.Remove(httpMapping);
}
return false;
}
public bool Route(HttpContext httpContext)
{
string residual = "";
@ -52,7 +71,7 @@ namespace ln.http
{
foreach (HttpMapping httpMapping in _mappings[n])
{
if ((httpMapping.Method == httpContext.Request.Method) || (httpMapping.Method == HttpMethod.ANY))
if ((httpMapping.Method == httpContext.Request?.Method) || (httpMapping.Method == HttpMethod.ANY))
{
Match match = httpMapping.UriRegex.Match(httpContext.RoutableUri);
if (match.Success)
@ -168,6 +187,12 @@ namespace ln.http
public void Dispose()
{
_httpServer?.RemoveRouter(this);
_httpServer = null;
_parentRouter?.RemoveHttpMapping(_parentMapping);
_parentMapping = null;
_parentRouter = null;
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using ln.logging;
using ln.http.exceptions;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading;
using ln.protocols.helper;
@ -49,6 +50,7 @@ namespace ln.http
public void Connection(HttpConnection httpConnection) =>
ThreadPool.QueueUserWorkItem((state => ConnectionWorker(httpConnection)));
public void ConnectionWorker(HttpConnection httpConnection)
{
try
@ -67,23 +69,40 @@ namespace ln.http
try
{
foreach (var routerDelegate in _routerDelegates)
{
if (!routerDelegate(httpContext) && httpContext.Response is not null)
break;
}
if (httpContext.Response is null)
httpContext.Response = HttpResponse.NotFound();
if (!RouteRequest(httpContext))
throw new HttpException(HttpStatusCode.NotFound);
}
catch (Exception exception)
{
Logging.Log(exception);
if ((exception is HttpException httpException) && (httpException.HttpResponse != null))
httpContext.Response = httpException.HttpResponse;
if (exception is HttpException httpException)
{
try
{
HttpContext errorContext = new HttpContext(httpContext, httpException);
if (!RouteRequest(errorContext))
{
errorContext.RoutableUri = String.Format("/_err.html",
(int)httpException.HttpStatusCode);
RouteRequest(errorContext);
}
httpContext.Response = errorContext.Response;
}
finally
{
if (httpContext.Response is null)
httpContext.Response = HttpResponse
.InternalServerError()
.Content(
String.Format("An internal error occured ({0})", exception.ToString()));
}
}
else
{
httpContext.Response = HttpResponse.InternalServerError()
.Content(String.Format("An internal error occured ({0})", exception.ToString()));
}
}
try
@ -124,6 +143,17 @@ namespace ln.http
httpConnection.ClientStream.Dispose();
}
}
private bool RouteRequest(HttpContext httpContext)
{
foreach (var routerDelegate in _routerDelegates)
{
if (routerDelegate(httpContext))
return true;
}
return false;
}
private HttpRequest ReadRequest(HttpConnection httpConnection)
{

View File

@ -19,6 +19,7 @@ namespace ln.http
{ 206, "Partial Content"},
{ 207, "Multi Status"},
{ 208, "Already Reported"},
{ 400, "Bad Request"},
{ 403, "Access denied" },
{ 404, "Not Found" },
{ 500, "Internal Error" }

View File

@ -13,7 +13,7 @@ namespace ln.http.exceptions
public class BadRequestException: HttpException
{
public BadRequestException()
:base(400,"Bad Request")
:base(HttpStatusCode.BadRequest)
{
}
}

View File

@ -4,27 +4,27 @@ namespace ln.http.exceptions
{
public class HttpException : Exception
{
public int StatusCode { get; } = 500;
public HttpStatusCode HttpStatusCode { get; }
public virtual HttpResponse HttpResponse { get; protected set; }
public HttpException(String message)
public HttpException(HttpStatusCode httpStatusCode)
: base(httpStatusCode.ToString())
{
HttpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, Exception innerException)
:base(httpStatusCode.ToString(), innerException)
{
HttpStatusCode = httpStatusCode;
}
public HttpException(HttpStatusCode httpStatusCode, String message)
: base(message)
{
HttpStatusCode = httpStatusCode;
}
public HttpException(String message, Exception innerException)
public HttpException(HttpStatusCode httpStatusCode,String message, Exception innerException)
: base(message, innerException)
{
}
public HttpException(int statusCode,String message)
: base(message)
{
StatusCode = statusCode;
}
public HttpException(int statusCode,String message, Exception innerException)
: base(message, innerException)
{
StatusCode = statusCode;
HttpStatusCode = httpStatusCode;
}
}

View File

@ -14,7 +14,7 @@ namespace ln.http.exceptions
public class MethodNotAllowedException : HttpException
{
public MethodNotAllowedException()
:base(405,"Method not allowed")
:base(HttpStatusCode.MethodNotAllowed)
{
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace ln.http.exceptions
{
public class NotFoundException : HttpException
{
public NotFoundException()
: base(HttpStatusCode.NotFound)
{
}
}
}

View File

@ -3,10 +3,9 @@ namespace ln.http.exceptions
{
public class PayloadTooLargeException: HttpException
{
public PayloadTooLargeException(int payloadsize,int limit)
:base(513,"Payload too large")
public PayloadTooLargeException()
:base(HttpStatusCode.PayloadTooLarge)
{
HttpResponse = new HttpResponse(HttpStatusCode.PayloadTooLarge).Content(String.Format("Payload too large ({0} > {1})", payloadsize, limit));
}
}
}

View File

@ -1,11 +0,0 @@
using System;
namespace ln.http.exceptions
{
public class ResourceNotFoundException : HttpException
{
public ResourceNotFoundException(String resourcePath, String nextResource)
: base(404, String.Format("Could not find resource \"{0}\" within \"{1}\"", nextResource, resourcePath))
{
}
}
}

View File

@ -13,7 +13,7 @@ namespace ln.http.exceptions
public class UnsupportedMediaTypeException : HttpException
{
public UnsupportedMediaTypeException()
: base(415, "Unsupported Media Type")
: base(HttpStatusCode.UnsupportedMediaType)
{
}
}

View File

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.4.3-ci</Version>
<Authors>Harald Wolff-Thobaben</Authors>
@ -9,9 +8,10 @@
<Description />
<Copyright>(c) 2020 Harald Wolff-Thobaben</Copyright>
<PackageTags>http server</PackageTags>
<LangVersion>9</LangVersion>
<PackageVersion>0.6.4</PackageVersion>
<LangVersion>default</LangVersion>
<PackageVersion>0.6.5</PackageVersion>
<AssemblyVersion>0.6.2.0</AssemblyVersion>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>

View File

@ -43,7 +43,7 @@ namespace ln.http.router
if (DefaultRoute != null)
return DefaultRoute(httpContext);
throw new HttpException(410, string.Format("Gone. Hostname {0} not found on this server.", httpContext.Request.Host));
throw new HttpException(HttpStatusCode.Gone, string.Format("Gone. Hostname {0} not found on this server.", httpContext.Request.Host));
}
}
}

View File

@ -45,10 +45,10 @@ namespace ln.http.websocket
Stream = httpRequest.ConnectionStream;
if ((!httpRequest.GetRequestHeader("upgrade", "").Contains("websocket")) && (!httpRequest.GetRequestHeader("connection", "").Contains("Upgrade")))
throw new HttpException(400, "This resource is a websocket endpoint only");
throw new HttpException(HttpStatusCode.BadRequest, "This resource is a websocket endpoint only");
if (!httpRequest.GetRequestHeader("Sec-WebSocket-Version", "").Equals("13"))
throw new HttpException(400, "Unsupported Protocol Version (WebSocket)");
throw new HttpException(HttpStatusCode.BadRequest, "Unsupported Protocol Version (WebSocket)");
String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key");

View File

@ -161,10 +161,10 @@ namespace ln.http.websocket
Stream = stream;
if ((!httpRequest.GetRequestHeader("upgrade", "").Contains("websocket")) && (!httpRequest.GetRequestHeader("connection", "").Contains("Upgrade")))
throw new HttpException(400, "This resource is a websocket endpoint only");
throw new HttpException(HttpStatusCode.BadRequest, "This resource is a websocket endpoint only");
if (!httpRequest.GetRequestHeader("Sec-WebSocket-Version", "").Equals("13"))
throw new HttpException(400, "Unsupported Protocol Version (WebSocket)");
throw new HttpException(HttpStatusCode.BadRequest, "Unsupported Protocol Version (WebSocket)");
String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key");