SimpleHttpRouter, RouterTarget

master
Harald Wolff 2020-01-07 12:23:14 +01:00
parent 92b83af7f0
commit 120c8d3de3
8 changed files with 212 additions and 54 deletions

View File

@ -2,15 +2,12 @@
using System.Collections.Generic;
using ln.logging;
using ln.types.threads;
using ln.types;
using ln.http.listener;
using ln.http.connections;
using ln.types.net;
using ln.http.cert;
using System.Globalization;
using System.Net.Sockets;
using ln.http.session;
using ln.http.exceptions;
using System.Threading;
namespace ln.http
@ -21,7 +18,7 @@ namespace ln.http
public static int defaultPort = 8080;
public static bool exclusivePortListener = false;
public HttpRouter Router { get; set; }
public IHttpRouter Router { get; set; }
public bool IsRunning => !shutdown && (threadPool.CurrentPoolSize > 0);
public Logger Logger { get; set; }
@ -34,22 +31,25 @@ namespace ln.http
DynamicPool threadPool;
public DynamicPool ThreadPool => threadPool;
HashSet<Connection> currentConnections = new HashSet<Connection>();
public IEnumerable<Connection> CurrentConnections => currentConnections;
public HTTPServer()
{
Logger = Logger.Default;
threadPool = new DynamicPool(1024);
}
public HTTPServer(HttpRouter router)
public HTTPServer(IHttpRouter router)
: this()
{
Router = router;
}
public HTTPServer(Listener listener, HttpRouter router)
public HTTPServer(Listener listener, IHttpRouter router)
: this(router)
{
AddListener(listener);
}
public HTTPServer(Endpoint endpoint, HttpRouter router)
public HTTPServer(Endpoint endpoint, IHttpRouter router)
: this(new HttpListener(endpoint), router) { }
public void AddListener(Listener listener)
@ -100,63 +100,96 @@ namespace ln.http
foreach (Listener listener in listeners)
StopListener(listener);
for (int n = 0; n < 150; n++)
{
lock (currentConnections)
{
if (currentConnections.Count == 0)
break;
if ((n % 20) == 0)
{
Logging.Log(LogLevel.INFO, "HTTPServer: still waiting for {0} connections to close", currentConnections.Count);
}
}
Thread.Sleep(100);
}
lock (currentConnections)
{
foreach (Connection connection in currentConnections)
{
connection.Close();
}
}
threadPool.Stop(true);
}
private void HandleConnection(Connection connection)
{
HttpRequest httpRequest = null;
bool keepAlive = true;
lock (this.currentConnections)
currentConnections.Add(connection);
try
{
do
HttpRequest httpRequest = null;
bool keepAlive = true;
try
{
httpRequest = connection.ReadRequest(this);
if (httpRequest == null)
break;
HttpResponse response;
try
do
{
response = Router.Route(httpRequest);
} catch (HttpException httpExc)
{
response = new HttpResponse(httpRequest);
response.StatusCode = httpExc.StatusCode;
response.StatusMessage = httpExc.Message;
response.ContentWriter.WriteLine(httpExc.Message);
}
httpRequest = connection.ReadRequest(this);
if (httpRequest == null)
break;
if (response != null)
{
keepAlive = httpRequest.GetRequestHeader("connection", "keep-alive").Equals("keep-alive") && response.GetHeader("connection", "keep-alive").Equals("keep-alive");
response.SetHeader("connection", keepAlive ? "keep-alive" : "close");
HttpResponse response;
try
{
response = Router.Route(httpRequest.URI.AbsolutePath,httpRequest);
}
catch (HttpException httpExc)
{
response = new HttpResponse(httpRequest);
response.StatusCode = httpExc.StatusCode;
response.StatusMessage = httpExc.Message;
response.ContentWriter.WriteLine(httpExc.Message);
}
connection.SendResponse(response);
}
else
{
keepAlive = false;
}
} while (keepAlive);
}
catch (Exception e)
{
Logging.Log(e);
if (httpRequest != null)
{
HttpResponse httpResponse = new HttpResponse(httpRequest);
httpResponse.StatusCode = 500;
httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
httpResponse.ContentWriter.Flush();
if (response != null)
{
keepAlive = httpRequest.GetRequestHeader("connection", "keep-alive").Equals("keep-alive") && response.GetHeader("connection", "keep-alive").Equals("keep-alive");
response.SetHeader("connection", keepAlive ? "keep-alive" : "close");
connection.SendResponse(httpResponse);
connection.SendResponse(response);
}
else
{
keepAlive = false;
}
} while (keepAlive);
}
catch (Exception e)
{
Logging.Log(e);
if (httpRequest != null)
{
HttpResponse httpResponse = new HttpResponse(httpRequest);
httpResponse.StatusCode = 500;
httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
httpResponse.ContentWriter.Flush();
connection.SendResponse(httpResponse);
}
}
HttpRequest.ClearCurrent();
connection.GetStream().Close();
} finally
{
lock (currentConnections)
currentConnections.Remove(connection);
}
HttpRequest.ClearCurrent();
connection.GetStream().Close();
}
public void Log(DateTime startTime,double duration,HttpRequest httpRequest,HttpResponse httpResponse)

View File

