Add default 404 handling, extend HttpResponse, fix several issues
ln.build - build0.waldrennach.l--n.de build job pending Details

master
Harald Wolff 2020-12-06 15:01:02 +01:00
parent f868b0e998
commit 22c09b5b0a
7 changed files with 147 additions and 116 deletions

View File

@ -10,7 +10,7 @@ using ln.http.exceptions;
using System.Threading; using System.Threading;
using ln.type; using ln.type;
using ln.http.router; using ln.http.router;
using System.IO;
namespace ln.http namespace ln.http
{ {
@ -141,6 +141,7 @@ namespace ln.http
do do
{ {
httpRequest = connection.ReadRequest(this); httpRequest = connection.ReadRequest(this);
if (httpRequest == null) if (httpRequest == null)
break; break;
@ -151,23 +152,16 @@ namespace ln.http
} }
catch (HttpException httpExc) catch (HttpException httpExc)
{ {
response = new HttpResponse(httpRequest); response = new HttpResponse((HttpStatusCode)httpExc.StatusCode).Content(httpExc.Message);
response.StatusCode = httpExc.StatusCode;
response.StatusMessage = httpExc.Message;
response.ContentWriter.WriteLine(httpExc.Message);
} }
if (response != null) if (response == null)
{ response = HttpResponse.NotFound().Content(String.Format("The URI {0} could not be found on this server.", httpRequest.URI));
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); keepAlive = httpRequest.GetRequestHeader("connection", "keep-alive").Equals("keep-alive") && response.GetHeader("connection", "keep-alive").Equals("keep-alive");
} response.SetHeader("connection", keepAlive ? "keep-alive" : "close");
else
{ SendResponse(connection.GetStream(), httpRequest, response);
keepAlive = false;
}
response?.ContentStream?.Dispose(); response?.ContentStream?.Dispose();
@ -176,17 +170,16 @@ namespace ln.http
catch (Exception e) catch (Exception e)
{ {
Logging.Log(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(); HttpRequest.ClearCurrent();
connection.GetStream().Close(); connection.GetStream().Close();
} finally } 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) public static void StartSimpleServer(string[] arguments)
{ {
ArgumentContainer argumentContainer = new ArgumentContainer(new Argument[] ArgumentContainer argumentContainer = new ArgumentContainer(new Argument[]

View File

@ -7,44 +7,54 @@ namespace ln.http
{ {
public class HttpResponse public class HttpResponse
{ {
public HttpRequest HttpRequest { get; } //public HttpRequest HttpRequest { get; }
public Stream ContentStream { get; private set; } public Stream ContentStream { get; private set; }
public TextWriter ContentWriter { get; private set; } public TextWriter ContentWriter { get; private set; }
public bool HasCustomContentStream { get; private set; } public bool HasCustomContentStream { get; private set; }
int statusCode;
string statusMessage;
Dictionary<string, List<String>> headers = new Dictionary<string, List<string>>(); Dictionary<string, List<String>> headers = new Dictionary<string, List<string>>();
List<HttpCookie> cookies = new List<HttpCookie>(); List<HttpCookie> cookies = new List<HttpCookie>();
public HttpResponse(HttpRequest httpRequest)
public HttpResponse() : this(HttpStatusCode.OK)
{ {
HttpRequest = httpRequest; }
public HttpResponse(HttpStatusCode statusCode)
{
HttpStatusCode = statusCode;
ContentStream = new MemoryStream(); ContentStream = new MemoryStream();
ContentWriter = new StreamWriter(ContentStream); ContentWriter = new StreamWriter(ContentStream);
StatusCode = 200;
SetHeader("content-type", "text/html"); 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); 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; ContentStream = contentStream;
ContentWriter = null; ContentWriter = null;
HasCustomContentStream = true; HasCustomContentStream = true;
StatusCode = 200; HttpStatusCode = HttpStatusCode.OK;
SetHeader("content-type", "text/html"); 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); SetHeader("content-type", contentType);
} }
@ -80,56 +90,80 @@ namespace ln.http
headers[name].Add(value); headers[name].Add(value);
} }
public void RemoveHeader(String name) public void RemoveHeader(String name) => headers.Remove(name.ToUpper());
{ public bool ContainsHeader(string headerName) => headers.ContainsKey(headerName.ToUpper());
headers.Remove(name.ToUpper());
}
public bool ContainsHeader(string headerName)
{
return headers.ContainsKey(headerName.ToUpper());
}
public void AddCookie(string name,string value) public void AddCookie(string name,string value)
{ {
AddCookie(new HttpCookie(name, value)); AddCookie(new HttpCookie(name, value));
} }
public void AddCookie(HttpCookie httpCookie) public void AddCookie(HttpCookie httpCookie) => cookies.Add(httpCookie);
{ public void RemoveCookie(HttpCookie httpCookie) => cookies.Remove(httpCookie);
cookies.Add(httpCookie);
}
public void RemoveCookie(HttpCookie httpCookie)
{
cookies.Remove(httpCookie);
}
public HttpCookie[] Cookies => cookies.ToArray(); public HttpCookie[] Cookies => cookies.ToArray();
public HttpStatusCode HttpStatusCode { get; set; }
[Obsolete]
public int StatusCode public int StatusCode
{ {
get get => (int)HttpStatusCode;
{ set => HttpStatusCode = (HttpStatusCode)value;
return statusCode;
}
set {
statusCode = value;
statusMessage = HttpStatusCodes.GetStatusMessage(statusCode);
}
} }
[Obsolete]
public String StatusMessage public String StatusMessage
{ {
get { get => HttpStatusCode.ToString();
return statusMessage;
}
set {
statusMessage = value;
}
} }
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;
}
} }
} }

