master
Harald Wolff 2019-11-04 10:00:33 +01:00
parent 89dddbc07b
commit 79b3eabde6
13 changed files with 466 additions and 108 deletions

View File

@ -1,14 +1,15 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using ln.http.resources.session;
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;
namespace ln.http
@ -20,53 +21,59 @@ namespace ln.http
public static bool exclusivePortListener = false;
public HttpApplication DefaultApplication { get; set; }
public SessionCache SessionCache { get; set; }
public bool IsRunning => (currentListenerThreads.Count > 0) || (threadPool.CurrentPoolSize > 0);
public Func<HTTPServer,TcpClient,HTTPServerConnection> CreateServerConnection { get; set; }
public bool IsRunning => !shutdown && (threadPool.CurrentPoolSize > 0);
public Func<HTTPServer,HTTPServerConnection> CreateServerConnection { get; set; }
public Logger Logger { get; set; }
bool shutdown = false;
List<Listener> listeners = new List<Listener>();
public Listener[] Listeners => listeners.ToArray();
Dictionary<IPEndPoint, TcpListener> tcpListeners = new Dictionary<IPEndPoint, TcpListener>();
Dictionary<URI, HttpApplication> applications = new Dictionary<URI, HttpApplication>();
Dictionary<TcpListener, Thread> currentListenerThreads = new Dictionary<TcpListener, Thread>();
DynamicPool threadPool;
public DynamicPool ThreadPool => threadPool;
public HTTPServer()
{
Logger = Logger.Default;
CreateServerConnection = (HTTPServer httpServer, TcpClient tcpClient) => new HTTPServerConnection(httpServer, tcpClient);
SessionCache = new SessionCache();
threadPool = new DynamicPool(1024);
}
public void AddEndpoint(IPEndPoint endpoint)
public void AddListener(Listener listener)
{
if (this.tcpListeners.ContainsKey(endpoint))
{
throw new ArgumentOutOfRangeException(nameof(endpoint), "EndPoint already added");
}
this.tcpListeners.Add(endpoint, new TcpListener(endpoint));
listeners.Add(listener);
if (IsRunning)
StartListener(listener);
}
public void RemoveEndpoint(IPEndPoint endpoint)
public void StartListener(Listener listener)
{
if (this.tcpListeners.ContainsKey(endpoint))
{
this.tcpListeners[endpoint].Stop();
this.tcpListeners.Remove(endpoint);
}
listener.Open();
threadPool.Enqueue(
() => listener.AcceptMany(
(connection) => threadPool.Enqueue(
() => this.HandleConnection(connection)
)
)
);
}
public void StopListener(Listener listener)
{
listener.Close();
}
public void AddEndpoint(Endpoint endpoint)
{
AddListener(new HttpListener(endpoint));
}
public void AddApplication(URI BaseURI, HttpApplication application)
@ -89,13 +96,8 @@ namespace ln.http
{
threadPool.Start();
foreach (TcpListener tcpListener in this.tcpListeners.Values)
{
tcpListener.Start(backlog);
Thread listenerThread = new Thread(() => AcceptConnections(tcpListener));
listenerThread.Start();
}
foreach (Listener listener in listeners)
StartListener(listener);
}
public void Stop()
@ -104,64 +106,54 @@ namespace ln.http
{
this.shutdown = true;
}
foreach (Listener listener in listeners)
StopListener(listener);
foreach (TcpListener tcpListener in tcpListeners.Values)
{
tcpListener.Stop();
}
foreach (HTTPServerConnection connection in HTTPServerConnection.CurrentConnections)
connection?.Abort();
if (threadPool.State == PoolState.RUN)
threadPool.Stop(true);
threadPool.Stop(true);
}
private void AcceptConnections(TcpListener tcpListener)
private void HandleConnection(Connection connection)
{
lock (this)
{
if (currentListenerThreads.ContainsKey(tcpListener))
{
throw new Exception("HTTP listener thread already running");
}
currentListenerThreads.Add(tcpListener, Thread.CurrentThread);
}
HttpRequest httpRequest = null;
bool keepAlive = true;
try
{
while (!this.shutdown)
do
{
AcceptConnection(tcpListener);
}
httpRequest = connection.ReadRequest(this);
if (httpRequest == null)
break;
httpRequest.ApplySession(SessionCache);
HttpApplication application = GetHttpApplication(new URI(httpRequest.BaseURI.ToString()));
application.Authenticate(httpRequest);
application.Authorize(httpRequest);
HttpResponse response = application.GetResponse(httpRequest);
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(response);
} while (keepAlive);
}
catch (Exception e)
{
Logging.Log(LogLevel.ERROR, "HTTPServer: Listener thread caught exception {0}", e);
Logging.Log(e);
if (httpRequest != null)
{
HttpResponse httpResponse = new HttpResponse(httpRequest);
httpResponse.StatusCode = 500;
httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
connection.SendResponse(httpResponse);
}
}
lock (this)
{
currentListenerThreads.Remove(tcpListener);
}
}
private void AcceptConnection(TcpListener tcpListener)
{
TcpClient tcpClient = tcpListener.AcceptTcpClient();
HTTPServerConnection connection = CreateServerConnection(this, tcpClient);
if (connection == null)
{
Logging.Log(LogLevel.ERROR, "HTTPServer: CreateServerConnection(): returned null");
}
else
{
this.threadPool.Enqueue(connection);
}
connection.GetStream().Close();
}
public void Log(DateTime startTime,double duration,HttpRequest httpRequest,HttpResponse httpResponse)