@ -16,6 +16,7 @@ namespace ln.http
Dictionary<String, String> requestHeaders;
Dictionary<String, String> requestCookies;
Dictionary<string, String> requestParameters;
public HTTPServer HTTPServer { get; }
@ -71,6 +72,7 @@ namespace ln.http
requestHeaders = new Dictionary<string, string>(httpReader.Headers);
requestCookies = new Dictionary<string, string>();
requestParameters = new Dictionary<string, string>();
Setup();
@ -171,6 +173,17 @@ namespace ln.http
return requestCookies[name];
}
public String GetParameter(String parameterName) => GetParameter(parameterName, null);
public String GetParameter(String parameterName,String defaultValue)
{
if (!requestParameters.TryGetValue(parameterName, out string value))
value = defaultValue;
return value;
}
public void SetParameter(String parameterName,String parameterValue) => requestParameters[parameterName] = parameterValue;
public IEnumerable<string> ParameterNames => requestParameters.Keys;
public string self()
{
return BaseURI.ToString();

View File

@ -2,7 +2,7 @@
using ln.logging;
namespace ln.http
{
public abstract class HttpRouter
public abstract class HttpRouter : IHttpRouter
{
public HttpRouter()
{
@ -10,7 +10,7 @@ namespace ln.http
public abstract IHTTPResource FindResource(HttpRequest httpRequest);
public virtual HttpResponse Route(HttpRequest httpRequest)
public virtual HttpResponse Route(string path,HttpRequest httpRequest)
{
try
{

8
IHttpRouter.cs 100644
View File

@ -0,0 +1,8 @@
using System;
namespace ln.http
{
public interface IHttpRouter
{
HttpResponse Route(string path, HttpRequest httpRequest);
}
}

18
RouterTarget.cs 100644
View File

@ -0,0 +1,18 @@
using System;
namespace ln.http
{
public class RouterTarget :IHttpRouter
{
public Func<HttpRequest,HttpResponse> Target { get; }
public RouterTarget(Func<HttpRequest, HttpResponse> target)
{
Target = target;
}
public HttpResponse Route(string path, HttpRequest httpRequest)
{
return Target(httpRequest);
}
}
}

View File

@ -0,0 +1,82 @@
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Text;
namespace ln.http
{
public class SimpleHttpRouter : IHttpRouter
{
List<SimpleRoute> routes = new List<SimpleRoute>();
public SimpleHttpRouter()
{
}
public void AddSimpleRoute(string simpleRoute, IHttpRouter target) => AddSimpleRoute(simpleRoute, target, simpleRoute.Split('/').Length);
public void AddSimpleRoute(string simpleRoute, IHttpRouter target, int priority)
{
string[] parts = simpleRoute.Split(new char[] { '/' },StringSplitOptions.RemoveEmptyEntries);
StringBuilder stringBuilder = new StringBuilder("^");
foreach (string part in parts)
{
if (part.StartsWith(":", StringComparison.InvariantCulture))
stringBuilder.AppendFormat("/(?<{0}>[^/]+)", part.Substring(1));
else if (part.Equals("*"))
stringBuilder.AppendFormat("/(?<_>.*)");
else
stringBuilder.AppendFormat("/{0}", part);
}
stringBuilder.Append("/?$");
AddRoute(stringBuilder.ToString(), target, priority);
}
public void AddRoute(String route, IHttpRouter target) => AddRoute(route, target, 0);
public void AddRoute(String route, IHttpRouter target,int priority)
{
lock (this)
{
routes.Add(new SimpleRoute(route, target, priority));
routes.Sort((SimpleRoute a, SimpleRoute b) => b.Priority - a.Priority);
}
}
public HttpResponse Route(string path, HttpRequest httpRequest)
{
foreach (SimpleRoute simpleRoute in routes.ToArray())
{
Match match = simpleRoute.Route.Match(path);
if (match.Success)
{
string residual = "";
foreach (Group group in match.Groups)
{
httpRequest?.SetParameter(group.Name, group.Value);
if (group.Name.Equals("_"))
residual = group.Value;
}
return simpleRoute.Target.Route(residual,httpRequest);
}
}
throw new KeyNotFoundException();
}
public class SimpleRoute
{
public int Priority { get; }
public Regex Route { get; }
public IHttpRouter Target { get; }
public SimpleRoute(string regex, IHttpRouter target) : this(regex, target, 0) { }
public SimpleRoute(string regex, IHttpRouter target,int priority)
{
Route = new Regex(regex);
Target = target;
Priority = priority;
}
}
}
}

View File

@ -30,7 +30,8 @@ namespace ln.http.listener
public override Connection Accept() => new HttpConnection(this,tcpListener.AcceptTcpClient());
public override bool IsOpen => tcpListener != null;
public override bool IsOpen => (tcpListener != null);
public override void Open()
{
tcpListener = new TcpListener(Listen, Port);

View File

@ -67,6 +67,9 @@
<Compile Include="HttpRouter.cs" />
<Compile Include="IHTTPResource.cs" />
<Compile Include="router\VirtualHostRouter.cs" />
<Compile Include="SimpleHttpRouter.cs" />
<Compile Include="IHttpRouter.cs" />
<Compile Include="RouterTarget.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="exceptions\" />