441 lines
13 KiB
C#
441 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 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)
|
||
|
{
|
||
|
var t0=t.MakeArrayType();
|
||
|
object[] items2=new object[items.Count];
|
||
|
foreach(var item in items)
|
||
|
{
|
||
|
___to_type(t0,item);
|
||
|
}
|
||
|
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.SerializeObject));
|
||
|
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;
|
||
|
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);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|