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; 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; 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(HttpRequestContext httpContext, string routePath); 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 (parameterInfo.ParameterType.Equals(typeof(HttpRequestContext))) parameters[n] = requestContext; else if (parameterInfo.ParameterType.Equals(typeof(HttpRequest))) parameters[n] = requestContext.Request; else if (parameterInfo.ParameterType.Equals(typeof(HttpPrincipal))) parameters[n] = requestContext.AuthenticatedPrincipal; else if (TryFindArgumentByName( requestContext, _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) && (requestContext.Request.Headers.Contains("content-length"))) { if (_parameterInfos[n].ParameterType.Equals(typeof(Stream))) { parameters[n] = requestContext.Request.ContentStream; } else if (_parameterInfos[n].ParameterType.Equals(typeof(TextReader))) { parameters[n] = new StreamReader(requestContext.Request.ContentStream); } else if (_parameterInfos[n].ParameterType.Equals(typeof(string))) { using (var r = new StreamReader(requestContext.Request.ContentStream)) parameters[n] = r.ReadToEnd(); } else if (requestContext.Request.Headers.TryGetValue("Content-Type", out string contentType) && contentType.Equals("application/json")) { using (TextReader reader = new StreamReader(requestContext.Request.ContentStream)) { JSONValue jsonValue = JSONParser.Parse(reader); if (typeof(JSONValue).IsAssignableFrom(_parameterInfos[n].ParameterType)) parameters[n] = jsonValue; else parameters[n] = JSONMapper.DefaultMapper.FromJson(jsonValue, _parameterInfos[n].ParameterType); } } } else if (_parameterInfos[n].HasDefaultValue) { parameters[n] = _parameterInfos[n].DefaultValue; } else return false; } return true; } bool TryFindArgumentByName(HttpRequestContext httpContext, HttpArgumentSource argumentSource, string parameterName, out string parameterValue) { if (((argumentSource & HttpArgumentSource.PARAMETER) == HttpArgumentSource.PARAMETER) && httpContext.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(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 Route(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 Route(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(); } } }