Implement HttpRouter

master
Harald Wolff 2019-11-26 12:20:50 +01:00
parent f2d95bc93d
commit 92b83af7f0
12 changed files with 249 additions and 164 deletions

View File

@ -10,6 +10,7 @@ using ln.http.cert;
using System.Globalization; using System.Globalization;
using System.Net.Sockets; using System.Net.Sockets;
using ln.http.session; using ln.http.session;
using ln.http.exceptions;
namespace ln.http namespace ln.http
@ -20,30 +21,36 @@ namespace ln.http
public static int defaultPort = 8080; public static int defaultPort = 8080;
public static bool exclusivePortListener = false; public static bool exclusivePortListener = false;
public HttpApplication DefaultApplication { get; set; } public HttpRouter Router { get; set; }
public SessionCache SessionCache { get; set; }
public bool IsRunning => !shutdown && (threadPool.CurrentPoolSize > 0); public bool IsRunning => !shutdown && (threadPool.CurrentPoolSize > 0);
public Func<HTTPServer,HTTPServerConnection> CreateServerConnection { get; set; }
public Logger Logger { get; set; } public Logger Logger { get; set; }
bool shutdown = false; bool shutdown = false;
List<Listener> listeners = new List<Listener>(); List<Listener> listeners = new List<Listener>();
public Listener[] Listeners => listeners.ToArray(); public Listener[] Listeners => listeners.ToArray();
Dictionary<URI, HttpApplication> applications = new Dictionary<URI, HttpApplication>();
DynamicPool threadPool; DynamicPool threadPool;
public DynamicPool ThreadPool => threadPool; public DynamicPool ThreadPool => threadPool;
public HTTPServer() public HTTPServer()
{ {
Logger = Logger.Default; Logger = Logger.Default;
SessionCache = new SessionCache();
threadPool = new DynamicPool(1024); threadPool = new DynamicPool(1024);
} }
public HTTPServer(HttpRouter router)
: this()
{
Router = router;
}
public HTTPServer(Listener listener, HttpRouter router)
: this(router)
{
AddListener(listener);
}
public HTTPServer(Endpoint endpoint, HttpRouter router)
: this(new HttpListener(endpoint), router) { }
public void AddListener(Listener listener) public void AddListener(Listener listener)
{ {
@ -76,22 +83,6 @@ namespace ln.http
AddListener(new HttpListener(endpoint)); AddListener(new HttpListener(endpoint));
} }
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() public void Start()
{ {
threadPool.Start(); threadPool.Start();
@ -124,14 +115,17 @@ namespace ln.http
if (httpRequest == null) if (httpRequest == null)
break; break;
httpRequest.MakeCurrent(); HttpResponse response;
try
HttpApplication application = GetHttpApplication(new URI(httpRequest.BaseURI.ToString())); {
response = Router.Route(httpRequest);
application.Authenticate(httpRequest); } catch (HttpException httpExc)
application.Authorize(httpRequest); {
response = new HttpResponse(httpRequest);
HttpResponse response = application.GetResponse(httpRequest); response.StatusCode = httpExc.StatusCode;
response.StatusMessage = httpExc.Message;
response.ContentWriter.WriteLine(httpExc.Message);
}
if (response != null) if (response != null)
{ {
@ -154,6 +148,7 @@ namespace ln.http
HttpResponse httpResponse = new HttpResponse(httpRequest); HttpResponse httpResponse = new HttpResponse(httpRequest);
httpResponse.StatusCode = 500; httpResponse.StatusCode = 500;
httpResponse.ContentWriter.WriteLine("500 Internal Server Error"); httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
httpResponse.ContentWriter.Flush();
connection.SendResponse(httpResponse); connection.SendResponse(httpResponse);
} }

View File

@ -10,148 +10,148 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace ln.http namespace ln.http
{ {
public delegate void HTTPServerConnectionEvent(HTTPServerConnection connection); //public delegate void HTTPServerConnectionEvent(HTTPServerConnection connection);
public class HTTPServerConnection : PoolJob //public class HTTPServerConnection : PoolJob
{ //{
public static ThreadLocal<HTTPServerConnection> Current { get; } = new ThreadLocal<HTTPServerConnection>(); // public static ThreadLocal<HTTPServerConnection> Current { get; } = new ThreadLocal<HTTPServerConnection>();
static HashSet<HTTPServerConnection> currentConnections = new HashSet<HTTPServerConnection>(); // static HashSet<HTTPServerConnection> currentConnections = new HashSet<HTTPServerConnection>();
public static HTTPServerConnection[] CurrentConnections => currentConnections.ToArray(); // public static HTTPServerConnection[] CurrentConnections => currentConnections.ToArray();
public HTTPServer HTTPServer { get; } // public HTTPServer HTTPServer { get; }
public TcpClient TcpClient { get; } // public TcpClient TcpClient { get; }
public HttpRequest CurrentRequest { get; protected set; } // public HttpRequest CurrentRequest { get; protected set; }
public event HTTPServerConnectionEvent AbortRequested; // public event HTTPServerConnectionEvent AbortRequested;
public DateTime Created { get; } // public DateTime Created { get; }
public DateTime Interpreted { get; set; } // public DateTime Interpreted { get; set; }
public DateTime Finished { get; set; } // public DateTime Finished { get; set; }
public HTTPServerConnection(HTTPServer httpServer,TcpClient tcpClient) // public HTTPServerConnection(HTTPServer httpServer,TcpClient tcpClient)
{ // {
HTTPServer = httpServer; // HTTPServer = httpServer;
TcpClient = tcpClient; // TcpClient = tcpClient;
Created = DateTime.Now; // Created = DateTime.Now;
} // }
public virtual HttpResponse GetResponse(HttpRequest httpRequest,HttpApplication httpApplication) => httpApplication.GetResponse(httpRequest); // public virtual HttpResponse GetResponse(HttpRequest httpRequest,HttpApplication httpApplication) => httpApplication.GetResponse(httpRequest);
public virtual void Abort() // public virtual void Abort()
{ // {
if (AbortRequested != null) // if (AbortRequested != null)
AbortRequested(this); // AbortRequested(this);
} // }
public override void RunJob() // public override void RunJob()
{ // {
HTTPServerConnection saveCurrent = Current.Value; // HTTPServerConnection saveCurrent = Current.Value;
Current.Value = this; // Current.Value = this;
lock (currentConnections) // lock (currentConnections)
currentConnections.Add(this); // currentConnections.Add(this);
try // try
{ // {
setState("reading http request"); // setState("reading http request");
HttpReader httpReader = new HttpReader(TcpClient.GetStream()); // HttpReader httpReader = new HttpReader(TcpClient.GetStream());
httpReader.Read(); // httpReader.Read();
if (!httpReader.Valid) // if (!httpReader.Valid)
return; // return;
HttpResponse response = null; // HttpResponse response = null;
using (CurrentRequest = new HttpRequest(this.HTTPServer,httpReader, (IPEndPoint)TcpClient.Client.LocalEndPoint)) // using (CurrentRequest = new HttpRequest(this.HTTPServer,httpReader, (IPEndPoint)TcpClient.Client.LocalEndPoint))
{ // {
Interpreted = DateTime.Now; // Interpreted = DateTime.Now;
try // try
{ // {
HttpApplication application = HTTPServer.GetHttpApplication(new URI(CurrentRequest.BaseURI.ToString())); // HttpApplication application = HTTPServer.GetHttpApplication(new URI(CurrentRequest.BaseURI.ToString()));
application.Authenticate(CurrentRequest); // application.Authenticate(CurrentRequest);
application.Authorize(CurrentRequest); // application.Authorize(CurrentRequest);
setState("handling http request"); // setState("handling http request");
response = GetResponse(CurrentRequest, application); // response = GetResponse(CurrentRequest, application);
} // }
catch (Exception e) // catch (Exception e)
{ // {
setState("handling exception"); // setState("handling exception");
response = new HttpResponse(CurrentRequest, "text/plain"); // response = new HttpResponse(CurrentRequest, "text/plain");
response.StatusCode = 500; // response.StatusCode = 500;
response.ContentWriter.WriteLine("Exception caught: {0}", e); // response.ContentWriter.WriteLine("Exception caught: {0}", e);
} // }
setState("sending response"); // setState("sending response");
if (response == null) // if (response == null)
{ // {
Logging.Log(LogLevel.DEBUG, "Request {0} returned no Response", CurrentRequest); // Logging.Log(LogLevel.DEBUG, "Request {0} returned no Response", CurrentRequest);
} // }
else // else
{ // {
if (!response.HasCustomContentStream) // if (!response.HasCustomContentStream)
{ // {
response.ContentWriter.Flush(); // response.ContentWriter.Flush();
MemoryStream cstream = (MemoryStream)response.ContentStream; // MemoryStream cstream = (MemoryStream)response.ContentStream;
cstream.Position = 0; // cstream.Position = 0;
response.SetHeader("content-length", cstream.Length.ToString()); // response.SetHeader("content-length", cstream.Length.ToString());
} // }
if (CurrentRequest.Session != null) // if (CurrentRequest.Session != null)
HTTPServer?.SessionCache?.ApplySessionID(response, CurrentRequest.Session); // HTTPServer?.SessionCache?.ApplySessionID(response, CurrentRequest.Session);
response.AddCookie("LN_SEEN", DateTime.Now.ToString()); // response.AddCookie("LN_SEEN", DateTime.Now.ToString());
SendResponse(TcpClient.GetStream(), response); // SendResponse(TcpClient.GetStream(), response);
TcpClient.Close(); // TcpClient.Close();
Finished = DateTime.Now; // Finished = DateTime.Now;
HTTPServer.Log(Created, (Finished - Created).TotalMilliseconds, CurrentRequest, response); // HTTPServer.Log(Created, (Finished - Created).TotalMilliseconds, CurrentRequest, response);
} // }
} // }
} // }
finally // finally
{ // {
Current.Value = saveCurrent; // Current.Value = saveCurrent;
lock (currentConnections) // lock (currentConnections)
currentConnections.Remove(this); // currentConnections.Remove(this);
} // }
} // }
public static void SendResponse(Stream stream, HttpResponse response) // public static void SendResponse(Stream stream, HttpResponse response)
{ // {
StreamWriter streamWriter = new StreamWriter(stream); // StreamWriter streamWriter = new StreamWriter(stream);
streamWriter.NewLine = "\r\n"; // streamWriter.NewLine = "\r\n";
streamWriter.WriteLine("{0} {1} {2}", response.HttpRequest.Protocol, response.StatusCode, response.StatusMessage); // streamWriter.WriteLine("{0} {1} {2}", response.HttpRequest.Protocol, response.StatusCode, response.StatusMessage);
foreach (String headerName in response.GetHeaderNames()) // foreach (String headerName in response.GetHeaderNames())
{ // {
streamWriter.WriteLine("{0}: {1}", headerName, response.GetHeader(headerName)); // streamWriter.WriteLine("{0}: {1}", headerName, response.GetHeader(headerName));
} // }
foreach (HttpCookie httpCookie in response.Cookies) // foreach (HttpCookie httpCookie in response.Cookies)
{ // {
streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString()); // streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString());
} // }
streamWriter.WriteLine(); // streamWriter.WriteLine();
streamWriter.Flush(); // streamWriter.Flush();
response.ContentStream.CopyTo(stream); // response.ContentStream.CopyTo(stream);
response.ContentStream.Close(); // response.ContentStream.Close();
response.ContentStream.Dispose(); // response.ContentStream.Dispose();
streamWriter.Flush(); // streamWriter.Flush();
} // }
} //}
} }

View File

@ -1,18 +0,0 @@
using System;
namespace ln.http
{
public abstract class HttpApplication
{
public AuthenticationProvider AuthenticationProvider { get; protected set; }
public HttpApplication()
{
}
public abstract HttpResponse GetResponse(HttpRequest httpRequest);
public virtual void Authenticate(HttpRequest httpRequest) { }
public virtual void Authorize(HttpRequest httpRequest) { }
}
}

View File

@ -34,7 +34,7 @@ namespace ln.http
public QueryStringParameters Query { get; private set; } public QueryStringParameters Query { get; private set; }
public Session Session => HTTPServer.SessionCache.GetSession(this); public Session Session { get; set; }
public HttpUser CurrentUser => Session.CurrentUser; public HttpUser CurrentUser => Session.CurrentUser;

35
HttpRouter.cs 100644
View File

@ -0,0 +1,35 @@
using System;
using ln.logging;
namespace ln.http
{
public abstract class HttpRouter
{
public HttpRouter()
{
}
public abstract IHTTPResource FindResource(HttpRequest httpRequest);
public virtual HttpResponse Route(HttpRequest httpRequest)
{
try
{
IHTTPResource resource = FindResource(httpRequest);
return resource.GetResponse(httpRequest);
} catch (Exception e)
{
Logging.Log(e);
if (httpRequest != null)
{
HttpResponse httpResponse = new HttpResponse(httpRequest);
httpResponse.StatusCode = 500;
httpResponse.ContentWriter.WriteLine("500 Internal Server Error");
return httpResponse;
}
return null;
}
}
}
}

8
IHTTPResource.cs 100644
View File

@ -0,0 +1,8 @@
using System;
namespace ln.http
{
public interface IHTTPResource
{
HttpResponse GetResponse(HttpRequest httpRequest);
}
}

View File

@ -15,9 +15,9 @@ using ln.logging;
using ln.http.listener; using ln.http.listener;
namespace ln.http.connections namespace ln.http.connections
{ {
public abstract class Connection public abstract class Connection : IDisposable
{ {
public Listener Listener { get; } public Listener Listener { get; private set; }
public abstract IPv6 RemoteHost { get; } public abstract IPv6 RemoteHost { get; }
public abstract int RemotePort { get; } public abstract int RemotePort { get; }
@ -47,6 +47,13 @@ namespace ln.http.connections
} }
} }
public abstract void Close();
public virtual void Dispose()
{
Close();
Listener = null;
}
public virtual void SendResponse(HttpResponse response) => SendResponse(GetStream(), response); public virtual void SendResponse(HttpResponse response) => SendResponse(GetStream(), response);
public static void SendResponse(Stream stream, HttpResponse response) public static void SendResponse(Stream stream, HttpResponse response)
@ -70,6 +77,7 @@ namespace ln.http.connections
streamWriter.WriteLine(); streamWriter.WriteLine();
streamWriter.Flush(); streamWriter.Flush();
response.ContentStream.Position = 0;
response.ContentStream.CopyTo(stream); response.ContentStream.CopyTo(stream);
response.ContentStream.Close(); response.ContentStream.Close();
response.ContentStream.Dispose(); response.ContentStream.Dispose();

View File

@ -33,5 +33,10 @@ namespace ln.http.connections
public override Stream GetStream() => TcpClient.GetStream(); public override Stream GetStream() => TcpClient.GetStream();
public override void Close()
{
TcpClient.Close();
}
} }
} }

