Add support for /_err* error handler pages, include default error template, some cleanups
parent
787e0c860c
commit
b169066235
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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": "."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace ln.http.exceptions
|
|||
public class BadRequestException: HttpException
|
||||
{
|
||||
public BadRequestException()
|
||||
:base(400,"Bad Request")
|
||||
:base(HttpStatusCode.BadRequest)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace ln.http.exceptions
|
|||
public class MethodNotAllowedException : HttpException
|
||||
{
|
||||
public MethodNotAllowedException()
|
||||
:base(405,"Method not allowed")
|
||||
:base(HttpStatusCode.MethodNotAllowed)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
namespace ln.http.exceptions
|
||||
{
|
||||
public class NotFoundException : HttpException
|
||||
{
|
||||
public NotFoundException()
|
||||
: base(HttpStatusCode.NotFound)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ namespace ln.http.exceptions
|
|||
public class UnsupportedMediaTypeException : HttpException
|
||||
{
|
||||
public UnsupportedMediaTypeException()
|
||||
: base(415, "Unsupported Media Type")
|
||||
: base(HttpStatusCode.UnsupportedMediaType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
Loading…
Reference in New Issue