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.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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\" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue