Make HTTPServer shutdown with open connections
parent
0175870be6
commit
9ea8d16f91
119
HTTPServer.cs
119
HTTPServer.cs
|
@ -4,10 +4,10 @@ using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
|
||||||
using ln.http.resources.session;
|
using ln.http.resources.session;
|
||||||
using ln.logging;
|
using ln.logging;
|
||||||
using ln.types.threads;
|
using ln.types.threads;
|
||||||
|
using ln.types;
|
||||||
|
|
||||||
|
|
||||||
namespace ln.http
|
namespace ln.http
|
||||||
|
@ -24,20 +24,24 @@ namespace ln.http
|
||||||
|
|
||||||
public bool IsRunning => (currentListenerThreads.Count > 0) || (threadPool.CurrentPoolSize > 0);
|
public bool IsRunning => (currentListenerThreads.Count > 0) || (threadPool.CurrentPoolSize > 0);
|
||||||
|
|
||||||
|
public Func<HTTPServer,TcpClient,HTTPServerConnection> CreateServerConnection { get; set; }
|
||||||
|
|
||||||
bool shutdown = false;
|
bool shutdown = false;
|
||||||
|
|
||||||
Dictionary<IPEndPoint, TcpListener> tcpListeners = new Dictionary<IPEndPoint, TcpListener>();
|
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>();
|
Dictionary<TcpListener, Thread> currentListenerThreads = new Dictionary<TcpListener, Thread>();
|
||||||
|
|
||||||
Pool threadPool = new Pool(32);
|
DynamicPool threadPool;
|
||||||
|
|
||||||
public HTTPServer()
|
public HTTPServer()
|
||||||
{
|
{
|
||||||
|
CreateServerConnection = (HTTPServer httpServer, TcpClient tcpClient) => new HTTPServerConnection(httpServer, tcpClient);
|
||||||
|
|
||||||
SessionCache = new SessionCache();
|
SessionCache = new SessionCache();
|
||||||
|
|
||||||
|
threadPool = new DynamicPool(1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddEndpoint(IPEndPoint endpoint)
|
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;
|
applications[BaseURI] = application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpApplication GetHttpApplication(URI baseURI)
|
||||||
|
{
|
||||||
|
HttpApplication application = DefaultApplication;
|
||||||
|
|
||||||
|
if (applications.ContainsKey(baseURI))
|
||||||
|
application = applications[baseURI];
|
||||||
|
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
threadPool.Start();
|
||||||
|
|
||||||
foreach (TcpListener tcpListener in this.tcpListeners.Values)
|
foreach (TcpListener tcpListener in this.tcpListeners.Values)
|
||||||
{
|
{
|
||||||
tcpListener.Start(backlog);
|
tcpListener.Start(backlog);
|
||||||
|
@ -72,7 +89,6 @@ namespace ln.http
|
||||||
Thread listenerThread = new Thread(() => AcceptConnections(tcpListener));
|
Thread listenerThread = new Thread(() => AcceptConnections(tcpListener));
|
||||||
listenerThread.Start();
|
listenerThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
|
@ -87,12 +103,10 @@ namespace ln.http
|
||||||
tcpListener.Stop();
|
tcpListener.Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
threadPool.Close();
|
foreach (HTTPServerConnection connection in HTTPServerConnection.CurrentConnections)
|
||||||
|
connection.Abort();
|
||||||
|
|
||||||
while (IsRunning)
|
threadPool.Stop(true);
|
||||||
{
|
|
||||||
Thread.Sleep(50);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AcceptConnections(TcpListener tcpListener)
|
private void AcceptConnections(TcpListener tcpListener)
|
||||||
|
@ -130,92 +144,17 @@ namespace ln.http
|
||||||
private void AcceptConnection(TcpListener tcpListener)
|
private void AcceptConnection(TcpListener tcpListener)
|
||||||
{
|
{
|
||||||
TcpClient tcpClient = tcpListener.AcceptTcpClient();
|
TcpClient tcpClient = tcpListener.AcceptTcpClient();
|
||||||
this.threadPool.Enqueue(() => HandleConnection(tcpClient));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleConnection(TcpClient tcpClient)
|
HTTPServerConnection connection = CreateServerConnection(this, tcpClient);
|
||||||
{
|
if (connection == null)
|
||||||
HttpReader httpReader = new HttpReader(tcpClient.GetStream());
|
|
||||||
httpReader.Read();
|
|
||||||
|
|
||||||
HttpRequest httpRequest = new HttpRequest(httpReader, (IPEndPoint)tcpClient.Client.LocalEndPoint);
|
|
||||||
HttpResponse response = null;
|
|
||||||
|
|
||||||
httpRequest.ApplySession(SessionCache);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
HttpApplication application = DefaultApplication;
|
Logging.Log(LogLevel.ERROR, "HTTPServer: CreateServerConnection(): returned null");
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!response.HasCustomContentStream)
|
this.threadPool.Enqueue(connection);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ using ln.http.resources.session;
|
||||||
|
|
||||||
namespace ln.http
|
namespace ln.http
|
||||||
{
|
{
|
||||||
public class HttpRequest
|
public class HttpRequest : IDisposable
|
||||||
{
|
{
|
||||||
Dictionary<String, String> requestHeaders;
|
Dictionary<String, String> requestHeaders;
|
||||||
Dictionary<String, String> requestCookies;
|
Dictionary<String, String> requestCookies;
|
||||||
|
@ -192,25 +192,13 @@ namespace ln.http
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
contentReader?.Dispose();
|
||||||
|
ContentStream?.Dispose();
|
||||||
|
|
||||||
//private void SendResponse()
|
Session = null;
|
||||||
//{
|
}
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
<Compile Include="websocket\WebSocket.cs" />
|
<Compile Include="websocket\WebSocket.cs" />
|
||||||
<Compile Include="websocket\WebSocketEventArgs.cs" />
|
<Compile Include="websocket\WebSocketEventArgs.cs" />
|
||||||
<Compile Include="websocket\WebSocketFrame.cs" />
|
<Compile Include="websocket\WebSocketFrame.cs" />
|
||||||
|
<Compile Include="HTTPServerConnection.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="exceptions\" />
|
<Folder Include="exceptions\" />
|
||||||
|
|
|
@ -3,9 +3,9 @@ using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using ln.logging;
|
using ln.logging;
|
||||||
using ln.http.exceptions;
|
using ln.http.exceptions;
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace ln.http.websocket
|
namespace ln.http.websocket
|
||||||
{
|
{
|
||||||
public enum WebSocketOpcode : int
|
public enum WebSocketOpcode : int
|
||||||
|
@ -70,7 +70,9 @@ namespace ln.http.websocket
|
||||||
Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey)))
|
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;
|
State = WebSocketState.OPEN;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue