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> 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 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> 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> args=new Dictionary>(); 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; } } }