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