From 9ea8d16f9107097caefaede8171851e94437bc50 Mon Sep 17 00:00:00 2001 From: Harald Wolff Date: Wed, 11 Sep 2019 09:29:05 +0200 Subject: [PATCH] Make HTTPServer shutdown with open connections --- HTTPServer.cs | 119 ++++++++------------------------- HTTPServerConnection.cs | 142 ++++++++++++++++++++++++++++++++++++++++ HttpRequest.cs | 26 ++------ ln.http.csproj | 1 + websocket/WebSocket.cs | 6 +- 5 files changed, 183 insertions(+), 111 deletions(-) create mode 100644 HTTPServerConnection.cs diff --git a/HTTPServer.cs b/HTTPServer.cs index 2919a17..8886b7d 100644 --- a/HTTPServer.cs +++ b/HTTPServer.cs @@ -4,10 +4,10 @@ using System.Net; using System.Net.Sockets; using System.Threading; using System.IO; -using System.Text; using ln.http.resources.session; using ln.logging; using ln.types.threads; +using ln.types; namespace ln.http @@ -24,20 +24,24 @@ namespace ln.http public bool IsRunning => (currentListenerThreads.Count > 0) || (threadPool.CurrentPoolSize > 0); - + public Func CreateServerConnection { get; set; } bool shutdown = false; Dictionary tcpListeners = new Dictionary(); - Dictionary applications = new Dictionary(); + Dictionary applications = new Dictionary(); Dictionary currentListenerThreads = new Dictionary(); - Pool threadPool = new Pool(32); + DynamicPool threadPool; public HTTPServer() { + CreateServerConnection = (HTTPServer httpServer, TcpClient tcpClient) => new HTTPServerConnection(httpServer, tcpClient); + SessionCache = new SessionCache(); + + threadPool = new DynamicPool(1024); } public void AddEndpoint(IPEndPoint endpoint) @@ -58,13 +62,26 @@ namespace ln.http } } - public void AddApplication(Uri BaseURI, HttpApplication application) + public void AddApplication(URI BaseURI, HttpApplication application) { applications[BaseURI] = application; } + public HttpApplication GetHttpApplication(URI baseURI) + { + HttpApplication application = DefaultApplication; + + if (applications.ContainsKey(baseURI)) + application = applications[baseURI]; + + return application; + } + + public void Start() { + threadPool.Start(); + foreach (TcpListener tcpListener in this.tcpListeners.Values) { tcpListener.Start(backlog); @@ -72,7 +89,6 @@ namespace ln.http Thread listenerThread = new Thread(() => AcceptConnections(tcpListener)); listenerThread.Start(); } - } public void Stop() @@ -87,12 +103,10 @@ namespace ln.http tcpListener.Stop(); } - threadPool.Close(); + foreach (HTTPServerConnection connection in HTTPServerConnection.CurrentConnections) + connection.Abort(); - while (IsRunning) - { - Thread.Sleep(50); - } + threadPool.Stop(true); } private void AcceptConnections(TcpListener tcpListener) @@ -130,92 +144,17 @@ namespace ln.http private void AcceptConnection(TcpListener tcpListener) { TcpClient tcpClient = tcpListener.AcceptTcpClient(); - this.threadPool.Enqueue(() => HandleConnection(tcpClient)); - } - private void HandleConnection(TcpClient tcpClient) - { - HttpReader httpReader = new HttpReader(tcpClient.GetStream()); - httpReader.Read(); - - HttpRequest httpRequest = new HttpRequest(httpReader, (IPEndPoint)tcpClient.Client.LocalEndPoint); - HttpResponse response = null; - - httpRequest.ApplySession(SessionCache); - - try + HTTPServerConnection connection = CreateServerConnection(this, tcpClient); + if (connection == null) { - HttpApplication application = DefaultApplication; - - if (applications.ContainsKey(httpRequest.BaseURI)) - application = applications[httpRequest.BaseURI]; - - application.Authenticate(httpRequest); - application.Authorize(httpRequest); - - response = application.GetResponse(httpRequest); - } - catch (Exception e) - { - response = new HttpResponse(httpRequest, "text/plain"); - response.StatusCode = 500; - response.ContentWriter.WriteLine("Exception caught: {0}", e); - } - - if (response == null) - { - Logging.Log(LogLevel.DEBUG, "Request {0} returned no Response", httpRequest); + Logging.Log(LogLevel.ERROR, "HTTPServer: CreateServerConnection(): returned null"); } else { - if (!response.HasCustomContentStream) - { - response.ContentWriter.Flush(); - MemoryStream cstream = (MemoryStream)response.ContentStream; - cstream.Position = 0; - - response.SetHeader("content-length", cstream.Length.ToString()); - } - - if (SessionCache != null) - { - SessionCache.ApplySessionID(response, httpRequest.Session); - } - - response.AddCookie("LN_SEEN", DateTime.Now.ToString()); - - SendResponse(tcpClient.GetStream(), response); - - tcpClient.Close(); + this.threadPool.Enqueue(connection); } } - - - public static void SendResponse(Stream stream, HttpResponse response) - { - StreamWriter streamWriter = new StreamWriter(stream); - streamWriter.NewLine = "\r\n"; - - streamWriter.WriteLine("{0} {1} {2}", response.HttpRequest.Protocol, response.StatusCode, response.StatusMessage); - foreach (String headerName in response.GetHeaderNames()) - { - streamWriter.WriteLine("{0}: {1}", headerName, response.GetHeader(headerName)); - } - - foreach (HttpCookie httpCookie in response.Cookies) - { - streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString()); - } - - streamWriter.WriteLine(); - streamWriter.Flush(); - - response.ContentStream.CopyTo(stream); - response.ContentStream.Close(); - response.ContentStream.Dispose(); - - streamWriter.Flush(); - } } } diff --git a/HTTPServerConnection.cs b/HTTPServerConnection.cs new file mode 100644 index 0000000..55e642d --- /dev/null +++ b/HTTPServerConnection.cs @@ -0,0 +1,142 @@ +using System; +using System.Net.Sockets; +using ln.types.threads; +using System.Net; +using ln.types; +using ln.logging; +using System.IO; +using System.Threading; +using System.Collections.Generic; +using System.Linq; +namespace ln.http +{ + public delegate void HTTPServerConnectionEvent(HTTPServerConnection connection); + + public class HTTPServerConnection : PoolJob + { + public static ThreadLocal Current { get; } = new ThreadLocal(); + static HashSet currentConnections = new HashSet(); + public static HTTPServerConnection[] CurrentConnections => currentConnections.ToArray(); + + public HTTPServer HTTPServer { get; } + public TcpClient TcpClient { get; } + + public HttpRequest CurrentRequest { get; protected set; } + + public event HTTPServerConnectionEvent AbortRequested; + + public HTTPServerConnection(HTTPServer httpServer,TcpClient tcpClient) + { + HTTPServer = httpServer; + TcpClient = tcpClient; + } + + public virtual HttpResponse GetResponse(HttpRequest httpRequest,HttpApplication httpApplication) => httpApplication.GetResponse(httpRequest); + + public virtual void Abort() + { + if (AbortRequested != null) + AbortRequested(this); + } + + + public override void RunJob() + { + HTTPServerConnection saveCurrent = Current.Value; + Current.Value = this; + currentConnections.Add(this); + + try + { + setState("reading http request"); + + HttpReader httpReader = new HttpReader(TcpClient.GetStream()); + httpReader.Read(); + + HttpResponse response = null; + + using (CurrentRequest = new HttpRequest(httpReader, (IPEndPoint)TcpClient.Client.LocalEndPoint)) + { + CurrentRequest.ApplySession(HTTPServer.SessionCache); + + try + { + HttpApplication application = HTTPServer.GetHttpApplication(new URI(CurrentRequest.BaseURI.ToString())); + + application.Authenticate(CurrentRequest); + application.Authorize(CurrentRequest); + + setState("handling http request"); + + response = GetResponse(CurrentRequest, application); + } + catch (Exception e) + { + setState("handling exception"); + + response = new HttpResponse(CurrentRequest, "text/plain"); + response.StatusCode = 500; + response.ContentWriter.WriteLine("Exception caught: {0}", e); + } + } + setState("sending response"); + + if (response == null) + { + Logging.Log(LogLevel.DEBUG, "Request {0} returned no Response", CurrentRequest); + } + else + { + if (!response.HasCustomContentStream) + { + response.ContentWriter.Flush(); + MemoryStream cstream = (MemoryStream)response.ContentStream; + cstream.Position = 0; + + response.SetHeader("content-length", cstream.Length.ToString()); + } + + if (CurrentRequest.Session != null) + HTTPServer?.SessionCache?.ApplySessionID(response, CurrentRequest.Session); + + response.AddCookie("LN_SEEN", DateTime.Now.ToString()); + + SendResponse(TcpClient.GetStream(), response); + TcpClient.Close(); + } + } + finally + { + Current.Value = saveCurrent; + currentConnections.Remove(this); + } + } + + public static void SendResponse(Stream stream, HttpResponse response) + { + StreamWriter streamWriter = new StreamWriter(stream); + streamWriter.NewLine = "\r\n"; + + streamWriter.WriteLine("{0} {1} {2}", response.HttpRequest.Protocol, response.StatusCode, response.StatusMessage); + foreach (String headerName in response.GetHeaderNames()) + { + streamWriter.WriteLine("{0}: {1}", headerName, response.GetHeader(headerName)); + } + + foreach (HttpCookie httpCookie in response.Cookies) + { + streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString()); + } + + streamWriter.WriteLine(); + streamWriter.Flush(); + + response.ContentStream.CopyTo(stream); + response.ContentStream.Close(); + response.ContentStream.Dispose(); + + streamWriter.Flush(); + } + + } +} diff --git a/HttpRequest.cs b/HttpRequest.cs index b731759..9cd4ca5 100644 --- a/HttpRequest.cs +++ b/HttpRequest.cs @@ -10,7 +10,7 @@ using ln.http.resources.session; namespace ln.http { - public class HttpRequest + public class HttpRequest : IDisposable { Dictionary requestHeaders; Dictionary requestCookies; @@ -192,25 +192,13 @@ namespace ln.http return httpResponse; } + public void Dispose() + { + contentReader?.Dispose(); + ContentStream?.Dispose(); - //private void SendResponse() - //{ - // using (StreamWriter writer = new StreamWriter(this.stream)) - // { - // ResponseStream.Position = 0; - // SetResponseHeader("Content-Length", responseStream.Length.ToString()); - - // writer.WriteLine("{0} {1} {2}", Protocol, StatusCode, HttpStatusCodes.GetStatusMessage(StatusCode)); - // foreach (String rhName in responseHeaders.Keys){ - // writer.WriteLine("{0}: {1}", rhName, responseHeaders[rhName]); - // } - // writer.WriteLine(); - // writer.Flush(); - - // responseStream.CopyTo(this.stream); - // } - //} - + Session = null; + } } } diff --git a/ln.http.csproj b/ln.http.csproj index 87a3b39..cfb4ac7 100644 --- a/ln.http.csproj +++ b/ln.http.csproj @@ -56,6 +56,7 @@ + diff --git a/websocket/WebSocket.cs b/websocket/WebSocket.cs index 717da09..52aa678 100644 --- a/websocket/WebSocket.cs +++ b/websocket/WebSocket.cs @@ -3,9 +3,9 @@ using System.IO; using System.Threading; using ln.logging; using ln.http.exceptions; -using System.Linq; using System.Security.Cryptography; using System.Text; + namespace ln.http.websocket { public enum WebSocketOpcode : int @@ -70,7 +70,9 @@ namespace ln.http.websocket Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey))) ); - HTTPServer.SendResponse(Stream, httpResponse); + HTTPServerConnection.SendResponse(Stream, httpResponse); + + HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close(); State = WebSocketState.OPEN; }