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 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[]

View File

@ -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<string, List<String>> headers = new Dictionary<string, List<string>>();
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();
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;
}
}
}

View File

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

View File

@ -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();
}
}
}

View File

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

View File

@ -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<SimpleRoute> routes = new List<SimpleRoute>();
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;

View File

@ -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;
}