From 120c8d3de329f059e598d49ffd307d36224228fe Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Tue, 7 Jan 2020 12:23:14 +0100 Subject: [PATCH] SimpleHttpRouter, RouterTarget --- HTTPServer.cs | 135 ++++++++++++++++++++++++--------------- HttpRequest.cs | 13 ++++ HttpRouter.cs | 4 +- IHttpRouter.cs | 8 +++ RouterTarget.cs | 18 ++++++ SimpleHttpRouter.cs | 82 ++++++++++++++++++++++++ listener/HttpListener.cs | 3 +- ln.http.csproj | 3 + 8 files changed, 212 insertions(+), 54 deletions(-) create mode 100644 IHttpRouter.cs create mode 100644 RouterTarget.cs create mode 100644 SimpleHttpRouter.cs diff --git a/HTTPServer.cs b/HTTPServer.cs index 96ae30f..71422b1 100644 --- a/HTTPServer.cs +++ b/HTTPServer.cs @@ -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 currentConnections = new HashSet(); + public IEnumerable 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) diff --git a/HttpRequest.cs b/HttpRequest.cs index 20b4742..8519eb7 100644 --- a/HttpRequest.cs +++ b/HttpRequest.cs @@ -16,6 +16,7 @@ namespace ln.http Dictionary requestHeaders; Dictionary requestCookies; + Dictionary requestParameters; public HTTPServer HTTPServer { get; } @@ -71,6 +72,7 @@ namespace ln.http requestHeaders = new Dictionary(httpReader.Headers); requestCookies = new Dictionary(); + requestParameters = new Dictionary(); 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 ParameterNames => requestParameters.Keys; + + public string self() { return BaseURI.ToString(); diff --git a/HttpRouter.cs b/HttpRouter.cs index 82a16a1..99e1bca 100644 --- a/HttpRouter.cs +++ b/HttpRouter.cs @@ -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 { diff --git a/IHttpRouter.cs b/IHttpRouter.cs new file mode 100644 index 0000000..866a5cb --- /dev/null +++ b/IHttpRouter.cs @@ -0,0 +1,8 @@ +using System; +namespace ln.http +{ + public interface IHttpRouter + { + HttpResponse Route(string path, HttpRequest httpRequest); + } +} diff --git a/RouterTarget.cs b/RouterTarget.cs new file mode 100644 index 0000000..46b61b0 --- /dev/null +++ b/RouterTarget.cs @@ -0,0 +1,18 @@ +using System; +namespace ln.http +{ + public class RouterTarget :IHttpRouter + { + public Func Target { get; } + + public RouterTarget(Func target) + { + Target = target; + } + + public HttpResponse Route(string path, HttpRequest httpRequest) + { + return Target(httpRequest); + } + } +} diff --git a/SimpleHttpRouter.cs b/SimpleHttpRouter.cs new file mode 100644 index 0000000..5953a49 --- /dev/null +++ b/SimpleHttpRouter.cs @@ -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 routes = new List(); + + 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; + } + } + } +} diff --git a/listener/HttpListener.cs b/listener/HttpListener.cs index 1482b94..e4c3a70 100644 --- a/listener/HttpListener.cs +++ b/listener/HttpListener.cs @@ -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); diff --git a/ln.http.csproj b/ln.http.csproj index 777f943..a891008 100644 --- a/ln.http.csproj +++ b/ln.http.csproj @@ -67,6 +67,9 @@ + + +