From fda7d695d19a1698fccd6bba31af8546e4d0cf5b Mon Sep 17 00:00:00 2001 From: Harald Wolff-Thobaben Date: Mon, 7 Feb 2022 09:29:30 +0100 Subject: [PATCH] V0.5.0 --- .idea/.idea.ln.http/.idea/encodings.xml | 4 + .idea/.idea.ln.http/.idea/indexLayout.xml | 8 + .idea/.idea.ln.http/.idea/vcs.xml | 6 + ln.http.tests/UnitTest1.cs | 2 +- ln.http.tests/test.txt | 3 + ln.http/HTTPServer.cs | 37 +-- ln.http/HttpArgumentSource.cs | 15 + ln.http/HttpArgumentSourceAttribute.cs | 18 ++ ln.http/HttpContext.cs | 24 ++ ln.http/HttpEndpointController.cs | 181 ++++++++++++ ln.http/HttpMethod.cs | 7 +- ln.http/HttpPrincipal.cs | 49 ++++ ln.http/HttpRequest.cs | 12 +- ln.http/HttpResponse.cs | 28 +- ln.http/HttpRolePermission.cs | 10 + ln.http/HttpRouter.cs | 271 ++++++++++++++++++ ln.http/IHttpRouter.cs | 5 +- ln.http/MapAttribute.cs | 18 ++ ln.http/QueryStringParameters.cs | 3 +- ln.http/RoleAuthorization.cs | 38 +++ .../exceptions/PayLoadTooLargeException.cs | 12 + ln.http/io/UnbufferedStreamreader.cs | 70 ----- ln.http/listener/HttpListener.cs | 5 +- ln.http/listener/Listener.cs | 14 +- ln.http/ln.http.csproj | 3 +- .../CRUDObjectContainer.RegisteredType.cs | 71 ----- ln.http/rest/CRUDObjectContainer.cs | 98 ------- ln.http/rest/FieldDescriptor.cs | 47 --- ln.http/rest/FieldValueList.cs | 23 -- ln.http/rest/RestAPIAdapter.cs | 22 -- .../{FileRouter.cs => FileRequestRouter.cs} | 12 +- ln.http/router/HttpAccessRights.cs | 13 + ...ggingRouter.cs => LoggingRequestRouter.cs} | 29 +- ...RouterTarget.cs => RequestRouterTarget.cs} | 2 + ln.http/router/SimpleRouter.cs | 113 -------- ln.http/router/StaticRouter.cs | 14 +- ln.http/router/VirtualHostRouter.cs | 29 +- ...ketRouter.cs => WebsocketRequestRouter.cs} | 6 +- ln.http/session/Session.cs | 71 ----- ln.http/session/SessionCache.cs | 77 ----- 40 files changed, 800 insertions(+), 670 deletions(-) create mode 100644 .idea/.idea.ln.http/.idea/encodings.xml create mode 100644 .idea/.idea.ln.http/.idea/indexLayout.xml create mode 100644 .idea/.idea.ln.http/.idea/vcs.xml create mode 100644 ln.http.tests/test.txt create mode 100644 ln.http/HttpArgumentSource.cs create mode 100644 ln.http/HttpArgumentSourceAttribute.cs create mode 100644 ln.http/HttpContext.cs create mode 100644 ln.http/HttpEndpointController.cs create mode 100644 ln.http/HttpPrincipal.cs create mode 100644 ln.http/HttpRolePermission.cs create mode 100644 ln.http/HttpRouter.cs create mode 100644 ln.http/MapAttribute.cs create mode 100644 ln.http/RoleAuthorization.cs create mode 100644 ln.http/exceptions/PayLoadTooLargeException.cs delete mode 100644 ln.http/io/UnbufferedStreamreader.cs delete mode 100644 ln.http/rest/CRUDObjectContainer.RegisteredType.cs delete mode 100644 ln.http/rest/CRUDObjectContainer.cs delete mode 100644 ln.http/rest/FieldDescriptor.cs delete mode 100644 ln.http/rest/FieldValueList.cs delete mode 100644 ln.http/rest/RestAPIAdapter.cs rename ln.http/router/{FileRouter.cs => FileRequestRouter.cs} (59%) create mode 100644 ln.http/router/HttpAccessRights.cs rename ln.http/router/{LoggingRouter.cs => LoggingRequestRouter.cs} (56%) rename ln.http/router/{RouterTarget.cs => RequestRouterTarget.cs} (99%) delete mode 100644 ln.http/router/SimpleRouter.cs rename ln.http/router/{WebsocketRouter.cs => WebsocketRequestRouter.cs} (76%) delete mode 100644 ln.http/session/Session.cs delete mode 100644 ln.http/session/SessionCache.cs diff --git a/.idea/.idea.ln.http/.idea/encodings.xml b/.idea/.idea.ln.http/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.ln.http/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.ln.http/.idea/indexLayout.xml b/.idea/.idea.ln.http/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.ln.http/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.ln.http/.idea/vcs.xml b/.idea/.idea.ln.http/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.ln.http/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ln.http.tests/UnitTest1.cs b/ln.http.tests/UnitTest1.cs index 4349f07..6f29432 100644 --- a/ln.http.tests/UnitTest1.cs +++ b/ln.http.tests/UnitTest1.cs @@ -19,7 +19,7 @@ namespace ln.http.tests if (server != null) return; - SimpleRouter testRouter = new SimpleRouter(); + HttpRouter testRouter = new HttpRouter(); StaticRouter staticRouter = new StaticRouter(AppContext.BaseDirectory); testRouter.AddSimpleRoute("/static/*", staticRouter); diff --git a/ln.http.tests/test.txt b/ln.http.tests/test.txt new file mode 100644 index 0000000..79eba15 --- /dev/null +++ b/ln.http.tests/test.txt @@ -0,0 +1,3 @@ +ABCDEFGHIJKLMNOPQRSTUVWXYZ +0123456789 +ÄÖÜ diff --git a/ln.http/HTTPServer.cs b/ln.http/HTTPServer.cs index dab3120..24212e1 100644 --- a/ln.http/HTTPServer.cs +++ b/ln.http/HTTPServer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using ln.logging; using ln.threading; -using ln.application; using ln.http.listener; using ln.http.connections; using ln.http.exceptions; @@ -19,7 +18,9 @@ namespace ln.http public static int defaultPort = 8080; public static bool exclusivePortListener = false; - public IHttpRouter Router { get; set; } + public static bool DefaultRouteAuthentication { get; set; } = false; + + public HttpRouterDelegate Router { get; set; } public bool IsRunning => !shutdown; public Logger Logger { get; set; } @@ -36,17 +37,17 @@ namespace ln.http { Logger = Logger.Default; } - public HTTPServer(IHttpRouter router) + public HTTPServer(HttpRouterDelegate router) : this() { Router = router; } - public HTTPServer(Listener listener, IHttpRouter router) + public HTTPServer(Listener listener, HttpRouterDelegate router) : this(router) { AddListener(listener); } - public HTTPServer(Endpoint endpoint, IHttpRouter router) + public HTTPServer(Endpoint endpoint, HttpRouterDelegate router) : this(new HttpListener(endpoint), router) { } public void AddListener(Listener listener) @@ -129,36 +130,36 @@ namespace ln.http { using (HttpRequest httpRequest = connection.ReadRequest(this)) { - HttpResponse httpResponse = null; - if (httpRequest == null) break; + HttpContext httpContext = new HttpContext() + { Request = httpRequest, RoutableUri = httpRequest.RequestUri.AbsolutePath }; + try { - httpResponse = Router.Route(new HttpRoutingContext(httpRequest), httpRequest); - if (httpResponse == null) - httpResponse = HttpResponse.NotFound(); + if (!Router(httpContext) && httpContext.Response is null) + httpContext.Response = HttpResponse.NotFound(); } catch (Exception exception) { Logging.Log(exception); if ((exception is HttpException httpException) && (httpException.HttpResponse != null)) - httpResponse = httpException.HttpResponse; + httpContext.Response = httpException.HttpResponse; else - httpResponse = HttpResponse.InternalServerError() + httpContext.Response = HttpResponse.InternalServerError() .Content(String.Format("An internal error occured ({0})", exception.ToString())); } - httpResponse.WriteTo(connection.GetStream()); - httpResponse?.ContentStream?.Dispose(); + httpContext.Response.WriteTo(connection.GetStream()); + httpContext.Response?.ContentStream?.Dispose(); - keepalive = httpResponse.GetHeader("connection", "keep-alive").Equals("keep-alive") && httpRequest + keepalive = httpContext.Response.GetHeader("connection", "keep-alive").Equals("keep-alive") && httpRequest .GetRequestHeader("connection", httpRequest.Protocol.Equals("HTTP/1.1") ? "keep-alive" : "close").Contains("keep-alive", StringComparison.InvariantCultureIgnoreCase); } - } while (keepalive); + } while (keepalive && false); lock (this.currentConnections) currentConnections.Remove(connection); @@ -207,6 +208,7 @@ namespace ln.http // ; } + /* public static void StartSimpleServer(string[] arguments) { ArgumentContainer argumentContainer = new ArgumentContainer(new Argument[] @@ -218,7 +220,7 @@ namespace ln.http argumentContainer.Parse(ref arguments); - SimpleRouter router = new SimpleRouter(); + HttpRouter router = new HttpRouter(); router.AddSimpleRoute("/*", new RouterTarget((request) => { HttpResponse response = new HttpResponse(request); @@ -244,6 +246,7 @@ namespace ln.http new LoggingRouter(router)); server.Start(); } + */ } } diff --git a/ln.http/HttpArgumentSource.cs b/ln.http/HttpArgumentSource.cs new file mode 100644 index 0000000..33ae100 --- /dev/null +++ b/ln.http/HttpArgumentSource.cs @@ -0,0 +1,15 @@ + + +using System; + +namespace ln.http +{ + [Flags] + public enum HttpArgumentSource : int{ + AUTO = -1, + CONTENT = (1<<0), + PARAMETER = (1<<1), + HEADER = (1<<2), + QUERY = (1<<3) + } +} \ No newline at end of file diff --git a/ln.http/HttpArgumentSourceAttribute.cs b/ln.http/HttpArgumentSourceAttribute.cs new file mode 100644 index 0000000..9a1dbfe --- /dev/null +++ b/ln.http/HttpArgumentSourceAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace ln.http +{ + [AttributeUsage(AttributeTargets.Parameter)] + public class HttpArgumentSourceAttribute : Attribute + { + public HttpArgumentSource ArgumentSource { get; set; } + public string ArgumentName { get; set; } + + public HttpArgumentSourceAttribute(){} + + public HttpArgumentSourceAttribute(HttpArgumentSource argumentSource) + { + ArgumentSource = argumentSource; + } + } +} \ No newline at end of file diff --git a/ln.http/HttpContext.cs b/ln.http/HttpContext.cs new file mode 100644 index 0000000..c500aa8 --- /dev/null +++ b/ln.http/HttpContext.cs @@ -0,0 +1,24 @@ +using ln.http.router; + +namespace ln.http +{ + public class HttpContext + { + public HttpRequest Request { get; set; } + public HttpResponse Response { get; set; } + public HttpPrincipal AuthenticatedPrincipal { get; private set; } + + public string RoutableUri { get; set; } + + public bool Authenticate(HttpAuthenticationDelegate authenticationDelegate) + { + if (authenticationDelegate(this, out HttpPrincipal principal)) + { + AuthenticatedPrincipal = principal; + return true; + } + return false; + } + public void DeAuthenticate() => AuthenticatedPrincipal = null; + } +} \ No newline at end of file diff --git a/ln.http/HttpEndpointController.cs b/ln.http/HttpEndpointController.cs new file mode 100644 index 0000000..5291fe1 --- /dev/null +++ b/ln.http/HttpEndpointController.cs @@ -0,0 +1,181 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Reflection; +using ln.json; +using ln.json.mapping; +using ln.protocols.helper; +using ln.type; + +namespace ln.http +{ + public abstract class HttpEndpointController : HttpRouter + { + public HttpEndpointController() + { + Initialize(); + } + + void Initialize() + { + foreach (MethodInfo methodInfo in GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + { + foreach (MapAttribute mapAttribute in methodInfo.GetCustomAttributes()) + { + MappedEndpoint mappedEndpoint = CreateMapping(mapAttribute, methodInfo); + Map(mappedEndpoint.HttpMethod, mappedEndpoint.Path, mappedEndpoint.Route); + } + } + } + + MappedEndpoint CreateMapping(MapAttribute mapAttribute, MethodInfo methodInfo) + { + if (methodInfo.ReturnType == typeof(void)) + return new MappedEndpoint.VoidEndpoint(this, mapAttribute, methodInfo); + return new MappedEndpoint.NonVoidEndpoint(this, mapAttribute, methodInfo); + } + + public abstract class MappedEndpoint + { + public HttpMethod HttpMethod { get; } + public string Path { get; } + private HttpEndpointController EndpointController; + + public MethodInfo MethodInfo { get; } + + private ParameterInfo[] _parameterInfos; + private HttpArgumentSourceAttribute[] _argumentSourceAttributes; + private Type _returnType; + + public MappedEndpoint(HttpEndpointController endpointController, MapAttribute mapAttribute, MethodInfo methodInfo) + { + EndpointController = endpointController; + HttpMethod = mapAttribute.Method; + Path = mapAttribute.Path; + MethodInfo = methodInfo; + _parameterInfos = MethodInfo.GetParameters(); + _returnType = MethodInfo.ReturnType; + _argumentSourceAttributes = _parameterInfos + .Select((pi) => pi.GetCustomAttribute()).ToArray(); + } + + public abstract bool Route(HttpContext httpContext); + + bool TryApplyParameters(HttpContext httpContext, out object[] parameters) + { + parameters = new object[_parameterInfos.Length]; + + for (int n = 0; n < _parameterInfos.Length; n++) + { + ParameterInfo parameterInfo = _parameterInfos[n]; + + if (parameterInfo.ParameterType.Equals(typeof(HttpContext))) + parameters[n] = httpContext; + else if (parameterInfo.ParameterType.Equals(typeof(HttpRequest))) + parameters[n] = httpContext.Request; + else if (parameterInfo.ParameterType.Equals(typeof(HttpPrincipal))) + parameters[n] = httpContext.AuthenticatedPrincipal; + else if (TryFindArgumentByName( + httpContext, + _argumentSourceAttributes[n]?.ArgumentSource ?? HttpArgumentSource.AUTO, + _argumentSourceAttributes[n]?.ArgumentName ?? _parameterInfos[n].Name, + out string parameterValue + )) + { + if (!Cast.To(parameterValue, _parameterInfos[n].ParameterType, out parameters[n])) + { + parameters[n] = TypeDescriptor + .GetConverter(parameterInfo.ParameterType) + .ConvertFromInvariantString(parameterValue); + } + } + else if (_argumentSourceAttributes[n].ArgumentSource == HttpArgumentSource.CONTENT) + { + if (httpContext.Request.Headers.TryGetValue("Content-Type", out string contentType) && + contentType.Equals("application/json")) + { + using (TextReader reader = httpContext.Request.ContentStream.TextReader()) + parameters[n] = JSONMapper.DefaultMapper.FromJson(JSONParser.Parse(reader), _parameterInfos[n].ParameterType); + } + } + else if (_parameterInfos[n].HasDefaultValue) + { + parameters[n] = _parameterInfos[n].DefaultValue; + } + else + return false; + } + + return true; + } + + bool TryFindArgumentByName(HttpContext httpContext, HttpArgumentSource argumentSource, string parameterName, out string parameterValue) + { + if (((argumentSource & HttpArgumentSource.PARAMETER) == HttpArgumentSource.PARAMETER) && httpContext.Request.TryGetParameter(parameterName, out parameterValue)) + return true; + else if (((argumentSource & HttpArgumentSource.HEADER) == HttpArgumentSource.HEADER) && + (httpContext.Request.Headers.TryGetValue(parameterName, out parameterValue))) + return true; + else if (((argumentSource & HttpArgumentSource.QUERY) == HttpArgumentSource.QUERY) && + (httpContext.Request.Query.TryGetValue(parameterName, out parameterValue))) + return true; + + parameterValue = null; + return false; + } + + private object InvokeMethod(HttpContext httpContext) + { + if (!TryApplyParameters(httpContext, out object[] parameters)) + return HttpResponse.InternalServerError().Content("could not apply parameters"); + else + return MethodInfo.Invoke(EndpointController, parameters); + } + + public class NonVoidEndpoint : MappedEndpoint + { + public NonVoidEndpoint(HttpEndpointController endpointController, MapAttribute mapAttribute, MethodInfo methodInfo) + : base(endpointController, mapAttribute, methodInfo) + { + } + + public override bool Route(HttpContext httpContext) + { + object returnedValue = InvokeMethod(httpContext); + + if (returnedValue is HttpResponse httpResponse) + httpContext.Response = httpResponse; + else if ((returnedValue is JSONValue jsonResult) || (JSONMapper.DefaultMapper.Serialize(returnedValue, out jsonResult))) + httpContext.Response = HttpResponse.Default(httpContext.Request.Method) + .Content(jsonResult); + else + httpContext.Response = HttpResponse.InternalServerError().Content("Method result could not be serialized"); + return true; + + } + } + + public class VoidEndpoint : MappedEndpoint + { + public VoidEndpoint(HttpEndpointController endpointController, MapAttribute mapAttribute, MethodInfo methodInfo) + : base(endpointController, mapAttribute, methodInfo) + { + } + + public override bool Route(HttpContext httpContext) + { + object returnedValue = InvokeMethod(httpContext); + if (returnedValue is HttpResponse httpResponse) + httpContext.Response = httpResponse; + else + httpContext.Response ??= HttpResponse.Default(httpContext.Request.Method); + + return true; + } + } + + } + + } +} \ No newline at end of file diff --git a/ln.http/HttpMethod.cs b/ln.http/HttpMethod.cs index e5a5c15..6caad1b 100644 --- a/ln.http/HttpMethod.cs +++ b/ln.http/HttpMethod.cs @@ -1,8 +1,13 @@ +using System; + namespace ln.http { + [Flags] public enum HttpMethod { - UNKOWN, HEAD, GET, POST, PUT, PATCH, DELETE, CONNECT, OPTIONS, TRACE, PROPFIND, MKCOL, LOCK, UNLOCK + NONE = 0, + HEAD, GET, POST, PUT, PATCH, DELETE, CONNECT, OPTIONS, TRACE, PROPFIND, MKCOL, LOCK, UNLOCK, + ANY = -1 } } \ No newline at end of file diff --git a/ln.http/HttpPrincipal.cs b/ln.http/HttpPrincipal.cs new file mode 100644 index 0000000..0f94ca6 --- /dev/null +++ b/ln.http/HttpPrincipal.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Text; +using ln.http.router; + +namespace ln.http +{ + public class HttpPrincipal + { + public string UniqueId { get; set; } + public string Username { get; set; } + + public HttpPrincipal AuthenticatedPrincipal { get; set; } + + private Dictionary permissions = new Dictionary(); + public IEnumerable> Permissions => permissions; + + public void AddPermission(string roleName, HttpAccessRights accessRights) + { + if (permissions.TryGetValue(roleName, out HttpAccessRights roleAccessFlags)) + { + roleAccessFlags |= accessRights; + permissions[roleName] = roleAccessFlags; + } + else + { + permissions.Add(roleName, accessRights); + } + } + public void RemovePermission(string roleName, HttpAccessRights accessRights) + { + if (permissions.TryGetValue(roleName, out HttpAccessRights roleAccessFlags)) + { + roleAccessFlags &= ~accessRights; + permissions[roleName] = roleAccessFlags; + } + } + public bool HasPermission(string roleName, HttpAccessRights accessRights) => + permissions.TryGetValue(roleName, out HttpAccessRights roleAccessFlags) && + ((roleAccessFlags & accessRights) == accessRights); + + public override string ToString() + { + if (AuthenticatedPrincipal is null) + return string.Format("{0}[{1}]", Username, UniqueId); + else + return string.Format("{2}=>{0}[{1}]", Username, UniqueId, AuthenticatedPrincipal.ToString()); + } + } +} \ No newline at end of file diff --git a/ln.http/HttpRequest.cs b/ln.http/HttpRequest.cs index a3e9acc..6ec36f8 100644 --- a/ln.http/HttpRequest.cs +++ b/ln.http/HttpRequest.cs @@ -28,8 +28,7 @@ namespace ln.http public Uri BaseUri { get; } public Uri RequestUri { get; } - - + Dictionary requestCookies; Dictionary requestParameters; @@ -42,7 +41,7 @@ namespace ln.http if (Enum.TryParse(BaseRequest.Method, out HttpMethod httpMethod)) Method = httpMethod; else - Method = HttpMethod.UNKOWN; + Method = HttpMethod.NONE; Headers.TryGetValue("Host", out string host); @@ -73,8 +72,13 @@ namespace ln.http RequestUri = new Uri(BaseUri, BaseRequest.RequestUri); Query = new QueryStringParameters(RequestUri.Query); + requestParameters = new Dictionary(); + requestCookies = new Dictionary(); + if (Headers.TryGetValue("Cookie", out string cookies)) SetupCookies(cookies); + + } private void SetupCookies(string cookies) @@ -129,6 +133,8 @@ namespace ln.http return value; } + public bool TryGetParameter(String parameterName, out string parameterValue) => + requestParameters.TryGetValue(parameterName, out parameterValue); public void SetParameter(String parameterName, String parameterValue) => requestParameters[parameterName] = parameterValue; diff --git a/ln.http/HttpResponse.cs b/ln.http/HttpResponse.cs index e03c563..ab58289 100644 --- a/ln.http/HttpResponse.cs +++ b/ln.http/HttpResponse.cs @@ -2,6 +2,8 @@ using System.IO; using System.Collections.Generic; using System.Linq; +using ln.json; +using ln.json.mapping; namespace ln.http { @@ -40,9 +42,7 @@ namespace ln.http public HttpResponse(HttpRequest httpRequest, Stream contentStream) : this(contentStream) { } public HttpResponse(Stream contentStream) { - ContentStream = contentStream; - ContentWriter = null; - HasCustomContentStream = true; + Content(contentStream); HttpStatusCode = HttpStatusCode.OK; SetHeader("content-type", "text/html"); @@ -153,6 +153,18 @@ namespace ln.http public static HttpResponse ServiceUnavailable() => new HttpResponse(HttpStatusCode.ServiceUnavailable); public static HttpResponse GatewayTimeout() => new HttpResponse(HttpStatusCode.GatewayTimeout); + public static HttpResponse Default(HttpMethod httpMethod) + { + switch (httpMethod) + { + case HttpMethod.DELETE: + return NoContent(); + case HttpMethod.POST: + return Created(); + default: + return OK(); + } + } public HttpResponse Content(Exception exception) @@ -171,6 +183,16 @@ namespace ln.http return this; } + public HttpResponse Content(JSONValue json) => ContentType("application/json").Content(json.ToString()); + + public HttpResponse Content(Stream contentStream) + { + ContentStream = contentStream; + ContentWriter = null; + HasCustomContentStream = true; + return this; + } + public virtual void WriteTo(Stream stream) { SetHeader("Content-Length", ContentStream.Length.ToString()); diff --git a/ln.http/HttpRolePermission.cs b/ln.http/HttpRolePermission.cs new file mode 100644 index 0000000..ff2a3eb --- /dev/null +++ b/ln.http/HttpRolePermission.cs @@ -0,0 +1,10 @@ +using ln.http.router; + +namespace ln.http +{ + public class HttpRolePermission + { + public string RoleName { get; set; } + public HttpAccessRights AccessRights { get; set; } + } +} \ No newline at end of file diff --git a/ln.http/HttpRouter.cs b/ln.http/HttpRouter.cs new file mode 100644 index 0000000..a58ca7b --- /dev/null +++ b/ln.http/HttpRouter.cs @@ -0,0 +1,271 @@ +using System; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Linq; + +namespace ln.http +{ + public delegate bool HttpRouterDelegate(HttpContext context); + public delegate bool HttpAuthenticationDelegate(HttpContext httpContext, out HttpPrincipal principal); + public delegate bool HttpAuthorizationDelegate(HttpContext httpContext); + public delegate void HttpFilterDelegate(HttpContext httpContext); + + + public enum HttpRoutePriority : int { HIGHEST = 0, HIGH = 1, NORMAL = 2, LOW = 3, LOWEST = 4 } + + public class HttpRouter + { + public event HttpFilterDelegate HttpFilters; + public event HttpAuthenticationDelegate AuthenticationDelegates; + + private List[] _mappings = new List[5]; + + public HttpRouter() + { + for (int n = 0; n < 5; n++) + _mappings[n] = new List(); + } + + public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRouterDelegate routerDelegate) => + Map(httpMethod, uri, HttpRoutePriority.NORMAL, routerDelegate); + + public HttpMapping Map(HttpMethod httpMethod, string uri, HttpRoutePriority priority, HttpRouterDelegate routerDelegate) + { + HttpMapping httpMapping = new HttpMapping(httpMethod, RegexFromRoute(uri), routerDelegate); + _mappings[(int)priority].Add(httpMapping); + return httpMapping; + } + + public bool Route(HttpContext httpContext) + { + string residual = ""; + + if (AuthenticationDelegates is not null) + { + foreach (HttpAuthenticationDelegate authenticationDelegate in AuthenticationDelegates.GetInvocationList()) + { + if (httpContext.Authenticate(authenticationDelegate)) + break; + } + } + + HttpFilters?.Invoke(httpContext); + + for (int n = 0; n < _mappings.Length; n++) + { + foreach (HttpMapping httpMapping in _mappings[n]) + { + if ((httpMapping.Method == httpContext.Request.Method) || (httpMapping.Method == HttpMethod.ANY)) + { + Match match = httpMapping.UriRegex.Match(httpContext.RoutableUri); + if (match.Success) + { + foreach (Group group in match.Groups) + { + httpContext.Request?.SetParameter(group.Name, group.Value); + if (group.Name.Equals("_")) + if (group.Value.StartsWith("/", StringComparison.InvariantCulture)) + residual = group.Value; + else + residual = "/" + group.Value; + } + + string saveRoutableUri = httpContext.RoutableUri; + httpContext.RoutableUri = residual; + if (httpMapping.Route(httpContext)) + return true; + + httpContext.RoutableUri = saveRoutableUri; + } + } + } + + if (httpContext.Response is not null) + break; + } + return false; + } + + + private string RegexFromRoute(string route) + { + string[] parts = route.Split(new char[] { '/' }); + string[] reparts = parts.Select((part) => + { + if (part.StartsWith(":", StringComparison.InvariantCulture)) + if (part.EndsWith("*", StringComparison.InvariantCulture)) + return string.Format("(?<{0}>[^/]+)(?<_>/.*)?", part.Substring(1, part.Length - 2)); + else + return string.Format("(?<{0}>[^/]+)", part.Substring(1)); + else if (part.Equals("*")) + return string.Format("(?<_>.*)"); + else + return string.Format("{0}", part); + }).ToArray(); + + return string.Format("^{0}/?$", string.Join("/", reparts)); + } + + public class HttpMapping + { + public HttpMethod Method { get; set; } + public Regex UriRegex { get; set; } + public HttpRouterDelegate RouterDelegate { get; set; } + + public bool AuthenticationRequired { get; set; } + public HttpAuthorizationDelegate AuthorizationDelegate { get; set; } + + public event HttpFilterDelegate HttpFilters; + + public HttpMapping(HttpMethod httpMethod, string reUri, HttpRouterDelegate routerDelegate) + { + Method = httpMethod; + UriRegex = new Regex(reUri); + RouterDelegate = routerDelegate; + AuthenticationRequired = HTTPServer.DefaultRouteAuthentication; + } + + public HttpMapping UseFilter(HttpFilterDelegate filterDelegate) + { + HttpFilters += filterDelegate; + return this; + } + + public HttpMapping Authenticate() + { + AuthenticationRequired = true; + return this; + } + public HttpMapping Anonymous() + { + AuthenticationRequired = false; + return this; + } + + public HttpMapping Authorize(HttpAuthorizationDelegate authorizationDelegate) + { + AuthorizationDelegate = authorizationDelegate; + return this; + } + + + + + public bool Route(HttpContext httpContext) + { + if (AuthenticationRequired && httpContext.AuthenticatedPrincipal is null) + return false; + + if ((AuthorizationDelegate is not null) && (!AuthorizationDelegate(httpContext))) + return false; + + HttpFilters?.Invoke(httpContext); + + return RouterDelegate(httpContext); + } + } + + + /* + List routes = new List(); + public SimpleRoute[] Routes => routes.ToArray(); + */ + + + + + /* + public void AddSimpleRoute(string simpleRoute, Func target) => AddSimpleRoute(simpleRoute, new RouterTarget(target)); + public void AddSimpleRoute(string simpleRoute, Func target) => AddSimpleRoute(simpleRoute, new RouterTarget(target)); + public void AddSimpleRoute(string simpleRoute, Func target) => AddSimpleRoute(simpleRoute, new RouterTarget(target)); + public void AddSimpleRoute(string simpleRoute, IHttpRouter target) => AddSimpleRoute(simpleRoute, target, simpleRoute.Split('/').Length); + public void AddSimpleRoute(string simpleRoute, IHttpRouter target, int priority) + { + string[] parts = simpleRoute.Split(new char[] { '/' }); + string[] reparts = parts.Select((part) => + { + if (part.StartsWith(":", StringComparison.InvariantCulture)) + if (part.EndsWith("*", StringComparison.InvariantCulture)) + return string.Format("(?<{0}>[^/]+)(?<_>/.*)?", part.Substring(1, part.Length - 2)); + else + return string.Format("(?<{0}>[^/]+)", part.Substring(1)); + else if (part.Equals("*")) + return string.Format("(?<_>.*)"); + else + return string.Format("{0}", part); + }).ToArray(); + + string reroute = string.Format("{0}\\/?$", string.Join("/", reparts)); + + AddRoute(reroute, target, priority); + } + + public void AddRoute(String route, IHttpRouter target) => AddRoute(route, target, 0); + public void AddRoute(String route, IHttpRouter target,int priority) + { + lock (this) + { + routes.Add(new SimpleRoute(route, target, priority)); + routes.Sort((SimpleRoute a, SimpleRoute b) => b.Priority - a.Priority); + } + } + public void Remove(SimpleRoute simpleRoute) => routes.Remove(simpleRoute); + + public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) + { + HttpResponse httpResponse; + + if (OnRoute != null) + { + foreach (RouterFilterDelegate filterDelegate in OnRoute.GetInvocationList()) + { + if (filterDelegate(this, ref routingContext, httpRequest, out httpResponse)) + return httpResponse; + } + } + + foreach (SimpleRoute simpleRoute in routes.ToArray()) + { + Match match = simpleRoute.Route.Match(routingContext.Path); + if (match.Success) + { + string residual = ""; + + foreach (Group group in match.Groups) + { + httpRequest?.SetParameter(group.Name, group.Value); + if (group.Name.Equals("_")) + if (group.Value.StartsWith("/", StringComparison.InvariantCulture)) + residual = group.Value; + else + residual = "/" + group.Value; + } + + httpResponse = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest); + if (httpResponse != null) + return httpResponse; + } + } + return null; + } + + + public class SimpleRoute + { + public int Priority { get; } + + public Regex Route { get; } + public IHttpRouter Target { get; } + + public SimpleRoute(string regex, IHttpRouter target) : this(regex, target, 0) { } + public SimpleRoute(string regex, IHttpRouter target,int priority) + { + Route = new Regex(regex); + Target = target; + Priority = priority; + } + } + */ + + } +} diff --git a/ln.http/IHttpRouter.cs b/ln.http/IHttpRouter.cs index 3934d8a..e61107a 100644 --- a/ln.http/IHttpRouter.cs +++ b/ln.http/IHttpRouter.cs @@ -2,8 +2,5 @@ using ln.http.router; namespace ln.http { - public interface IHttpRouter - { - HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest); - } + } diff --git a/ln.http/MapAttribute.cs b/ln.http/MapAttribute.cs new file mode 100644 index 0000000..d4a74a0 --- /dev/null +++ b/ln.http/MapAttribute.cs @@ -0,0 +1,18 @@ +using System; + +namespace ln.http +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + public class MapAttribute : Attribute + { + public string Path { get; set; } + public HttpMethod Method { get; set; } + + public MapAttribute(){} + public MapAttribute(HttpMethod method, string path) + { + Method = method; + Path = path; + } + } +} \ No newline at end of file diff --git a/ln.http/QueryStringParameters.cs b/ln.http/QueryStringParameters.cs index 211ed3b..5e6334f 100644 --- a/ln.http/QueryStringParameters.cs +++ b/ln.http/QueryStringParameters.cs @@ -29,7 +29,8 @@ namespace ln.http public string this[string key] { get => parameters[key]; - set => throw new NotImplementedException(); } + set => throw new NotSupportedException(); + } public ICollection Keys => parameters.Keys; public ICollection Values => parameters.Values; diff --git a/ln.http/RoleAuthorization.cs b/ln.http/RoleAuthorization.cs new file mode 100644 index 0000000..b978db7 --- /dev/null +++ b/ln.http/RoleAuthorization.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using ln.http.router; + +namespace ln.http +{ + public static class RoleAuthorization + { + public static HttpAuthorizationDelegate Require(string roleName, HttpAccessRights accessRights) + { + return context => context.AuthenticatedPrincipal?.HasPermission(roleName, accessRights) ?? false; + } + + public static HttpAuthorizationDelegate RequireAll(params HttpAuthorizationDelegate[] authorizationDelegates) + { + return context => + { + foreach (HttpAuthorizationDelegate authorizationDelegate in authorizationDelegates) + { + if (!authorizationDelegate(context)) + return false; + } + return true; + }; + } + public static HttpAuthorizationDelegate RequireOneOf(params HttpAuthorizationDelegate[] authorizationDelegates) + { + return context => + { + foreach (HttpAuthorizationDelegate authorizationDelegate in authorizationDelegates) + { + if (authorizationDelegate(context)) + return true; + } + return false; + }; + } + } +} \ No newline at end of file diff --git a/ln.http/exceptions/PayLoadTooLargeException.cs b/ln.http/exceptions/PayLoadTooLargeException.cs new file mode 100644 index 0000000..15850aa --- /dev/null +++ b/ln.http/exceptions/PayLoadTooLargeException.cs @@ -0,0 +1,12 @@ +using System; +namespace ln.http.exceptions +{ + public class PayloadTooLargeException: HttpException + { + public PayloadTooLargeException(int payloadsize,int limit) + :base(513,"Payload too large") + { + HttpResponse = new HttpResponse(HttpStatusCode.PayloadTooLarge).Content(String.Format("Payload too large ({0} > {1})", payloadsize, limit)); + } + } +} diff --git a/ln.http/io/UnbufferedStreamreader.cs b/ln.http/io/UnbufferedStreamreader.cs deleted file mode 100644 index b2e35de..0000000 --- a/ln.http/io/UnbufferedStreamreader.cs +++ /dev/null @@ -1,70 +0,0 @@ -// /** -// * File: UnbufferedStreamreader.cs -// * Author: haraldwolff -// * -// * This file and it's content is copyrighted by the Author and / or copyright holder. -// * Any use wihtout proper permission is illegal and may lead to legal actions. -// * -// * -// **/ -using System; -using System.IO; -using System.Text; -namespace ln.http.io -{ - public class UnbufferedStreamReader : TextReader - { - public Stream Stream { get; } - - public UnbufferedStreamReader(Stream stream) - { - Stream = stream; - } - - public override int Read() => Stream.ReadByte(); - - public override string ReadLine() - { - StringBuilder stringBuilder = new StringBuilder(); - - char ch; - int _ch = 0; - - while ((_ch = Stream.ReadByte()) != -1) - { - ch = (char)_ch; - if (ch == '\r') - { - ch = (char)Stream.ReadByte(); - if (ch == '\n') - return stringBuilder.ToString(); - - stringBuilder.Append('\r'); - } - stringBuilder.Append(ch); - } - - if ((_ch == -1) && (stringBuilder.Length == 0)) - return null; - - return stringBuilder.ToString(); - } - - public string ReadToken() - { - StringBuilder stringBuilder = new StringBuilder(); - char ch = (char)Stream.ReadByte(); - - while (char.IsWhiteSpace(ch)) - ch = (char)Stream.ReadByte(); - - while (!char.IsWhiteSpace(ch)) - { - stringBuilder.Append(ch); - ch = (char)Stream.ReadByte(); - } - - return stringBuilder.ToString(); - } - } -} diff --git a/ln.http/listener/HttpListener.cs b/ln.http/listener/HttpListener.cs index 21dc57b..4c505b5 100644 --- a/ln.http/listener/HttpListener.cs +++ b/ln.http/listener/HttpListener.cs @@ -27,7 +27,10 @@ namespace ln.http.listener public override Endpoint LocalEndpoint => new Endpoint(tcpListener.LocalEndpoint); - public override Connection Accept() => new HttpConnection(this,tcpListener.AcceptTcpClient()); + public override Connection Accept() + { + return new HttpConnection(this,tcpListener.AcceptTcpClient()); + } public override bool IsOpen => (tcpListener != null); diff --git a/ln.http/listener/Listener.cs b/ln.http/listener/Listener.cs index 71c8218..e1691a1 100644 --- a/ln.http/listener/Listener.cs +++ b/ln.http/listener/Listener.cs @@ -8,7 +8,9 @@ // * // **/ using System; +using System.Net.Sockets; using ln.http.connections; +using ln.logging; using ln.type; namespace ln.http.listener { @@ -30,7 +32,17 @@ namespace ln.http.listener public virtual void AcceptMany(Action handler) { while (IsOpen) - handler(Accept()); + { + try + { + handler(Accept()); + } + catch (SocketException soe) + { + if (IsOpen) + Logging.Log(soe); + } + } } public abstract void Open(); diff --git a/ln.http/ln.http.csproj b/ln.http/ln.http.csproj index 3f832fc..9d46335 100644 --- a/ln.http/ln.http.csproj +++ b/ln.http/ln.http.csproj @@ -10,11 +10,12 @@ (c) 2020 Harald Wolff-Thobaben http server 9 + 0.5.0 - + diff --git a/ln.http/rest/CRUDObjectContainer.RegisteredType.cs b/ln.http/rest/CRUDObjectContainer.RegisteredType.cs deleted file mode 100644 index 3137ecf..0000000 --- a/ln.http/rest/CRUDObjectContainer.RegisteredType.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ln.http.rest -{ - - public abstract partial class CRUDObjectContainer - { - class RegisteredType - { - public CRUDObjectContainer ObjectContainer { get; } - public Type ObjectType { get; } - - Func _idGetter; - Dictionary objectCache = new Dictionary(); - - public IEnumerable FieldDescriptors => fieldDescriptors; - List fieldDescriptors = new List(); - - public RegisteredType(CRUDObjectContainer objectContainer,Type objectType,Func idGetter) - { - ObjectContainer = objectContainer; - ObjectType = objectType; - _idGetter = idGetter; - } - - public bool TryGetObjectID(object objectInstance,out object objectIdentifier) - { - try - { - objectIdentifier = _idGetter(objectInstance); - } catch (Exception e) - { - objectIdentifier = null; - return false; - } - return true; - } - - public void AddCachedObject(object objectInstance) - { - Type objectType = objectInstance.GetType(); - if (!ObjectType.Equals(objectType)) - throw new ArgumentException(); - - if (!TryGetObjectID(objectInstance, out object objectIdentifier)) - throw new ArgumentOutOfRangeException(); - - objectCache.Add(objectIdentifier, objectInstance); - } - - public void RemoveCachedObject(object objectInstance) - { - Type objectType = objectInstance.GetType(); - if (!ObjectType.Equals(objectType)) - throw new ArgumentException(); - - if (!TryGetObjectID(objectInstance, out object objectIdentifier)) - throw new ArgumentOutOfRangeException(); - - objectCache.Remove(objectIdentifier); - } - - } - } -} - - - - - diff --git a/ln.http/rest/CRUDObjectContainer.cs b/ln.http/rest/CRUDObjectContainer.cs deleted file mode 100644 index 6b825d2..0000000 --- a/ln.http/rest/CRUDObjectContainer.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace ln.http.rest -{ - public delegate void CRUDEvent(CRUDObjectContainer sender, CRUDEventArgs args); - public enum CRUDEventType { CREATE, READ, UPDATE, DELETE } - - public class CRUDEventArgs : EventArgs - { - public CRUDEventType EventType { get; } - public Type ObjectType { get; } - public object ObjectIdentity { get; } - public object ObjectInstance { get; } - public FieldValueList FieldValues { get; } - - public CRUDEventArgs(CRUDEventType eventType, Type objectType, object objectIdentity, object objectInstance, FieldValueList fieldValues) - { - EventType = eventType; - ObjectType = objectType; - ObjectIdentity = objectIdentity; - ObjectInstance = objectInstance; - FieldValues = fieldValues; - } - } - - public abstract partial class CRUDObjectContainer - { - public event CRUDEvent OnCRUDEvent; - - - Dictionary registeredTypes = new Dictionary(); - - public CRUDObjectContainer() - { } - - public abstract bool TryGetObjectIdentifier(object objectInstance, out object objectIdentifier); - - public abstract bool TryCreateObject(Type objectType, FieldValueList fieldValues, out object objectInstance); - public abstract bool TryLookupObject(Type objectType, object objectIdentifier, out object objectInstance); - public abstract bool TryUpdateObject(Type objectType, object objectIdentifier, FieldValueList fieldValues); - public abstract bool TryDeleteObject(Type objectType, object objectIdentifier); - - public virtual bool TryUpdateObject(object objectInstance, FieldValueList fieldValues) - { - if (TryGetObjectIdentifier(objectInstance, out object objectIdentifier)) - return TryUpdateObject(objectInstance.GetType(), objectIdentifier, fieldValues); - return false; - } - public virtual bool TryDeleteObject(object objectInstance) - { - if (TryGetObjectIdentifier(objectInstance, out object objectIdentifier)) - return TryDeleteObject(objectInstance.GetType(), objectIdentifier); - return false; - } - - protected void FireCRUDEvent(CRUDEventArgs args) => OnCRUDEvent?.Invoke(this, args); - protected void FireCRUDEVent(CRUDEventType EventType, Type ObjectType, object ObjectIdentity, object ObjectInstance, FieldValueList FieldValues) - => FireCRUDEvent(new CRUDEventArgs(EventType, ObjectType, ObjectIdentity, ObjectInstance, FieldValues)); - - public virtual IEnumerable RegisteredTypes => registeredTypes.Keys; - public virtual bool ContainsRegisteredType(Type objectType) => registeredTypes.ContainsKey(objectType); - - public virtual bool TryRegisterType(Type objectType) => TryRegisterType(objectType, null); - public virtual bool TryRegisterType(Type objectType, Func idGetter) - { - if (ContainsRegisteredType(objectType)) - throw new ArgumentException("objectType already registered", nameof(objectType)); - - RegisteredType registeredType = new RegisteredType(this, objectType, idGetter); - - registeredTypes.Add(objectType, registeredType); - - return true; - } - public virtual bool TryGetFieldDescriptors(Type objectType, out FieldDescriptor[] fieldDescriptors) - { - if (registeredTypes.TryGetValue(objectType, out RegisteredType registeredType)) - { - fieldDescriptors = registeredType.FieldDescriptors.ToArray(); - return true; - } - fieldDescriptors = null; - return false; - } - - public virtual IEnumerable EnumerateObjectInstances(Type objectType) => EnumerateObjectInstances(objectType, null); - public abstract IEnumerable EnumerateObjectInstances(Type objectType, FieldValueList conditionalFieldValues); - - public abstract bool AddForeignObjectInstance(object objectInstance); - } -} - - - - - diff --git a/ln.http/rest/FieldDescriptor.cs b/ln.http/rest/FieldDescriptor.cs deleted file mode 100644 index 6ddd6d8..0000000 --- a/ln.http/rest/FieldDescriptor.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text; - -namespace ln.http.rest -{ - public class FieldDescriptor - { - Action setter; - Func getter; - - public Type OwningType { get; } - - public string FieldName { get; } - public Type FieldType { get; } - - public bool CanWrite { get; } - - public FieldDescriptor(FieldInfo fieldInfo) - { - OwningType = fieldInfo.DeclaringType; - FieldName = fieldInfo.Name; - FieldType = fieldInfo.FieldType; - - CanWrite = !fieldInfo.IsInitOnly; - - getter = fieldInfo.GetValue; - setter = fieldInfo.SetValue; - } - - public FieldDescriptor(PropertyInfo propertyInfo) - { - OwningType = propertyInfo.DeclaringType; - FieldName = propertyInfo.Name; - FieldType = propertyInfo.PropertyType; - - CanWrite = propertyInfo.CanWrite; - - getter = propertyInfo.GetValue; - setter = propertyInfo.SetValue; - } - - public void SetFieldValue(object objectInstance, object fieldValue) => setter(objectInstance, fieldValue); - public object GetFieldValue(object objectInstance) => getter(objectInstance); - } -} diff --git a/ln.http/rest/FieldValueList.cs b/ln.http/rest/FieldValueList.cs deleted file mode 100644 index 71cf1ab..0000000 --- a/ln.http/rest/FieldValueList.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ln.http.rest -{ - public class FieldValueList - { - public Type ObjectType { get; } - - Dictionary fieldValues = new Dictionary(); - - public FieldValueList(Type objectType) - { - ObjectType = objectType; - } - - public IEnumerable FieldNames => fieldValues.Keys; - public object GetFieldValue(string fieldName) => fieldValues[fieldName]; - public void SetFieldValue(string fieldName, object fieldValue) => fieldValues[fieldName] = fieldValue; - public void RemoveFieldValue(string fieldName) => fieldValues.Remove(fieldName); - - } -} diff --git a/ln.http/rest/RestAPIAdapter.cs b/ln.http/rest/RestAPIAdapter.cs deleted file mode 100644 index 49f05af..0000000 --- a/ln.http/rest/RestAPIAdapter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using ln.http.router; -using System; -using System.Collections.Generic; -using System.Text; - -namespace ln.http.rest -{ - public class RestAPIAdapter : IHttpRouter - { - public CRUDObjectContainer ObjectContainer { get; } - - public RestAPIAdapter(CRUDObjectContainer objectContainer) - { - ObjectContainer = objectContainer; - } - - public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) - { - throw new NotImplementedException(); - } - } -} diff --git a/ln.http/router/FileRouter.cs b/ln.http/router/FileRequestRouter.cs similarity index 59% rename from ln.http/router/FileRouter.cs rename to ln.http/router/FileRequestRouter.cs index 77915ff..cfedbb5 100644 --- a/ln.http/router/FileRouter.cs +++ b/ln.http/router/FileRequestRouter.cs @@ -12,7 +12,7 @@ using ln.http.mime; namespace ln.http.router { - public class FileRouter : IHttpRouter + public class FileRouter { public string FileName { get; set; } @@ -24,13 +24,11 @@ namespace ln.http.router FileName = filename; } - public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) + public bool Route(HttpContext httpContext) { - HttpResponse httpResponse = new HttpResponse(httpRequest, new FileStream(FileName, FileMode.Open)); - httpResponse.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(FileName))); - - - return httpResponse; + httpContext.Response = new HttpResponse(httpContext.Request, new FileStream(FileName, FileMode.Open)); + httpContext.Response.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(FileName))); + return true; } } diff --git a/ln.http/router/HttpAccessRights.cs b/ln.http/router/HttpAccessRights.cs new file mode 100644 index 0000000..b191efc --- /dev/null +++ b/ln.http/router/HttpAccessRights.cs @@ -0,0 +1,13 @@ +using System; + +namespace ln.http.router +{ + [Flags] + public enum HttpAccessRights + { + READ = (1<<0), + WRITE = (1<<1), + EXECUTE = (1<<2), + SPECIAL = (1<<3) + } +} \ No newline at end of file diff --git a/ln.http/router/LoggingRouter.cs b/ln.http/router/LoggingRequestRouter.cs similarity index 56% rename from ln.http/router/LoggingRouter.cs rename to ln.http/router/LoggingRequestRouter.cs index fd0861a..7b9726e 100644 --- a/ln.http/router/LoggingRouter.cs +++ b/ln.http/router/LoggingRequestRouter.cs @@ -12,28 +12,28 @@ using ln.logging; using System.Diagnostics; namespace ln.http.router { - public class LoggingRouter : IHttpRouter + public class LoggingRouter { - IHttpRouter Next { get; set; } + HttpRouterDelegate Next { get; set; } Logger Logger { get; } - public LoggingRouter(IHttpRouter next) + public LoggingRouter(HttpRouterDelegate next) :this(next, Logger.Default) {} - public LoggingRouter(IHttpRouter next,Logger logger) + public LoggingRouter(HttpRouterDelegate next,Logger logger) { Next = next; Logger = logger; } - public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) + public bool Route(HttpContext httpContext) { DateTime start = DateTime.Now; - HttpResponse response = null; - + bool success = false; + try { - response = Next.Route(routingContext, httpRequest); + success = Next(httpContext); } catch (Exception e) { @@ -44,16 +44,17 @@ namespace ln.http.router DateTime end = DateTime.Now; TimeSpan duration = end - start; - Logger.Log(LogLevel.INFO, "{0} {1} {2} {3} {4}", + Logger.Log(LogLevel.INFO, "{0} {1} {2} {3} {4} {5} {6}", start, + end, duration, - response?.StatusCode.ToString() ?? "-", - httpRequest?.Method, - httpRequest?.RequestUri + httpContext.Response?.StatusCode.ToString() ?? "-", + httpContext.AuthenticatedPrincipal?.ToString() ?? "-", + httpContext.Request.Method, + httpContext.Request.RequestUri ); } - - return response; + return success; } } } diff --git a/ln.http/router/RouterTarget.cs b/ln.http/router/RequestRouterTarget.cs similarity index 99% rename from ln.http/router/RouterTarget.cs rename to ln.http/router/RequestRouterTarget.cs index 6be5089..7fcb076 100644 --- a/ln.http/router/RouterTarget.cs +++ b/ln.http/router/RequestRouterTarget.cs @@ -2,6 +2,7 @@ using ln.http.exceptions; namespace ln.http.router { + /* public class RouterTarget :IHttpRouter { public Func Target { get; } @@ -58,4 +59,5 @@ namespace ln.http.router } +*/ } diff --git a/ln.http/router/SimpleRouter.cs b/ln.http/router/SimpleRouter.cs deleted file mode 100644 index 33d7ffa..0000000 --- a/ln.http/router/SimpleRouter.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using System.Collections.Generic; -using System.Text; -using System.Linq; -namespace ln.http.router -{ - - public delegate bool RouterFilterDelegate(SimpleRouter sender, ref HttpRoutingContext routingContext, HttpRequest httpRequest, out HttpResponse httpResponse); - - public class SimpleRouter : IHttpRouter - { - public event RouterFilterDelegate OnRoute; - - List routes = new List(); - public SimpleRoute[] Routes => routes.ToArray(); - - public SimpleRouter() - { - } - - public void AddSimpleRoute(string simpleRoute, Func target) => AddSimpleRoute(simpleRoute, new RouterTarget(target)); - public void AddSimpleRoute(string simpleRoute, Func target) => AddSimpleRoute(simpleRoute, new RouterTarget(target)); - public void AddSimpleRoute(string simpleRoute, Func target) => AddSimpleRoute(simpleRoute, new RouterTarget(target)); - public void AddSimpleRoute(string simpleRoute, IHttpRouter target) => AddSimpleRoute(simpleRoute, target, simpleRoute.Split('/').Length); - public void AddSimpleRoute(string simpleRoute, IHttpRouter target, int priority) - { - string[] parts = simpleRoute.Split(new char[] { '/' }); - string[] reparts = parts.Select((part) => - { - if (part.StartsWith(":", StringComparison.InvariantCulture)) - if (part.EndsWith("*", StringComparison.InvariantCulture)) - return string.Format("(?<{0}>[^/]+)(?<_>/.*)?", part.Substring(1, part.Length - 2)); - else - return string.Format("(?<{0}>[^/]+)", part.Substring(1)); - else if (part.Equals("*")) - return string.Format("(?<_>.*)"); - else - return string.Format("{0}", part); - }).ToArray(); - - string reroute = string.Format("{0}\\/?$", string.Join("/", reparts)); - - AddRoute(reroute, target, priority); - } - - public void AddRoute(String route, IHttpRouter target) => AddRoute(route, target, 0); - public void AddRoute(String route, IHttpRouter target,int priority) - { - lock (this) - { - routes.Add(new SimpleRoute(route, target, priority)); - routes.Sort((SimpleRoute a, SimpleRoute b) => b.Priority - a.Priority); - } - } - public void Remove(SimpleRoute simpleRoute) => routes.Remove(simpleRoute); - - public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) - { - HttpResponse httpResponse; - - if (OnRoute != null) - { - foreach (RouterFilterDelegate filterDelegate in OnRoute.GetInvocationList()) - { - if (filterDelegate(this, ref routingContext, httpRequest, out httpResponse)) - return httpResponse; - } - } - - foreach (SimpleRoute simpleRoute in routes.ToArray()) - { - Match match = simpleRoute.Route.Match(routingContext.Path); - if (match.Success) - { - string residual = ""; - - foreach (Group group in match.Groups) - { - httpRequest?.SetParameter(group.Name, group.Value); - if (group.Name.Equals("_")) - if (group.Value.StartsWith("/", StringComparison.InvariantCulture)) - residual = group.Value; - else - residual = "/" + group.Value; - } - - httpResponse = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest); - if (httpResponse != null) - return httpResponse; - } - } - return null; - } - - - public class SimpleRoute - { - public int Priority { get; } - - public Regex Route { get; } - public IHttpRouter Target { get; } - - public SimpleRoute(string regex, IHttpRouter target) : this(regex, target, 0) { } - public SimpleRoute(string regex, IHttpRouter target,int priority) - { - Route = new Regex(regex); - Target = target; - Priority = priority; - } - } - } -} diff --git a/ln.http/router/StaticRouter.cs b/ln.http/router/StaticRouter.cs index 3bb1710..6010938 100644 --- a/ln.http/router/StaticRouter.cs +++ b/ln.http/router/StaticRouter.cs @@ -14,7 +14,7 @@ using ln.http.mime; namespace ln.http.router { - public class StaticRouter : IHttpRouter + public class StaticRouter { public String RootPath { get; } @@ -35,9 +35,9 @@ namespace ln.http.router public void AddIndex(string indexName) => indexNames.Add(indexName); public void RemoveIndex(string indexName) => indexNames.Remove(indexName); - public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) + public bool Route(HttpContext httpContext) { - string finalPath = routingContext.Path.Length > 0 ? Path.Combine(RootPath, routingContext.Path.Substring(1)) : "."; + string finalPath = httpContext.RoutableUri.Length > 0 ? Path.Combine(RootPath, httpContext.RoutableUri.Substring(1)) : "."; if (Directory.Exists(finalPath)) { @@ -56,12 +56,12 @@ namespace ln.http.router { lock (this) { - HttpResponse httpResponse = new HttpResponse(httpRequest, new FileStream(finalPath, FileMode.Open, FileAccess.Read)); - httpResponse.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(finalPath))); - return httpResponse; + httpContext.Response = new HttpResponse(httpContext.Request, new FileStream(finalPath, FileMode.Open, FileAccess.Read)); + httpContext.Response.SetHeader("content-type", MimeTypeMap.GetMimeType(Path.GetExtension(finalPath))); + return true; } } - return null; + return false; } } diff --git a/ln.http/router/VirtualHostRouter.cs b/ln.http/router/VirtualHostRouter.cs index 52b935f..6d00957 100644 --- a/ln.http/router/VirtualHostRouter.cs +++ b/ln.http/router/VirtualHostRouter.cs @@ -1,27 +1,28 @@ using ln.http.exceptions; using System.Collections.Generic; +using System.Net.Http; namespace ln.http.router { - public class VirtualHostRouter : IHttpRouter + public class VirtualHostRouter { - public IHttpRouter DefaultRoute { get; set; } + public HttpRouterDelegate DefaultRoute { get; set; } - Dictionary virtualHosts = new Dictionary(); + Dictionary virtualHosts = new Dictionary(); public VirtualHostRouter() { } - public VirtualHostRouter(IHttpRouter defaultRoute) + public VirtualHostRouter(HttpRouterDelegate defaultRoute) { DefaultRoute = defaultRoute; } - public VirtualHostRouter(IEnumerable> routes) + public VirtualHostRouter(IEnumerable> routes) { - foreach (KeyValuePair route in routes) + foreach (KeyValuePair route in routes) virtualHosts.Add(route.Key, route.Value); } - public VirtualHostRouter(IHttpRouter defaultRoute,IEnumerable> routes) + public VirtualHostRouter(HttpRouterDelegate defaultRoute,IEnumerable> routes) :this(routes) { DefaultRoute = defaultRoute; @@ -29,20 +30,20 @@ namespace ln.http.router public VirtualHostRouter(VirtualHostRouter source) : this(source.virtualHosts) { } - public void Add(string hostname,IHttpRouter router) => virtualHosts.Add(hostname, router); + public void Add(string hostname,HttpRouterDelegate router) => virtualHosts.Add(hostname, router); public void Remove(string hostname) => virtualHosts.Remove(hostname); public bool Contains(string hostname) => virtualHosts.ContainsKey(hostname); - public bool TryGetValue(string hostname, out IHttpRouter router) => virtualHosts.TryGetValue(hostname, out router); + public bool TryGetValue(string hostname, out HttpRouterDelegate router) => virtualHosts.TryGetValue(hostname, out router); - public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) + public bool Route(HttpContext httpContext) { - if (virtualHosts.TryGetValue(httpRequest.Host, out IHttpRouter virtualHost)) - return virtualHost.Route(routingContext, httpRequest); + if (virtualHosts.TryGetValue(httpContext.Request.Host, out HttpRouterDelegate virtualHost)) + return virtualHost(httpContext); if (DefaultRoute != null) - return DefaultRoute.Route(routingContext, httpRequest); + return DefaultRoute(httpContext); - throw new HttpException(410, string.Format("Gone. Hostname {0} not found on this server.", httpRequest.Host)); + throw new HttpException(410, string.Format("Gone. Hostname {0} not found on this server.", httpContext.Request.Host)); } } } diff --git a/ln.http/router/WebsocketRouter.cs b/ln.http/router/WebsocketRequestRouter.cs similarity index 76% rename from ln.http/router/WebsocketRouter.cs rename to ln.http/router/WebsocketRequestRouter.cs index 5b3015b..820b4dd 100644 --- a/ln.http/router/WebsocketRouter.cs +++ b/ln.http/router/WebsocketRequestRouter.cs @@ -4,7 +4,7 @@ using ln.http.exceptions; using ln.logging; namespace ln.http.router { - public class WebsocketRouter : IHttpRouter + public class WebsocketRouter { Func createWebsocket; @@ -15,9 +15,9 @@ namespace ln.http.router public WebSocket CreateWebSocket(HttpRequest request) => createWebsocket(request); - public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) + public bool Route(HttpContext httpContext) { - WebSocket websocket = CreateWebSocket(httpRequest); + WebSocket websocket = CreateWebSocket(httpContext.Request); try { websocket.Run(); diff --git a/ln.http/session/Session.cs b/ln.http/session/Session.cs deleted file mode 100644 index 67eb0c9..0000000 --- a/ln.http/session/Session.cs +++ /dev/null @@ -1,71 +0,0 @@ -/* -using System; -using System.Collections.Generic; -using System.Linq; - -namespace ln.http.session -{ - public class Session : IDisposable - { - public Guid SessionID { get; private set; } - public long SessionAge => DateTimeOffset.Now.ToUnixTimeSeconds() - LastTouch; - public long LastTouch { get; private set; } - - public HttpUser CurrentUser { get; set; } - - - private readonly Dictionary elements = new Dictionary(); - - public Session() - { - SessionID = Guid.NewGuid(); - CurrentUser = new HttpUser(); - } - - public object this[string name] - { - get => this.elements[name]; - set => this.elements[name] = value; - } - - public T Get(string name) where T:class - { - if (elements.ContainsKey(name)) - return elements[name] as T; - return null; - } - - public String[] ElementNames => this.elements.Keys.ToArray(); - - public void Touch() - { - LastTouch = DateTimeOffset.Now.ToUnixTimeSeconds(); - } - - public virtual void Authenticate(HttpRequest httpRequest) - { - - } - - public void Dispose() - { - foreach (object o in elements.Values) - { -// if (o is IDisposable disposable) -// { -// try -// { -// disposable.Dispose(); -// } -//#pragma warning disable RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist -// catch (Exception) -//#pragma warning restore RECS0022 // catch-Klausel, die System.Exception abfängt und keinen Text aufweist - // { - - // } - //} - } - } - } -} -*/ diff --git a/ln.http/session/SessionCache.cs b/ln.http/session/SessionCache.cs deleted file mode 100644 index 481f4de..0000000 --- a/ln.http/session/SessionCache.cs +++ /dev/null @@ -1,77 +0,0 @@ -/* -using System; -using System.Collections.Generic; -namespace ln.http.session -{ - public class SessionCache - { - Dictionary sessions = new Dictionary(); - - public SessionCache() - { - } - - public bool Contains(Guid sessionID) - { - return this.sessions.ContainsKey(sessionID); - } - - /* - * Create a new Session instance - * - #1# - public virtual Session CreateSession() - { - return new Session(); - } - - /* - * Find and return SessionID from given HttpRequest - * - #1# - public virtual Guid FindSessionID(HttpRequest httpRequest) - { - if (httpRequest.ContainsCookie("SID_LN")) - { - return Guid.Parse(httpRequest.GetCookie("SID_LN")); - } - return Guid.Empty; - } - - /* - * Apply SessionID of the given Session to the given Response - * - #1# - public virtual void ApplySessionID(HttpResponse httpResponse,Session session) - { - HttpCookie sessionCookie = new HttpCookie("SID_LN",session.SessionID.ToString()); - sessionCookie.Path = "/"; - httpResponse.AddCookie(sessionCookie); - } - - public Session GetSession(HttpRequest httpRequest) - { - Guid sessionID = FindSessionID(httpRequest); - if (!Guid.Empty.Equals(sessionID) && Contains(sessionID)) - { - lock (sessions) - { - Session session = this.sessions[sessionID]; - session.Touch(); - return session; - } - } - else - { - Session session = CreateSession(); - lock (this.sessions) - { - this.sessions.Add(session.SessionID, session); - } - return session; - } - } - - } -} -*/