269 lines
11 KiB
C#
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();
|
|
}
|
|
}
|
|
} |