diff --git a/HTTPServer.cs b/HTTPServer.cs index d717d25..96ae30f 100644 --- a/HTTPServer.cs +++ b/HTTPServer.cs @@ -10,6 +10,7 @@ using ln.http.cert; using System.Globalization; using System.Net.Sockets; using ln.http.session; +using ln.http.exceptions; namespace ln.http @@ -20,30 +21,36 @@ namespace ln.http public static int defaultPort = 8080; public static bool exclusivePortListener = false; - public HttpApplication DefaultApplication { get; set; } - public SessionCache SessionCache { get; set; } + public HttpRouter Router { get; set; } public bool IsRunning => !shutdown && (threadPool.CurrentPoolSize > 0); - public Func CreateServerConnection { get; set; } - public Logger Logger { get; set; } + bool shutdown = false; List listeners = new List(); public Listener[] Listeners => listeners.ToArray(); - Dictionary applications = new Dictionary(); - DynamicPool threadPool; public DynamicPool ThreadPool => threadPool; public HTTPServer() { Logger = Logger.Default; - - SessionCache = new SessionCache(); 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) { @@ -76,22 +83,6 @@ namespace ln.http 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() { threadPool.Start(); @@ -124,14 +115,17 @@ namespace ln.http if (httpRequest == null) break; - httpRequest.MakeCurrent(); - - HttpApplication application = GetHttpApplication(new URI(httpRequest.BaseURI.ToString())); - - application.Authenticate(httpRequest); - application.Authorize(httpRequest); - - HttpResponse response = application.GetResponse(httpRequest); + HttpResponse response; + try + { + response = Router.Route(httpRequest); + } catch (HttpException httpExc) + { + response = new HttpResponse(httpRequest); + response.StatusCode = httpExc.StatusCode; + response.StatusMessage = httpExc.Message; + response.ContentWriter.WriteLine(httpExc.Message); + } if (response != null) { @@ -154,6 +148,7 @@ namespace ln.http HttpResponse httpResponse = new HttpResponse(httpRequest); httpResponse.StatusCode = 500; httpResponse.ContentWriter.WriteLine("500 Internal Server Error"); + httpResponse.ContentWriter.Flush(); connection.SendResponse(httpResponse); } diff --git a/HTTPServerConnection.cs b/HTTPServerConnection.cs index 420f02b..93e63ac 100644 --- a/HTTPServerConnection.cs +++ b/HTTPServerConnection.cs @@ -10,148 +10,148 @@ using System.Collections.Generic; using System.Linq; namespace ln.http { - public delegate void HTTPServerConnectionEvent(HTTPServerConnection connection); + //public delegate void HTTPServerConnectionEvent(HTTPServerConnection connection); - public class HTTPServerConnection : PoolJob - { - public static ThreadLocal Current { get; } = new ThreadLocal(); - static HashSet currentConnections = new HashSet(); - public static HTTPServerConnection[] CurrentConnections => currentConnections.ToArray(); + //public class HTTPServerConnection : PoolJob + //{ + // public static ThreadLocal Current { get; } = new ThreadLocal(); + // static HashSet currentConnections = new HashSet(); + // public static HTTPServerConnection[] CurrentConnections => currentConnections.ToArray(); - public HTTPServer HTTPServer { get; } - public TcpClient TcpClient { get; } + // public HTTPServer HTTPServer { 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 Interpreted { get; set; } - public DateTime Finished { get; set; } + // public DateTime Created { get; } + // public DateTime Interpreted { get; set; } + // public DateTime Finished { get; set; } - public HTTPServerConnection(HTTPServer httpServer,TcpClient tcpClient) - { - HTTPServer = httpServer; - TcpClient = tcpClient; - Created = DateTime.Now; - } + // public HTTPServerConnection(HTTPServer httpServer,TcpClient tcpClient) + // { + // HTTPServer = httpServer; + // TcpClient = tcpClient; + // 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() - { - if (AbortRequested != null) - AbortRequested(this); - } + // public virtual void Abort() + // { + // if (AbortRequested != null) + // AbortRequested(this); + // } - public override void RunJob() - { - HTTPServerConnection saveCurrent = Current.Value; - Current.Value = this; - lock (currentConnections) - currentConnections.Add(this); + // public override void RunJob() + // { + // HTTPServerConnection saveCurrent = Current.Value; + // Current.Value = this; + // lock (currentConnections) + // currentConnections.Add(this); - try - { - setState("reading http request"); + // try + // { + // setState("reading http request"); - HttpReader httpReader = new HttpReader(TcpClient.GetStream()); - httpReader.Read(); + // HttpReader httpReader = new HttpReader(TcpClient.GetStream()); + // httpReader.Read(); - if (!httpReader.Valid) - return; + // if (!httpReader.Valid) + // return; - HttpResponse response = null; + // HttpResponse response = null; - using (CurrentRequest = new HttpRequest(this.HTTPServer,httpReader, (IPEndPoint)TcpClient.Client.LocalEndPoint)) - { - Interpreted = DateTime.Now; + // using (CurrentRequest = new HttpRequest(this.HTTPServer,httpReader, (IPEndPoint)TcpClient.Client.LocalEndPoint)) + // { + // Interpreted = DateTime.Now; - try - { - HttpApplication application = HTTPServer.GetHttpApplication(new URI(CurrentRequest.BaseURI.ToString())); + // try + // { + // HttpApplication application = HTTPServer.GetHttpApplication(new URI(CurrentRequest.BaseURI.ToString())); - application.Authenticate(CurrentRequest); - application.Authorize(CurrentRequest); + // application.Authenticate(CurrentRequest); + // application.Authorize(CurrentRequest); - setState("handling http request"); + // setState("handling http request"); - response = GetResponse(CurrentRequest, application); - } - catch (Exception e) - { - setState("handling exception"); + // 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); - } + // response = new HttpResponse(CurrentRequest, "text/plain"); + // response.StatusCode = 500; + // response.ContentWriter.WriteLine("Exception caught: {0}", e); + // } - setState("sending response"); + // 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; + // 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()); - } + // response.SetHeader("content-length", cstream.Length.ToString()); + // } - if (CurrentRequest.Session != null) - HTTPServer?.SessionCache?.ApplySessionID(response, CurrentRequest.Session); + // if (CurrentRequest.Session != null) + // HTTPServer?.SessionCache?.ApplySessionID(response, CurrentRequest.Session); - response.AddCookie("LN_SEEN", DateTime.Now.ToString()); + // response.AddCookie("LN_SEEN", DateTime.Now.ToString()); - SendResponse(TcpClient.GetStream(), response); - TcpClient.Close(); + // SendResponse(TcpClient.GetStream(), response); + // TcpClient.Close(); - Finished = DateTime.Now; + // Finished = DateTime.Now; - HTTPServer.Log(Created, (Finished - Created).TotalMilliseconds, CurrentRequest, response); - } - } - } - finally - { - Current.Value = saveCurrent; - lock (currentConnections) - currentConnections.Remove(this); - } - } + // HTTPServer.Log(Created, (Finished - Created).TotalMilliseconds, CurrentRequest, response); + // } + // } + // } + // finally + // { + // Current.Value = saveCurrent; + // lock (currentConnections) + // currentConnections.Remove(this); + // } + // } - public static void SendResponse(Stream stream, HttpResponse response) - { - StreamWriter streamWriter = new StreamWriter(stream); - streamWriter.NewLine = "\r\n"; + // 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)); - } + // 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()); - } + // foreach (HttpCookie httpCookie in response.Cookies) + // { + // streamWriter.WriteLine("Set-Cookie: {0}", httpCookie.ToString()); + // } - streamWriter.WriteLine(); - streamWriter.Flush(); + // streamWriter.WriteLine(); + // streamWriter.Flush(); - response.ContentStream.CopyTo(stream); - response.ContentStream.Close(); - response.ContentStream.Dispose(); + // response.ContentStream.CopyTo(stream); + // response.ContentStream.Close(); + // response.ContentStream.Dispose(); - streamWriter.Flush(); - } + // streamWriter.Flush(); + // } - } + //} } diff --git a/HttpApplication.cs b/HttpApplication.cs deleted file mode 100644 index ca8ca07..0000000 --- a/HttpApplication.cs +++ /dev/null @@ -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) { } - - } -} diff --git a/HttpRequest.cs b/HttpRequest.cs index 7efdb4f..20b4742 100644 --- a/HttpRequest.cs +++ b/HttpRequest.cs @@ -34,7 +34,7 @@ namespace ln.http public QueryStringParameters Query { get; private set; } - public Session Session => HTTPServer.SessionCache.GetSession(this); + public Session Session { get; set; } public HttpUser CurrentUser => Session.CurrentUser; diff --git a/HttpRouter.cs b/HttpRouter.cs new file mode 100644 index 0000000..82a16a1 --- /dev/null +++ b/HttpRouter.cs @@ -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; + } + } + + } +} diff --git a/IHTTPResource.cs b/IHTTPResource.cs new file mode 100644 index 0000000..b48c503 --- /dev/null +++ b/IHTTPResource.cs @@ -0,0 +1,8 @@ +using System; +namespace ln.http +{ + public interface IHTTPResource + { + HttpResponse GetResponse(HttpRequest httpRequest); + } +} diff --git a/connections/Connection.cs b/connections/Connection.cs index 93369fe..09d3066 100644 --- a/connections/Connection.cs +++ b/connections/Connection.cs @@ -15,9 +15,9 @@ using ln.logging; using ln.http.listener; 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 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 static void SendResponse(Stream stream, HttpResponse response) @@ -70,6 +77,7 @@ namespace ln.http.connections streamWriter.WriteLine(); streamWriter.Flush(); + response.ContentStream.Position = 0; response.ContentStream.CopyTo(stream); response.ContentStream.Close(); response.ContentStream.Dispose(); diff --git a/connections/HttpConnection.cs b/connections/HttpConnection.cs index 1d7d421..21b756f 100644 --- a/connections/HttpConnection.cs +++ b/connections/HttpConnection.cs @@ -33,5 +33,10 @@ namespace ln.http.connections public override Stream GetStream() => TcpClient.GetStream(); + public override void Close() + { + TcpClient.Close(); + } + } } diff --git a/connections/HttpsConnection.cs b/connections/HttpsConnection.cs index 9695de6..daf1c6c 100644 --- a/connections/HttpsConnection.cs +++ b/connections/HttpsConnection.cs @@ -38,5 +38,10 @@ namespace ln.http.connections } public override Stream GetStream() => sslStream; + + public override void Close() + { + sslStream.Close(); + } } } diff --git a/ln.http.csproj b/ln.http.csproj index 65a7cf8..777f943 100644 --- a/ln.http.csproj +++ b/ln.http.csproj @@ -42,7 +42,6 @@ - @@ -65,6 +64,9 @@ + + + @@ -74,6 +76,7 @@ + diff --git a/router/VirtualHostRouter.cs b/router/VirtualHostRouter.cs new file mode 100644 index 0000000..190e434 --- /dev/null +++ b/router/VirtualHostRouter.cs @@ -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 virtualHosts = new Dictionary(); + + public VirtualHostRouter() + { + } + public VirtualHostRouter(HttpRouter defaultRoute) + { + DefaultRoute = defaultRoute; + } + public VirtualHostRouter(IEnumerable> routes) + { + foreach (KeyValuePair route in routes) + virtualHosts.Add(route.Key, route.Value); + } + public VirtualHostRouter(HttpRouter defaultRoute,IEnumerable> 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)); + } + } +} diff --git a/websocket/WebSocket.cs b/websocket/WebSocket.cs index ed8941a..f22182a 100644 --- a/websocket/WebSocket.cs +++ b/websocket/WebSocket.cs @@ -6,6 +6,7 @@ using ln.http.exceptions; using System.Security.Cryptography; using System.Text; using ln.types; +using ln.http.connections; namespace ln.http.websocket { @@ -68,7 +69,7 @@ namespace ln.http.websocket Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey))) ); - HTTPServerConnection.SendResponse(Stream, httpResponse); + Connection.SendResponse(Stream, httpResponse); //HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close(); State = WebSocketState.OPEN; }