234 lines
10 KiB
C#
234 lines
10 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;
|
|
|
|
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;
|
|
|
|
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();
|
|
}
|
|
|
|
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)
|
|
{
|
|
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 (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();
|
|
}
|
|
}
|
|
} |