tesses.http/Tesses.Http/ASPNetStyleEndpoint.cs

451 lines
13 KiB
C#

using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading;
using System.Text;
using Newtonsoft.Json;
using System.Reflection;
using System.Linq;
using HeyRed.Mime;
namespace Tesses.Http
{
public abstract class ASPEndpointRequestHandler : IRequestHandler
{
protected virtual void NotFound(ServerContext ctx)
{
}
protected virtual Stream GetPostFileStream(string name,string filename,string contentType)
{
return new MemoryStream();
}
List<___RouteMethod> ___RouteMethods=new List<___RouteMethod>();
private class ___RouteMethod
{
public string Method;
public bool ContainsServerCtx;
public bool ContainsMultipart;
public bool ReturnsVoid;
public string Name;
public MethodInfo info;
public ASPEndpointRequestHandler _instance;
public void Call(ServerContext ctx,MultipartParser p,Dictionary<string,List<string>> args)
{
if(!ContainsMultipart && p != null)
{
foreach(var item in p.Parse(_instance.GetPostFileStream,true))
{
if(!item.HasFileName)
{
args.Add(item.Name,item.GetStringData());
}
}
}
object[] ar_= _args.Select<___Arg,object>((e)=>{
return e.GetArgument(ctx,p,args);
}).ToArray();
object o=info.Invoke(_instance,ar_);
if(!ReturnsVoid)
{
IResponse r = o as IResponse;
Stream s = o as Stream;
string r2 = o as string;
StringBuilder b=o as StringBuilder;
if(r!=null)
{
r.Handle(ctx);
}else if(s != null)
{
FileResponse resp=new FileResponse(s,"application/octet-stream",true);
resp.Handle(ctx);
}else if(b != null)
{
TextResponse resp=new TextResponse(b.ToString(),"text/plain");
resp.Handle(ctx);
}
else if(!string.IsNullOrEmpty(r2))
{
TextResponse resp=new TextResponse(r2,"text/plain");
resp.Handle(ctx);
}else{
TextResponse resp=new TextResponse(JsonConvert.SerializeObject(o),"application/json");
resp.Handle(ctx);
}
}
}
public List<___Arg> _args=new List<___Arg>();
}
private class ___Arg
{
public bool _isServerCtx;
public bool _isMultipart;
public string name;
public Type type;
private object Default()
{
if(type.GetTypeInfo().IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
private object ___to_type_ar(Type t,List<string> items)
{
object o;
if(t.IsArray)
{
object[] items2=new object[items.Count];
int i=0;
foreach(var item in items)
{
items2[i]=___to_type(t.GetElementType(),item);
i++;
}
o=items;
}else{
o=___to_type(t,items.FirstOrDefault());
}
return o;
}
private object ___to_type(Type t,string value)
{
try{
if(t == typeof(String))
{
return value;
}
foreach(var item in t.GetMethods())
{
var _params = item.GetParameters();
if(_params.Length != 1)
{
continue;
}
if(_params[0].ParameterType != typeof(String)) continue;
if(item.IsStatic && !item.IsConstructor && item.Name == "Parse")
{
return item.Invoke(null,new object[]{value});
}
if(item.IsConstructor)
{
return Activator.CreateInstance(t,value);
}
}
MethodInfo method = typeof(JsonConvert).GetMethod(nameof(JsonConvert.DeserializeObject));
MethodInfo generic = method.MakeGenericMethod(t);
return generic.Invoke(null,new object[]{value});
}catch(Exception ex)
{
_=ex;
return Default();
}
}
public object GetArgument(ServerContext ctx,MultipartParser p,Dictionary<string,List<string>> args)
{
if(_isServerCtx)
{
return ctx;
}
if(_isMultipart)
{
return p;
}
foreach(var item in args)
{
if(item.Key.ToLower() == name)
{
return ___to_type_ar(type,item.Value);
}
}
return Default();
}
}
public TextResponse String(string text,string mimeType="text/html")
{
return new TextResponse(text,mimeType);
}
public FileResponse File(Stream strm,string mimeType,bool ownStream=true)
{
return new FileResponse(strm,mimeType,ownStream);
}
public FileResponse File(string filename,bool inline=true)
{
return new FileResponse(filename,inline);
}
public FileResponse File(Stream strm,string mimeType,string filename,bool inline=false,bool ownStream=false)
{
return new FileResponse(strm,mimeType,filename,inline,ownStream);
}
bool ___has_loaded=false;
private void ___lazy_load_everything()
{
if(!___has_loaded)
{
Type t=GetType();
foreach(var item in t.GetMethods())
{
IHttpRoute route = new HttpGetAttribute($"/{item.Name}");
foreach(var r in item.GetCustomAttributes())
{
var rte=r as IHttpRoute;
if(rte != null)
{
route=rte;
if(string.IsNullOrWhiteSpace(rte.RouteName))
{
route.RouteName = $"/{item.Name}";
}
break;
}
}
___RouteMethod m=new ___RouteMethod();
var __props = item.GetParameters();
m.ContainsServerCtx = __props.Any(e=>e.ParameterType == typeof(ServerContext));
m.ContainsMultipart = __props.Any(e=>e.ParameterType == typeof(MultipartParser));
m.ReturnsVoid = item.ReturnType == typeof(void);
m.info=item;
m.Name = route.RouteName;
m.Method=route.Method;
m._instance = this;
m._args=new List<___Arg>();
if(m.ContainsServerCtx || !m.ReturnsVoid)
{
//add route to
foreach(var args in __props)
{
___Arg a=new ___Arg();
a._isServerCtx = args.ParameterType == typeof(ServerContext);
a._isMultipart = args.ParameterType == typeof(MultipartParser);
a.name = args.Name.ToLower();
a.type = args.ParameterType;
m._args.Add(a);
}
___RouteMethods.Add(m);
}
}
}
___has_loaded=true;
}
public async Task Handle(ServerContext ctx)
{
___lazy_load_everything();
Dictionary<string,List<string>> args=new Dictionary<string, List<string>>();
MultipartParser parser=null;
bool handled=false;
foreach(var m in ___RouteMethods)
{
args.Clear();
string p=ctx.Request.GetQueryParameters(ctx.Request.CurrentUrl,args);
if(m.Method == ctx.Request.RequestLine.Method && m.Name ==p)
{
//want to check for method
if(m.Method == "POST")
{
string _ctt;
if(ctx.Request.Headers.TryGetFirst("Content-Type",out _ctt))
{
if(_ctt.StartsWith("multipart/form-data"))
{
parser = ctx.Request.GetMultipartParser();
}
if(_ctt == "application/x-www-form-urlencoded")
{
ctx.Request.GetUrlEncodedPost(args);
}
}
}
m.Call(ctx,parser,args);
handled=true;
break;
}
}
if(!handled)
{
NotFound(ctx);
}
await Task.CompletedTask;
}
}
public class FileResponse : IResponse
{
bool inline;
public FileResponse(Stream strm,string mimeType,bool ownStream)
{
this.ownStream=ownStream;
Stream = strm;
MimeType=mimeType;
FileName="";
}
public FileResponse(Stream strm,string mimeType,string filename,bool inline,bool ownStream)
{
this.ownStream=ownStream;
Stream =strm;
MimeType = mimeType;
FileName=filename;
this.inline=inline;
}
bool ownStream;
public FileResponse(string filename,bool inline)
{
ownStream=true;
MimeType=MimeTypesMap.GetMimeType(filename);
Stream=File.OpenRead(filename);
FileName = Path.GetFileName(filename);
this.inline=inline;
}
public Stream Stream {get;set;}
public string MimeType {get;set;}
public string FileName {get;set;}
public void Handle(ServerContext ctx)
{
if(string.IsNullOrWhiteSpace(FileName))
{
ctx.Response.WithContentType(MimeType).SendRangableResponseStream(Stream);
}else{
ctx.Response.WithContentType(MimeType).WithFileName(FileName,inline).SendRangableResponseStream(Stream);
}
if(ownStream)
Stream.Dispose();
}
}
public class TextResponse : IResponse
{
public TextResponse(string text,string mime)
{
Text=text;
MimeType=mime;
}
public string Text {get;set;}
public string MimeType {get;set;}
public void Handle(ServerContext ctx)
{
ctx.Response.WithContentType(MimeType).SendText(Text);
}
}
public interface IResponse
{
void Handle(ServerContext ctx);
}
public interface IHttpRoute
{
string RouteName {get;set;}
string Method {get;}
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class HttpGetAttribute :Attribute, IHttpRoute
{
public string Method {get{return "GET";}}
public string RouteName {get;set;}
public HttpGetAttribute(string name)
{
RouteName=name;
}
public HttpGetAttribute()
{
RouteName = "";
}
}
[AttributeUsage(AttributeTargets.Method)]
public sealed class HttpPostAttribute :Attribute, IHttpRoute
{
public string Method {get{return "POST";}}
public string RouteName {get;set;}
public HttpPostAttribute(string name)
{
RouteName=name;
}
public HttpPostAttribute()
{
RouteName = "";
}
}
public sealed class HttpMethodAttribute : Attribute, IHttpRoute
{
string _meth;
public string Method {get{return _meth;}}
public string RouteName {get;set;}
public HttpMethodAttribute(string method)
{
_meth=method;
RouteName="";
}
public HttpMethodAttribute(string method,string route)
{
_meth = method;
RouteName = route;
}
}
}