ln.http/ln.http/HttpEndpointController.cs

269 lines
11 KiB
C#

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<MapAttribute>();
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<MapAttribute>())
{
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<HttpArgumentSourceAttribute>()).ToArray();
_requiresAttributes = methodInfo.GetCustomAttributes<RequiresAttribute>().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<JsonBodyAttribute>() 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<RouteAttribute>() is RouteAttribute routeAttribute) &&
context.TryGetParameter(routeAttribute.Name ?? parameterInfo.Name, out string pvalue))
{
value = pvalue;
return true;
}
if ((parameterInfo.GetCustomAttribute<QueryAttribute>() 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();
}
}
}