tesses.http/Tesses.Http/HttpServer.cs

296 lines
10 KiB
C#

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<string> items=new List<string>();
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<string> items=new List<string>();
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<ServerContext,bool> 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();
}
}
}