diff --git a/ln.http.service/Program.cs b/ln.http.service/Program.cs index bdfb070..b7f4b7b 100644 --- a/ln.http.service/Program.cs +++ b/ln.http.service/Program.cs @@ -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(); 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; + } + } } diff --git a/ln.http.service/bootstrap.json b/ln.http.service/bootstrap.json index f7d302a..fca3f71 100644 --- a/ln.http.service/bootstrap.json +++ b/ln.http.service/bootstrap.json @@ -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": "." + } } } \ No newline at end of file diff --git a/ln.http.service/error.html b/ln.http.service/error.html new file mode 100644 index 0000000..00de89f --- /dev/null +++ b/ln.http.service/error.html @@ -0,0 +1,11 @@ + + + + + Sorry, we had some trouble serving your request + + +

{{ httpContext.HttpException.HttpStatusCode }} {{ httpContext.HttpException.Message }}

+

The request URI that caused this was {{ httpContext.SourceContext.Request.RequestUri }}

+ + \ No newline at end of file diff --git a/ln.http.service/ln.http.service.csproj b/ln.http.service/ln.http.service.csproj index c1529c0..326fb24 100644 --- a/ln.http.service/ln.http.service.csproj +++ b/ln.http.service/ln.http.service.csproj @@ -2,8 +2,8 @@ Exe - net5.0 9 + net6.0 @@ -11,7 +11,10 @@ - + + + + @@ -20,4 +23,11 @@ + + + + PreserveNewest + + + diff --git a/ln.http.sln b/ln.http.sln index 63f9281..59b57ce 100644 --- a/ln.http.sln +++ b/ln.http.sln @@ -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 diff --git a/ln.http/FileSystemRouter.cs b/ln.http/FileSystemRouter.cs index 2ec833c..4a05b34 100644 --- a/ln.http/FileSystemRouter.cs +++ b/ln.http/FileSystemRouter.cs @@ -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; } } diff --git a/ln.http/HttpContext.cs b/ln.http/HttpContext.cs index fb64e84..4ad93e5 100644 --- a/ln.http/HttpContext.cs +++ b/ln.http/HttpContext.cs @@ -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() diff --git a/ln.http/HttpRouter.cs b/ln.http/HttpRouter.cs index 2653244..214b7ff 100644 --- a/ln.http/HttpRouter.cs +++ b/ln.http/HttpRouter.cs @@ -18,6 +18,8 @@ namespace ln.http private List[] _mappings = new List[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; + } } } diff --git a/ln.http/HttpServer.cs b/ln.http/HttpServer.cs index 64db98f..8f779c6 100644 --- a/ln.http/HttpServer.cs +++ b/ln.http/HttpServer.cs @@ -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) { diff --git a/ln.http/HttpStatusCodes.cs b/ln.http/HttpStatusCodes.cs index 2bbac34..7344dd0 100644 --- a/ln.http/HttpStatusCodes.cs +++ b/ln.http/HttpStatusCodes.cs @@ -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" } diff --git a/ln.http/exceptions/BadRequestException.cs b/ln.http/exceptions/BadRequestException.cs index 2a62857..0364941 100644 --- a/ln.http/exceptions/BadRequestException.cs +++ b/ln.http/exceptions/BadRequestException.cs @@ -13,7 +13,7 @@ namespace ln.http.exceptions public class BadRequestException: HttpException { public BadRequestException() - :base(400,"Bad Request") + :base(HttpStatusCode.BadRequest) { } } diff --git a/ln.http/exceptions/HttpException.cs b/ln.http/exceptions/HttpException.cs index 83df3a2..817e142 100644 --- a/ln.http/exceptions/HttpException.cs +++ b/ln.http/exceptions/HttpException.cs @@ -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; } } diff --git a/ln.http/exceptions/MethodNotAllowedException.cs b/ln.http/exceptions/MethodNotAllowedException.cs index 687f32a..760550f 100644 --- a/ln.http/exceptions/MethodNotAllowedException.cs +++ b/ln.http/exceptions/MethodNotAllowedException.cs @@ -14,7 +14,7 @@ namespace ln.http.exceptions public class MethodNotAllowedException : HttpException { public MethodNotAllowedException() - :base(405,"Method not allowed") + :base(HttpStatusCode.MethodNotAllowed) { } } diff --git a/ln.http/exceptions/NotFoundException.cs b/ln.http/exceptions/NotFoundException.cs new file mode 100644 index 0000000..9cc3b33 --- /dev/null +++ b/ln.http/exceptions/NotFoundException.cs @@ -0,0 +1,11 @@ +using System; +namespace ln.http.exceptions +{ + public class NotFoundException : HttpException + { + public NotFoundException() + : base(HttpStatusCode.NotFound) + { + } + } +} diff --git a/ln.http/exceptions/PayLoadTooLargeException.cs b/ln.http/exceptions/PayLoadTooLargeException.cs index 15850aa..3663d40 100644 --- a/ln.http/exceptions/PayLoadTooLargeException.cs +++ b/ln.http/exceptions/PayLoadTooLargeException.cs @@ -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)); } } } diff --git a/ln.http/exceptions/ResourceNotFoundException.cs b/ln.http/exceptions/ResourceNotFoundException.cs deleted file mode 100644 index 81869f0..0000000 --- a/ln.http/exceptions/ResourceNotFoundException.cs +++ /dev/null @@ -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)) - { - } - } -} diff --git a/ln.http/exceptions/UnsupportedMediaTypeException.cs b/ln.http/exceptions/UnsupportedMediaTypeException.cs index ee795d0..2049566 100644 --- a/ln.http/exceptions/UnsupportedMediaTypeException.cs +++ b/ln.http/exceptions/UnsupportedMediaTypeException.cs @@ -13,7 +13,7 @@ namespace ln.http.exceptions public class UnsupportedMediaTypeException : HttpException { public UnsupportedMediaTypeException() - : base(415, "Unsupported Media Type") + : base(HttpStatusCode.UnsupportedMediaType) { } } diff --git a/ln.http/ln.http.csproj b/ln.http/ln.http.csproj index 1e7ad18..40723ed 100644 --- a/ln.http/ln.http.csproj +++ b/ln.http/ln.http.csproj @@ -1,7 +1,6 @@  - net5.0 true 0.4.3-ci Harald Wolff-Thobaben @@ -9,9 +8,10 @@ (c) 2020 Harald Wolff-Thobaben http server - 9 - 0.6.4 + default + 0.6.5 0.6.2.0 + net5.0;net6.0 diff --git a/ln.http/router/VirtualHostRouter.cs b/ln.http/router/VirtualHostRouter.cs index 6d00957..689cecc 100644 --- a/ln.http/router/VirtualHostRouter.cs +++ b/ln.http/router/VirtualHostRouter.cs @@ -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)); } } } diff --git a/ln.http/websocket/WebSocket.cs b/ln.http/websocket/WebSocket.cs index f97cf5b..fa3b754 100644 --- a/ln.http/websocket/WebSocket.cs +++ b/ln.http/websocket/WebSocket.cs @@ -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"); diff --git a/ln.http/websocket/WebSocketResponse.cs b/ln.http/websocket/WebSocketResponse.cs index 569a2d4..c19c859 100644 --- a/ln.http/websocket/WebSocketResponse.cs +++ b/ln.http/websocket/WebSocketResponse.cs @@ -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");