View File

@ -38,5 +38,10 @@ namespace ln.http.connections
} }
public override Stream GetStream() => sslStream; public override Stream GetStream() => sslStream;
public override void Close()
{
sslStream.Close();
}
} }
} }

View File

@ -42,7 +42,6 @@
<Compile Include="HttpResponse.cs" /> <Compile Include="HttpResponse.cs" />
<Compile Include="HttpStatusCodes.cs" /> <Compile Include="HttpStatusCodes.cs" />
<Compile Include="QueryStringParameters.cs" /> <Compile Include="QueryStringParameters.cs" />
<Compile Include="HttpApplication.cs" />
<Compile Include="HttpCookie.cs" /> <Compile Include="HttpCookie.cs" />
<Compile Include="session\Session.cs" /> <Compile Include="session\Session.cs" />
<Compile Include="session\SessionCache.cs" /> <Compile Include="session\SessionCache.cs" />
@ -65,6 +64,9 @@
<Compile Include="listener\HttpListener.cs" /> <Compile Include="listener\HttpListener.cs" />
<Compile Include="listener\HttpsListener.cs" /> <Compile Include="listener\HttpsListener.cs" />
<Compile Include="listener\Listener.cs" /> <Compile Include="listener\Listener.cs" />
<Compile Include="HttpRouter.cs" />
<Compile Include="IHTTPResource.cs" />
<Compile Include="router\VirtualHostRouter.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="exceptions\" /> <Folder Include="exceptions\" />
@ -74,6 +76,7 @@
<Folder Include="connections\" /> <Folder Include="connections\" />
<Folder Include="cert\" /> <Folder Include="cert\" />
<Folder Include="listener\" /> <Folder Include="listener\" />
<Folder Include="router\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\ln.logging\ln.logging.csproj"> <ProjectReference Include="..\ln.logging\ln.logging.csproj">

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using ln.http.exceptions;
using ln.types;
namespace ln.http.router
{
public class VirtualHostRouter : HttpRouter
{
public HttpRouter DefaultRoute { get; set; }
Dictionary<string, HttpRouter> virtualHosts = new Dictionary<string, HttpRouter>();
public VirtualHostRouter()
{
}
public VirtualHostRouter(HttpRouter defaultRoute)
{
DefaultRoute = defaultRoute;
}
public VirtualHostRouter(IEnumerable<KeyValuePair<string, HttpRouter>> routes)
{
foreach (KeyValuePair<string, HttpRouter> route in routes)
virtualHosts.Add(route.Key, route.Value);
}
public VirtualHostRouter(HttpRouter defaultRoute,IEnumerable<KeyValuePair<string, HttpRouter>> routes)
:this(routes)
{
DefaultRoute = defaultRoute;
}
public VirtualHostRouter(VirtualHostRouter source)
: this(source.virtualHosts) { }
public override IHTTPResource FindResource(HttpRequest httpRequest)
{
if (virtualHosts.TryGetValue(httpRequest.Hostname, out HttpRouter virtualHost))
return virtualHost.FindResource(httpRequest);
if (DefaultRoute != null)
return DefaultRoute.FindResource(httpRequest);
throw new HttpException(410, String.Format("Gone. Hostname {0} not found on this server.",httpRequest.Hostname));
}
}
}

View File

@ -6,6 +6,7 @@ using ln.http.exceptions;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using ln.types; using ln.types;
using ln.http.connections;
namespace ln.http.websocket namespace ln.http.websocket
{ {
@ -68,7 +69,7 @@ namespace ln.http.websocket
Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey))) Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey)))
); );
HTTPServerConnection.SendResponse(Stream, httpResponse); Connection.SendResponse(Stream, httpResponse);
//HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close(); //HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close();
State = WebSocketState.OPEN; State = WebSocketState.OPEN;
} }