diff --git a/ln.http/HTTPServer.cs b/ln.http/HTTPServer.cs index 57cf82b..dd43787 100644 --- a/ln.http/HTTPServer.cs +++ b/ln.http/HTTPServer.cs @@ -10,7 +10,7 @@ using ln.http.exceptions; using System.Threading; using ln.type; using ln.http.router; - +using System.IO; namespace ln.http { @@ -141,6 +141,7 @@ namespace ln.http do { httpRequest = connection.ReadRequest(this); + if (httpRequest == null) break; @@ -151,23 +152,16 @@ namespace ln.http } catch (HttpException httpExc) { - response = new HttpResponse(httpRequest); - response.StatusCode = httpExc.StatusCode; - response.StatusMessage = httpExc.Message; - response.ContentWriter.WriteLine(httpExc.Message); + response = new HttpResponse((HttpStatusCode)httpExc.StatusCode).Content(httpExc.Message); } - if (response != null) - { - keepAlive = httpRequest.GetRequestHeader("connection", "keep-alive").Equals("keep-alive") && response.GetHeader("connection", "keep-alive").Equals("keep-alive"); - response.SetHeader("connection", keepAlive ? "keep-alive" : "close"); + if (response == null) + response = HttpResponse.NotFound().Content(String.Format("The URI {0} could not be found on this server.", httpRequest.URI)); - connection.SendResponse(response); - } - else - { - keepAlive = false; - } + keepAlive = httpRequest.GetRequestHeader("connection", "keep-alive").Equals("keep-alive") && response.GetHeader("connection", "keep-alive").Equals("keep-alive"); + response.SetHeader("connection", keepAlive ? "keep-alive" : "close"); + + SendResponse(connection.GetStream(), httpRequest, response); response?.ContentStream?.Dispose(); @@ -176,17 +170,16 @@ namespace ln.http catch (Exception e) { Logging.Log(e); - if (httpRequest != null) - { - HttpResponse httpResponse = new HttpResponse(httpRequest); - httpResponse.StatusCode = 500; - httpResponse.ContentWriter.WriteLine("500 Internal Server Error"); - httpResponse.ContentWriter.Flush(); - connection.SendResponse(httpResponse); - } + SendResponse( + connection.GetStream(), + httpRequest, + HttpResponse + .InternalServerError() + .Content(e) + ); } - + HttpRequest.ClearCurrent(); connection.GetStream().Close(); } finally @@ -196,13 +189,36 @@ namespace ln.http } } - public void Log(DateTime startTime,double duration,HttpRequest httpRequest,HttpResponse httpResponse) + public static void SendResponse(Stream stream, HttpRequest request, HttpResponse response) { - Logger.Log(LogLevel.INFO, "{0} {1} {2} {3}",startTime.ToString("yyyyMMdd-HH:mm:ss"),duration.ToString(CultureInfo.InvariantCulture),httpRequest.Hostname,httpRequest.RequestURL); + request.FinishRequest(); + response.SetHeader("Content-Length", response.ContentStream.Length.ToString()); + + StreamWriter streamWriter = new StreamWriter(stream); + streamWriter.NewLine = "\r\n"; + + streamWriter.WriteLine("{0} {1} {2}", request.Protocol, (int)response.HttpStatusCode, response.HttpStatusCode.ToString()); + 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.Position = 0; + response.ContentStream.CopyTo(stream); + response.ContentStream.Close(); + response.ContentStream.Dispose(); + + stream.Flush(); } - - public static void StartSimpleServer(string[] arguments) { ArgumentContainer argumentContainer = new ArgumentContainer(new Argument[] diff --git a/ln.http/HttpResponse.cs b/ln.http/HttpResponse.cs index afc0aa1..0cea430 100644 --- a/ln.http/HttpResponse.cs +++ b/ln.http/HttpResponse.cs @@ -7,44 +7,54 @@ namespace ln.http { public class HttpResponse { - public HttpRequest HttpRequest { get; } + //public HttpRequest HttpRequest { get; } public Stream ContentStream { get; private set; } public TextWriter ContentWriter { get; private set; } public bool HasCustomContentStream { get; private set; } - int statusCode; - string statusMessage; Dictionary> headers = new Dictionary>(); List cookies = new List(); - public HttpResponse(HttpRequest httpRequest) + + public HttpResponse() : this(HttpStatusCode.OK) { - HttpRequest = httpRequest; + } + public HttpResponse(HttpStatusCode statusCode) + { + HttpStatusCode = statusCode; ContentStream = new MemoryStream(); ContentWriter = new StreamWriter(ContentStream); - StatusCode = 200; SetHeader("content-type", "text/html"); } - public HttpResponse(HttpRequest httpRequest,string contentType) - :this(httpRequest) + + [Obsolete] + public HttpResponse(HttpRequest httpRequest) : this() { } + [Obsolete] + public HttpResponse(HttpRequest httpRequest,string contentType) : this(contentType) { } + public HttpResponse(string contentType) + :this() { SetHeader("content-type", contentType); } - public HttpResponse(HttpRequest httpRequest, Stream contentStream) + [Obsolete] + public HttpResponse(HttpRequest httpRequest, Stream contentStream) : this(contentStream) { } + public HttpResponse(Stream contentStream) { - HttpRequest = httpRequest; ContentStream = contentStream; ContentWriter = null; HasCustomContentStream = true; - StatusCode = 200; + HttpStatusCode = HttpStatusCode.OK; SetHeader("content-type", "text/html"); } - public HttpResponse(HttpRequest httpRequest, Stream contentStream,string contentType) - :this(httpRequest,contentStream) + + [Obsolete] + public HttpResponse(HttpRequest httpRequest, Stream contentStream,string contentType) : this(contentStream, contentType){ } + public HttpResponse(Stream contentStream,string contentType) + :this(contentStream) { SetHeader("content-type", contentType); } @@ -80,56 +90,80 @@ namespace ln.http headers[name].Add(value); } - public void RemoveHeader(String name) - { - headers.Remove(name.ToUpper()); - } - - public bool ContainsHeader(string headerName) - { - return headers.ContainsKey(headerName.ToUpper()); - } + public void RemoveHeader(String name) => headers.Remove(name.ToUpper()); + public bool ContainsHeader(string headerName) => headers.ContainsKey(headerName.ToUpper()); public void AddCookie(string name,string value) { AddCookie(new HttpCookie(name, value)); } - public void AddCookie(HttpCookie httpCookie) - { - cookies.Add(httpCookie); - } - public void RemoveCookie(HttpCookie httpCookie) - { - cookies.Remove(httpCookie); - } + public void AddCookie(HttpCookie httpCookie) => cookies.Add(httpCookie); + public void RemoveCookie(HttpCookie httpCookie) => cookies.Remove(httpCookie); public HttpCookie[] Cookies => cookies.ToArray(); - - + public HttpStatusCode HttpStatusCode { get; set; } + + [Obsolete] public int StatusCode { - get - { - return statusCode; - } - set { - statusCode = value; - statusMessage = HttpStatusCodes.GetStatusMessage(statusCode); - } + get => (int)HttpStatusCode; + set => HttpStatusCode = (HttpStatusCode)value; } + [Obsolete] public String StatusMessage { - get { - return statusMessage; - } - set { - statusMessage = value; - } + get => HttpStatusCode.ToString(); } + public static HttpResponse OK() => new HttpResponse(HttpStatusCode.OK); + public static HttpResponse Created() => new HttpResponse(HttpStatusCode.Created); + public static HttpResponse Accepted() => new HttpResponse(HttpStatusCode.Accepted); + public static HttpResponse NoContent() => new HttpResponse(HttpStatusCode.NoContent); + + public static HttpResponse MovedPermanently() => new HttpResponse(HttpStatusCode.MovedPermanently); + public static HttpResponse TemporaryRedirect() => new HttpResponse(HttpStatusCode.TemporaryRedirect); + public static HttpResponse PermanentRedirect() => new HttpResponse(HttpStatusCode.PermanentRedirect); + + public static HttpResponse NotFound() => new HttpResponse(HttpStatusCode.NotFound); + public static HttpResponse BadRequest() => new HttpResponse(HttpStatusCode.BadRequest); + public static HttpResponse Unauthorized() => new HttpResponse(HttpStatusCode.Unauthorized); + public static HttpResponse Forbidden() => new HttpResponse(HttpStatusCode.Forbidden); + public static HttpResponse MethodNotAllowed() => new HttpResponse(HttpStatusCode.MethodNotAllowed); + public static HttpResponse RequestTimeout() => new HttpResponse(HttpStatusCode.RequestTimeout); + public static HttpResponse ImATeapot() => new HttpResponse(HttpStatusCode.ImATeapot); + public static HttpResponse UpgradeRequired() => new HttpResponse(HttpStatusCode.UpgradeRequired); + + public static HttpResponse InternalServerError() => new HttpResponse(HttpStatusCode.InternalServerError); + public static HttpResponse NotImplemented() => new HttpResponse(HttpStatusCode.NotImplemented); + public static HttpResponse BadGateway() => new HttpResponse(HttpStatusCode.BadGateway); + public static HttpResponse ServiceUnavailable() => new HttpResponse(HttpStatusCode.ServiceUnavailable); + public static HttpResponse GatewayTimeout() => new HttpResponse(HttpStatusCode.GatewayTimeout); + + + + public HttpResponse Content(Exception exception) + { + SetHeader("content-type", "text/plain"); + ContentWriter.WriteLine("{0}", exception); + ContentWriter.Flush(); + + return this; + } + public HttpResponse Content(string text) + { + ContentWriter.Write(text); + ContentWriter.Flush(); + + return this; + } + + + + + } } diff --git a/ln.http/HttpStatusCodes.cs b/ln.http/HttpStatusCodes.cs index d419fb9..2bbac34 100644 --- a/ln.http/HttpStatusCodes.cs +++ b/ln.http/HttpStatusCodes.cs @@ -22,7 +22,6 @@ namespace ln.http { 403, "Access denied" }, { 404, "Not Found" }, { 500, "Internal Error" } - }; public static String GetStatusMessage(int code){ diff --git a/ln.http/connections/Connection.cs b/ln.http/connections/Connection.cs index 904c339..05e17fe 100644 --- a/ln.http/connections/Connection.cs +++ b/ln.http/connections/Connection.cs @@ -55,37 +55,5 @@ namespace ln.http.connections Listener = null; } - public virtual void SendResponse(HttpResponse response) => SendResponse(GetStream(), response); - - public static void SendResponse(Stream stream, HttpResponse response) - { - response.HttpRequest.FinishRequest(); - - 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.Position = 0; - response.ContentStream.CopyTo(stream); - response.ContentStream.Close(); - response.ContentStream.Dispose(); - - stream.Flush(); - } } } diff --git a/ln.http/ln.http.csproj b/ln.http/ln.http.csproj index afdbc29..a3c83e9 100644 --- a/ln.http/ln.http.csproj +++ b/ln.http/ln.http.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 true - 0.2.1-test + 0.2.2-test Harald Wolff-Thobaben l--n.de diff --git a/ln.http/router/SimpleRouter.cs b/ln.http/router/SimpleRouter.cs index 127a2a8..33d7ffa 100644 --- a/ln.http/router/SimpleRouter.cs +++ b/ln.http/router/SimpleRouter.cs @@ -5,8 +5,13 @@ using System.Text; using System.Linq; namespace ln.http.router { + + public delegate bool RouterFilterDelegate(SimpleRouter sender, ref HttpRoutingContext routingContext, HttpRequest httpRequest, out HttpResponse httpResponse); + public class SimpleRouter : IHttpRouter { + public event RouterFilterDelegate OnRoute; + List routes = new List(); public SimpleRoute[] Routes => routes.ToArray(); @@ -34,7 +39,7 @@ namespace ln.http.router return string.Format("{0}", part); }).ToArray(); - string reroute = string.Join("/", reparts); + string reroute = string.Format("{0}\\/?$", string.Join("/", reparts)); AddRoute(reroute, target, priority); } @@ -52,6 +57,17 @@ namespace ln.http.router public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) { + HttpResponse httpResponse; + + if (OnRoute != null) + { + foreach (RouterFilterDelegate filterDelegate in OnRoute.GetInvocationList()) + { + if (filterDelegate(this, ref routingContext, httpRequest, out httpResponse)) + return httpResponse; + } + } + foreach (SimpleRoute simpleRoute in routes.ToArray()) { Match match = simpleRoute.Route.Match(routingContext.Path); @@ -69,9 +85,9 @@ namespace ln.http.router residual = "/" + group.Value; } - HttpResponse response = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest); - if (response != null) - return response; + httpResponse = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest); + if (httpResponse != null) + return httpResponse; } } return null; diff --git a/ln.http/websocket/WebSocket.cs b/ln.http/websocket/WebSocket.cs index e570e2a..4585424 100644 --- a/ln.http/websocket/WebSocket.cs +++ b/ln.http/websocket/WebSocket.cs @@ -54,9 +54,7 @@ namespace ln.http.websocket String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key"); - HttpResponse httpResponse = new HttpResponse(httpRequest); - httpResponse.StatusCode = 101; - httpResponse.StatusMessage = "Switching Protocols"; + HttpResponse httpResponse = new HttpResponse(HttpStatusCode.SwitchingProtocols); httpResponse.AddHeader("upgrade", "websocket"); httpResponse.AddHeader("connection", "Upgrade"); httpResponse.AddHeader("Sec-WebSocket-Version", "13"); @@ -68,7 +66,7 @@ namespace ln.http.websocket Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey))) ); - Connection.SendResponse(Stream, httpResponse); + HTTPServer.SendResponse(Stream, httpRequest, httpResponse); //HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close(); State = WebSocketState.OPEN; }