using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; namespace Tesses.Http { public static class RequestHandler { private class __IREQ : IRequestHandler { HandleRequestAsync a; public __IREQ(HandleRequestAsync hra) { a=hra; } public async Task Handle(ServerContext ctx) { if(a!=null) await a(ctx); } } private static bool DefaultFileExists(VirtualStorage storage,string[] _defaultFileNames,string path,out string name) { foreach(var def in _defaultFileNames) { name = storage.PathCombine(path, def); if(storage.FileExists(name)) { return true; } } name = ""; return false; } private static bool DefaultFileExists(string[] _defaultFileNames,string path,out string name) { foreach(var def in _defaultFileNames) { name = Path.Combine(path, def); if(File.Exists(name)) { return true; } } name = ""; return false; } public static IRequestHandler FromVirtualStorage(VirtualStorage storage,string[] filenames=null,bool listDirectories=true,IRequestHandler _notFound=null) { if(filenames==null) filenames = new string[]{ "index.html", "index.htm", "default.html", "default.htm" }; if(_notFound == null) _notFound=error; return FromDelegate(async(e)=>{ string someUrl = WebUtility.UrlDecode(e.Request.CurrentUrl.Split(new char[]{'?'},2)[0].Substring(1)); //Console.WriteLine(someUrl); if (storage.DirectoryExists(someUrl)) { string name; if(DefaultFileExists(storage,filenames,someUrl,out name)) { e.Response.SendFile(storage,name); }else{ if(listDirectories){ List items=new List(); foreach(var item in storage.EnumerateDirectories(someUrl)) { items.Add(item + "/"); } foreach(var item in storage.EnumerateFiles(someUrl)) { items.Add(item); } e.Response.SendFileListing($"Index of {e.Request.CurrentUrl.Split(new char[]{'?'},2)[0]}","Directory listing",items); }else{ e.Response.StatusLine=403; await _notFound.Handle(e); } } } else if (storage.FileExists(someUrl)) { e.Response.SendFile(storage,someUrl); } else { e.Response.StatusLine=404; await _notFound.Handle(e); } }); } public static IRequestHandler FromDirectory(string directory,string[] filenames=null,bool listDirectories=true,IRequestHandler _notFound=null) { if(filenames==null) filenames = new string[]{ "index.html", "index.htm", "default.html", "default.htm" }; if(_notFound == null) _notFound=error; return FromDelegate(async(e)=>{ string someUrl = Path.Combine(directory,WebUtility.UrlDecode(e.Request.CurrentUrl.Split(new char[]{'?'},2)[0].Substring(1)).Replace('/', Path.DirectorySeparatorChar)); //Console.WriteLine(someUrl); if (Directory.Exists(someUrl)) { string name; if(DefaultFileExists(filenames,someUrl,out name)) { e.Response.SendFile(name); }else{ if(listDirectories){ List items=new List(); foreach(var item in Directory.GetDirectories(someUrl)) { items.Add(Path.GetFileName(item) + "/"); } foreach(var item in Directory.GetFiles(someUrl)) { items.Add(Path.GetFileName(item)); } e.Response.SendFileListing($"Index of {e.Request.CurrentUrl.Split(new char[]{'?'},2)[0]}","Directory listing",items); }else{ e.Response.StatusLine=403; await _notFound.Handle(e); } } } else if (File.Exists(someUrl)) { e.Response.SendFile(someUrl); } else { e.Response.StatusLine=404; await _notFound.Handle(e); } }); } public static IRequestHandler FromDelegate(HandleRequestAsync hra) { return new __IREQ(hra); } public static IRequestHandler FromDelegateSync(HandleRequest req) { return new __IREQ(async(e)=>{ await Task.Run(()=>{ if(req != null) req(e); }); }); } internal static IRequestHandler error=new ErrorRequestHandler(); public static IRequestHandler Guaranteed(ServerContext ctx,IRequestHandler value) { if(value == null){ ctx.Response.StatusLine =404; return error; } return value; } } public interface IRequestHandler { Task Handle(ServerContext ctx); } public delegate Task HandleRequestAsync(ServerContext ctx); public delegate void HandleRequest(ServerContext ctx); public sealed class HTTPServer { public IPEndPoint LocalEndPoint {get{return (IPEndPoint)_listener.LocalEndpoint;}} IRequestHandler req; public bool AllowMultipleRequestsOnOneConnection {get;set;} public HTTPServer(HandleRequestAsync handler,TcpListener lstn) { req=RequestHandler.FromDelegate(handler); _listener=lstn; } public HTTPServer(HandleRequest handler,TcpListener lstn) { req = RequestHandler.FromDelegateSync(handler); } public HTTPServer(IRequestHandler handler,TcpListener lstn) { req=handler; _listener=lstn; } public Func FirstTime {get;set;}=null; public bool CatchExceptions {get;set;} = true; TcpListener _listener; public void Listen(CancellationToken token=default(CancellationToken)) { ListenAsync(token).Wait(); } public async Task ListenAsync(CancellationToken token=default(CancellationToken)) { _listener.Start(); using (var r = token.Register(() => _listener.Stop())) { while (!token.IsCancellationRequested) { try{ var socket=await _listener.AcceptTcpClientAsync(); Task.Factory.StartNew(async()=>{ try{ await HandleConnectionAsync(socket); }catch(Exception ex) { _=ex; if(!CatchExceptions) { throw ex; } } }).Wait(0); }catch(Exception ex) { _=ex; if(!CatchExceptions) { throw ex; } } } } } private async Task HandleConnectionAsync(TcpClient clt) { bool first=true; bool allowMultipleRequests=AllowMultipleRequestsOnOneConnection; while(clt.Connected) { var s=clt.GetStream(); HttpParser parser=new HttpParser(s); parser.ReceiveHeaders(); parser.SentHeaders=new HeaderCollection(); if(!allowMultipleRequests) { parser.SentHeaders.Add("Connection","close"); }else{ parser.SentHeaders.Add("Connection","keep-alive"); } var version = this.GetType().Assembly.GetName().Version; parser.SentHeaders.Add("Server",$"Tesses.Http/{version.ToString()}"); var ctx = new ServerContext(()=>{return clt.Connected;},parser,(IPEndPoint)clt.Client.LocalEndPoint,(IPEndPoint)clt.Client.RemoteEndPoint); if(first && FirstTime != null) { if(!FirstTime(ctx)) { return; } } await req.Handle(ctx); first=false; if(parser.ReceivedHeaders.ContainsKey("Connection")) { if(parser.ReceivedHeaders["Connection"].Contains("close")) { break; } } if(!allowMultipleRequests) break; } clt.Dispose(); } } }