296 lines
10 KiB
C#
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();
|
|
}
|
|
}
|
|
|
|
|
|
}
|