View File

@ -22,7 +22,6 @@ namespace ln.http
{ 403, "Access denied" }, { 403, "Access denied" },
{ 404, "Not Found" }, { 404, "Not Found" },
{ 500, "Internal Error" } { 500, "Internal Error" }
}; };
public static String GetStatusMessage(int code){ public static String GetStatusMessage(int code){

View File

@ -55,37 +55,5 @@ namespace ln.http.connections
Listener = null; 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();
}
} }
} }

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.2.1-test</Version> <Version>0.2.2-test</Version>
<Authors>Harald Wolff-Thobaben</Authors> <Authors>Harald Wolff-Thobaben</Authors>
<Company>l--n.de</Company> <Company>l--n.de</Company>
<Description /> <Description />

View File

@ -5,8 +5,13 @@ using System.Text;
using System.Linq; using System.Linq;
namespace ln.http.router namespace ln.http.router
{ {
public delegate bool RouterFilterDelegate(SimpleRouter sender, ref HttpRoutingContext routingContext, HttpRequest httpRequest, out HttpResponse httpResponse);
public class SimpleRouter : IHttpRouter public class SimpleRouter : IHttpRouter
{ {
public event RouterFilterDelegate OnRoute;
List<SimpleRoute> routes = new List<SimpleRoute>(); List<SimpleRoute> routes = new List<SimpleRoute>();
public SimpleRoute[] Routes => routes.ToArray(); public SimpleRoute[] Routes => routes.ToArray();
@ -34,7 +39,7 @@ namespace ln.http.router
return string.Format("{0}", part); return string.Format("{0}", part);
}).ToArray(); }).ToArray();
string reroute = string.Join("/", reparts); string reroute = string.Format("{0}\\/?$", string.Join("/", reparts));
AddRoute(reroute, target, priority); AddRoute(reroute, target, priority);
} }
@ -52,6 +57,17 @@ namespace ln.http.router
public HttpResponse Route(HttpRoutingContext routingContext, HttpRequest httpRequest) 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()) foreach (SimpleRoute simpleRoute in routes.ToArray())
{ {
Match match = simpleRoute.Route.Match(routingContext.Path); Match match = simpleRoute.Route.Match(routingContext.Path);
@ -69,9 +85,9 @@ namespace ln.http.router
residual = "/" + group.Value; residual = "/" + group.Value;
} }
HttpResponse response = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest); httpResponse = simpleRoute.Target.Route(routingContext.Routed(residual), httpRequest);
if (response != null) if (httpResponse != null)
return response; return httpResponse;
} }
} }
return null; return null;

View File

@ -54,9 +54,7 @@ namespace ln.http.websocket
String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key"); String wsKey = httpRequest.GetRequestHeader("Sec-WebSocket-Key");
HttpResponse httpResponse = new HttpResponse(httpRequest); HttpResponse httpResponse = new HttpResponse(HttpStatusCode.SwitchingProtocols);
httpResponse.StatusCode = 101;
httpResponse.StatusMessage = "Switching Protocols";
httpResponse.AddHeader("upgrade", "websocket"); httpResponse.AddHeader("upgrade", "websocket");
httpResponse.AddHeader("connection", "Upgrade"); httpResponse.AddHeader("connection", "Upgrade");
httpResponse.AddHeader("Sec-WebSocket-Version", "13"); httpResponse.AddHeader("Sec-WebSocket-Version", "13");
@ -68,7 +66,7 @@ namespace ln.http.websocket
Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey))) Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(acceptKey)))
); );
Connection.SendResponse(Stream, httpResponse); HTTPServer.SendResponse(Stream, httpRequest, httpResponse);
//HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close(); //HTTPServerConnection.Current.Value.AbortRequested += (connection) => Close();
State = WebSocketState.OPEN; State = WebSocketState.OPEN;
} }