View File

@ -3,6 +3,7 @@ using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Net;
using ln.types.net;
namespace ln.http
{
@ -17,7 +18,7 @@ namespace ln.http
private int blen;
private int bptr;
public IPEndPoint RemoteEndpoint { get; private set; }
public Endpoint RemoteEndpoint { get; private set; }
public String Method { get; private set; }
public String URL { get; private set; }
@ -31,7 +32,7 @@ namespace ln.http
{
Stream = stream;
}
public HttpReader(Stream stream,IPEndPoint remoteEndpoint)
public HttpReader(Stream stream,Endpoint remoteEndpoint)
{
Stream = stream;
RemoteEndpoint = remoteEndpoint;
@ -84,7 +85,11 @@ namespace ln.http
bptr = 0;
do
{
blen += Stream.Read(buffer, blen, buffer.Length - blen);
int rlen = Stream.Read(buffer, blen, buffer.Length - blen);
if (rlen == 0)
throw new IOException();
blen += rlen;
while (bptr <= (blen - 4))
{
if (

View File

@ -7,6 +7,7 @@ using System.Net;
using System.Net.Sockets;
using ln.http.exceptions;
using ln.http.resources.session;
using ln.types.net;
namespace ln.http
{
@ -15,10 +16,10 @@ namespace ln.http
Dictionary<String, String> requestHeaders;
Dictionary<String, String> requestCookies;
public HTTPServer HTTPServer { get; }
public HTTPServer HTTPServer { get; }
public IPEndPoint RemoteEndpoint { get; private set; }
public IPEndPoint LocalEndpoint { get; private set; }
public Endpoint RemoteEndpoint { get; private set; }
public Endpoint LocalEndpoint { get; private set; }
public Uri BaseURI { get; set; }
public Uri URI { get; private set; }
@ -54,7 +55,7 @@ namespace ln.http
byte[] requestBody;
Stream connectionStream;
public HttpRequest(HTTPServer httpServer, HttpReader httpReader, IPEndPoint localEndpoint)
public HttpRequest(HTTPServer httpServer, HttpReader httpReader, Endpoint localEndpoint)
{
HTTPServer = httpServer;
connectionStream = httpReader.Stream;

View File

@ -49,11 +49,12 @@ namespace ln.http
SetHeader("content-type", contentType);
}
public String GetHeader(string name)
public String GetHeader(string name) => GetHeader(name, null);
public String GetHeader(string name, string defValue)
{
return String.Join(",", headers[name.ToUpper()]);
return headers.ContainsKey(name) ? String.Join(",", headers[name.ToUpper()]) : defValue;
}
public String[] GetHeaderValues(string name)
{
return headers[name.ToUpper()].ToArray();
@ -84,6 +85,10 @@ namespace ln.http
headers.Remove(name.ToUpper());
}
public bool ContainsHeader(string headerName)
{
return headers.ContainsKey(headerName.ToUpper());
}
public void AddCookie(string name,string value)
{

View File

@ -0,0 +1,59 @@
// /**
// * File: CertContainer.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
using System.IO;
namespace ln.http.cert
{
public class CertContainer
{
public string SearchPath { get; set; }
Dictionary<string, X509Certificate> certificates = new Dictionary<string, X509Certificate>();
public CertContainer(){ }
public CertContainer(string searchPath)
{
SearchPath = searchPath;
}
public void AddCertificate(string targetHost, X509Certificate certificate) => certificates[targetHost] = certificate;
public virtual X509Certificate LookupCertificate(string targetHost)
{
String p = Path.Combine(SearchPath, String.Format("{0}.pem",targetHost));
if (File.Exists(p))
{
return X509Certificate.CreateFromCertFile(p);
}
return null;
}
public X509Certificate SelectCertificate(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
if (!certificates.ContainsKey(targetHost) && (SearchPath != null))
{
X509Certificate certificate = LookupCertificate(targetHost);
if (certificate != null)
{
certificates[targetHost] = certificate;
}
else
{
return null;
}
}
return certificates[targetHost];
}
}
}

View File

@ -0,0 +1,80 @@
// /**
// * File: Connection.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using ln.types;
using System.IO;
using ln.types.net;
using ln.logging;
using ln.http.listener;
namespace ln.http.connections
{
public abstract class Connection
{
public Listener Listener { get; }
public abstract IPv6 RemoteHost { get; }
public abstract int RemotePort { get; }
public abstract Stream GetStream();
public Connection(Listener listener)
{
Listener = listener;
}
public virtual HttpRequest ReadRequest(HTTPServer httpServer)
{
try
{
HttpReader httpReader = new HttpReader(GetStream());
httpReader.Read();
return new HttpRequest(httpServer, httpReader, Listener.LocalEndpoint);
} catch (IOException)
{
return null;
} catch (Exception e)
{
Logging.Log(e);
return null;
}
}
public virtual void SendResponse(HttpResponse response) => SendResponse(GetStream(), response);
public static void SendResponse(Stream stream, HttpResponse response)
{
response.SetHeader("Content-Length", response.ContentStream.Length.ToString());
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();
stream.Flush();
}
}
}

View File

@ -0,0 +1,37 @@
// /**
// * File: HttpConnection.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using ln.types;
using ln.types.net;
using System.Net.Sockets;
using ln.http.listener;
namespace ln.http.connections
{
public class HttpConnection : Connection
{
public TcpClient TcpClient { get; }
public Endpoint RemoteEndpoint { get; }
public HttpConnection(Listener listener, TcpClient tcpClient)
:base(listener)
{
TcpClient = tcpClient;
RemoteEndpoint = new Endpoint(TcpClient.Client.RemoteEndPoint);
}
public override IPv6 RemoteHost => RemoteEndpoint.Address;
public override int RemotePort => RemoteEndpoint.Port;
public override Stream GetStream() => TcpClient.GetStream();
}
}

View File

@ -0,0 +1,42 @@
// /**
// * File: HttpsConnection.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using System.Security.AccessControl;
using ln.types;
using System.Net.Security;
using ln.http.listener;
using ln.types.net;
namespace ln.http.connections
{
public class HttpsConnection : Connection
{
Connection Connection { get; }
SslStream sslStream { get; }
public override IPv6 RemoteHost => Connection.RemoteHost;
public override int RemotePort => Connection.RemotePort;
public HttpsConnection(Listener listener,Connection connection,LocalCertificateSelectionCallback localCertificateSelectionCallback)
:base(listener)
{
Connection = connection;
sslStream = new SslStream(connection.GetStream(),false, null, localCertificateSelectionCallback);
}
public override HttpRequest ReadRequest(HTTPServer server)
{
throw new NotImplementedException();
}
public override Stream GetStream() => sslStream;
}
}

View File

@ -0,0 +1,55 @@
// /**
// * File: HttpListener.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.Net.Sockets;
using ln.types;
using ln.types.net;
using ln.http.connections;
namespace ln.http.listener
{
public class HttpListener : Listener
{
protected TcpListener tcpListener;
public HttpListener(int port) :this(IPv6.ANY,port){}
public HttpListener(Endpoint endpoint) : this(endpoint.Address, endpoint.Port) {}
public HttpListener(IPv4 listen, int port) : this((IPv6)listen,port){}
public HttpListener(IPv6 listen, int port)
: base(listen, port)
{
}
public override Connection Accept() => new HttpConnection(this,tcpListener.AcceptTcpClient());
public override bool IsOpen => tcpListener != null;
public override void Open()
{
tcpListener = new TcpListener(Listen, Port);
tcpListener.Start();
}
public override void Close()
{
if (tcpListener != null)
{
tcpListener.Stop();
tcpListener = null;
}
}
public override void Dispose()
{
if (IsOpen)
Close();
}
}
}

View File

@ -0,0 +1,30 @@
// /**
// * File: HttpsListener.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using ln.types;
using ln.types.net;
using ln.http.connections;
using ln.http.cert;
namespace ln.http.listener
{
public class HttpsListener : HttpListener
{
public HttpsListener(int port) : this(IPv6.ANY, port) { }
public HttpsListener(IPv4 listen, int port) : this((IPv6)listen, port) { }
public HttpsListener(IPv6 listen, int port) : base(listen, port) { }
public CertContainer CertContainer { get; set; } = new CertContainer();
public override Connection Accept()
{
return new HttpsConnection(this, base.Accept(),CertContainer.SelectCertificate);
}
}
}

View File

@ -0,0 +1,44 @@
// /**
// * File: Listener.cs
// * Author: haraldwolff
// *
// * This file and it's content is copyrighted by the Author and / or copyright holder.
// * Any use wihtout proper permission is illegal and may lead to legal actions.
// *
// *
// **/
using System;
using System.IO;
using ln.http.connections;
using ln.types.net;
namespace ln.http.listener
{
public abstract class Listener : IDisposable
{
public IPv6 Listen { get; }
public int Port { get; }
public Endpoint LocalEndpoint => new Endpoint(Listen, Port);
public abstract bool IsOpen { get; }
protected Listener(IPv6 listen, int port)
{
Listen = listen;
Port = port;
}
public virtual void AcceptMany(Action<Connection> handler)
{
while (IsOpen)
handler(Accept());
}
public abstract void Open();
public abstract void Close();
public abstract Connection Accept();
public abstract void Dispose();
}
}

View File

@ -29,6 +29,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Security" />
<Reference Include="Mono.Security" />
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs" />
@ -57,12 +58,22 @@
<Compile Include="websocket\WebSocketEventArgs.cs" />
<Compile Include="websocket\WebSocketFrame.cs" />
<Compile Include="HTTPServerConnection.cs" />
<Compile Include="connections\HttpConnection.cs" />
<Compile Include="connections\Connection.cs" />
<Compile Include="connections\HttpsConnection.cs" />
<Compile Include="cert\CertContainer.cs" />
<Compile Include="listener\HttpListener.cs" />
<Compile Include="listener\HttpsListener.cs" />
<Compile Include="listener\Listener.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="exceptions\" />
<Folder Include="session\" />
<Folder Include="client\" />
<Folder Include="websocket\" />
<Folder Include="connections\" />
<Folder Include="cert\" />
<Folder Include="listener\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ln.logging\ln.logging.csproj">

View File

@ -5,6 +5,7 @@ using ln.logging;
using ln.http.exceptions;
using System.Security.Cryptography;
using System.Text;
using ln.types;
namespace ln.http.websocket
{
@ -32,7 +33,7 @@ namespace ln.http.websocket
public delegate void WebSocketEventDelegate(WebSocket sender,WebSocketEventArgs e);
public class WebSocket
public abstract class WebSocket
{
public HTTPServer HTTPServer => HttpRequest.HTTPServer;
public HttpRequest HttpRequest { get; }
@ -40,10 +41,6 @@ namespace ln.http.websocket
public WebSocketState State { get; private set; } = WebSocketState.HANDSHAKE;
public event WebSocketEventDelegate WebSocketEvent;
Thread receiverThread;
public WebSocket(HttpRequest httpRequest)
{
HttpRequest = httpRequest;
@ -72,22 +69,11 @@ namespace ln.http.websocket
);
HTTPServerConnection.SendResponse(Stream, httpResponse);
HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close();
State = WebSocketState.OPEN;
}
public bool IsAlive => ((receiverThread != null) && receiverThread.IsAlive);
public void Start()
{
if ((receiverThread == null) || !receiverThread.IsAlive)
{
receiverThread = new Thread(() => Run());
receiverThread.Start();
}
}
public bool IsAlive => false;
public void Close()
{
@ -122,8 +108,10 @@ namespace ln.http.websocket
switch (webSocketFrame.Opcode)
{
case WebSocketOpcode.TEXT:
Received(Encoding.UTF8.GetString(webSocketFrame.ApplicationData));
break;
case WebSocketOpcode.BINARY:
WebSocketEvent(this, new WebSocketEventArgs(webSocketFrame));
Received(webSocketFrame.ApplicationData);
break;
case WebSocketOpcode.CLOSE:
if (State == WebSocketState.OPEN)
@ -159,8 +147,17 @@ namespace ln.http.websocket
} finally
{
}
}
receiverThread = null;
public virtual bool Received(string textMessage)
{
Logging.Log(LogLevel.WARNING, "WebSocket received unexpected text message:\n{0}", textMessage);
return false;
}
public virtual bool Received(byte[] binaryMessage)
{
Logging.Log(LogLevel.WARNING, "WebSocket received unexpected binary message:\n{0}",binaryMessage.ToHexString());
return false;
}
public void Send(WebSocketFrame frame)