ln.http/ln.http/HttpEndpointController.cs

238 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) && (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();
}
}
}