using System; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using ln.json; using ln.json.mapping; using ln.type; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace ln.http { public abstract class HttpEndpointController : HttpRouter, IDisposable { private HttpRouter _httpRouter; public HttpEndpointController():this(null){} public HttpEndpointController(HttpRouter httpRouter) { _httpRouter = httpRouter; MapAttribute mapAttribute = GetType().GetCustomAttribute(); if (mapAttribute is not null) { HttpMethod = mapAttribute.Method != HttpMethod.NONE ? mapAttribute.Method : HttpMethod.ANY; Route = mapAttribute.Path is not null ? new Regex(mapAttribute.Path) : null; } Initialize(); _httpRouter?.Map(this); } 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; private RequiresAttribute[] _requiresAttributes; 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(); _requiresAttributes = methodInfo.GetCustomAttributes().ToArray(); } public bool Route(HttpRequestContext httpContext, string routePath) { if (_requiresAttributes.Length > 0){ if (httpContext.AuthenticatedPrincipal is HttpPrincipal) { foreach (var requiresAttribute in _requiresAttributes) { if (requiresAttribute.Roles.All((role) => httpContext.AuthenticatedPrincipal.Roles.Contains(role))) { return Route2(httpContext, routePath); } } } httpContext.Response = HttpResponse.Unauthorized(); return true; } return Route2(httpContext, routePath); } public abstract bool Route2(HttpRequestContext httpContext, string routePath); bool TryFindParameterValue(HttpRequestContext context, ParameterInfo parameterInfo, out object value) { if (parameterInfo.GetCustomAttribute() is JsonBodyAttribute jsonBodyAttribute) { if (jsonBodyAttribute.Property is null) { value = context.Request.Json(parameterInfo.ParameterType); return true; } else if (context.Request.Json().TryGetValue(jsonBodyAttribute.Property, out JToken property)) { value = property.ToObject(parameterInfo.ParameterType); return true; } } if ((parameterInfo.GetCustomAttribute() is RouteAttribute routeAttribute) && context.TryGetParameter(routeAttribute.Name ?? parameterInfo.Name, out string pvalue)) { value = pvalue; return true; } if ((parameterInfo.GetCustomAttribute() is QueryAttribute queryAttribute) && context.Request.Query.TryGetValue(queryAttribute.Name ?? parameterInfo.Name, out string svalue)) { value = svalue; return true; } if (parameterInfo.ParameterType.Equals(typeof(Stream))) { value = context.Request.ContentStream; return true; } if (parameterInfo.ParameterType.Equals(typeof(HttpRequestContext))) { value = context; return true; } if (parameterInfo.ParameterType.Equals(typeof(HttpRequest))) { value = context.Request; return true; } if (parameterInfo.ParameterType.Equals(typeof(HttpPrincipal))) { value = context.AuthenticatedPrincipal; return true; } value = null; return false; } bool TryApplyParameters(HttpRequestContext requestContext, out object[] parameters) { parameters = new object[_parameterInfos.Length]; for (int n = 0; n < _parameterInfos.Length; n++) { ParameterInfo parameterInfo = _parameterInfos[n]; if (!TryFindParameterValue(requestContext, parameterInfo, out parameters[n])) parameters[n] = parameterInfo.DefaultValue; if ((parameters[n] != null) && (!parameterInfo.ParameterType.IsAssignableFrom(parameters[n].GetType()))) { if (!Cast.To(parameters[n], _parameterInfos[n].ParameterType, out parameters[n])) { parameters[n] = TypeDescriptor .GetConverter(parameterInfo.ParameterType) .ConvertFrom(parameters[n]); } } if ((parameters[n] == null) && (parameterInfo.ParameterType.IsValueType)) return false; } return true; } private object InvokeMethod(HttpRequestContext httpContext) { object[] parameters = null; try { if (!TryApplyParameters(httpContext, out parameters)) return HttpResponse.InternalServerError().Content("could not apply parameters"); else { return MethodInfo.Invoke(EndpointController, parameters); } } finally { DisposeParameters(parameters); } } public class NonVoidEndpoint : MappedEndpoint { public NonVoidEndpoint(HttpEndpointController endpointController, MapAttribute mapAttribute, MethodInfo methodInfo) : base(endpointController, mapAttribute, methodInfo) { } public override bool Route2(HttpRequestContext httpContext, string routePath) { 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 Route2(HttpRequestContext httpContext, string routePath) { object returnedValue = InvokeMethod(httpContext); if (returnedValue is HttpResponse httpResponse) httpContext.Response = httpResponse; else httpContext.Response ??= HttpResponse.Default(httpContext.Request.Method); return true; } } private void DisposeParameters(object[] parameters) { if (parameters is null) return; foreach (var o in parameters) { if (o is IDisposable disposable) disposable.Dispose(); } } } public void Dispose() { _httpRouter?.Dispose(); base.Dispose(); } } }