Make HTTPServer shutdown with open connections

master
Harald Wolff 2019-09-11 09:29:05 +02:00
parent 0175870be6
commit 9ea8d16f91
5 changed files with 183 additions and 111 deletions

View File

@ -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<HTTPServer,TcpClient,HTTPServerConnection> CreateServerConnection { get; set; }
bool shutdown = false;
Dictionary<IPEndPoint, TcpListener> tcpListeners = new Dictionary<IPEndPoint, TcpListener>();
Dictionary<Uri, HttpApplication> applications = new Dictionary<Uri, HttpApplication>();
Dictionary<URI, HttpApplication> applications = new Dictionary<URI, HttpApplication>();
Dictionary<TcpListener, Thread> currentListenerThreads = new Dictionary<TcpListener, Thread>();
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();
}
}
}

View File

@ -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<HTTPServerConnection> Current { get; } = new ThreadLocal<HTTPServerConnection>();
static HashSet<HTTPServerConnection> currentConnections = new HashSet<HTTPServerConnection>();
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();
}
}
}

View File

@ -10,7 +10,7 @@ using ln.http.resources.session;
namespace ln.http
{
public class HttpRequest
public class HttpRequest : IDisposable
{
Dictionary<String, String> requestHeaders;
Dictionary<String, String> 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;
}
}
}

View File

@ -56,6 +56,7 @@
<Compile Include="websocket\WebSocket.cs" />
<Compile Include="websocket\WebSocketEventArgs.cs" />
<Compile Include="websocket\WebSocketFrame.cs" />
<Compile Include="HTTPServerConnection.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="exceptions\" />

View File

@ -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;
}