add ParsedParameters to RequestContext, cleanup error handling

This commit is contained in:
GHXX 2024-01-16 23:52:28 +01:00
parent 8cdff9268a
commit 6cc849bf01
2 changed files with 30 additions and 11 deletions

View File

@ -213,11 +213,11 @@ public sealed class HttpServer {
foreach (var queryKV in queryStringArgs) { foreach (var queryKV in queryStringArgs) {
var queryKVSplitted = queryKV.Split('='); var queryKVSplitted = queryKV.Split('=');
if (queryKVSplitted.Length != 2) { if (queryKVSplitted.Length != 2) {
await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.BadRequest, "Malformed request URL parameters"); await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest, "Malformed request URL parameters");
return; return;
} }
if (!parsedQParams.TryAdd(WebUtility.UrlDecode(queryKVSplitted[0]), WebUtility.UrlDecode(queryKVSplitted[1]))) { if (!parsedQParams.TryAdd(WebUtility.UrlDecode(queryKVSplitted[0]), WebUtility.UrlDecode(queryKVSplitted[1]))) {
await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.BadRequest, "Duplicate request URL parameters"); await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest, "Duplicate request URL parameters");
return; return;
} }
} }
@ -230,20 +230,21 @@ public sealed class HttpServer {
if (stringToTypeParameterConverters[qparamInfo.type].TryConvertFromString(qparamValue, out object objRes)) { if (stringToTypeParameterConverters[qparamInfo.type].TryConvertFromString(qparamValue, out object objRes)) {
convertedQParamValues[i] = objRes; convertedQParamValues[i] = objRes;
} else { } else {
await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.BadRequest); await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest);
return; return;
} }
} else { } else {
if (qparamInfo.isOptional) { if (qparamInfo.isOptional) {
convertedQParamValues[i] = null!; convertedQParamValues[i] = null!;
} else { } else {
await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.BadRequest, $"Missing required query parameter {qparamName}"); await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest, $"Missing required query parameter {qparamName}");
return; return;
} }
} }
} }
} }
convertedQParamValues[0] = rc; convertedQParamValues[0] = rc;
rc.ParsedParameters = parsedQParams.AsReadOnly();
await (Task) (mi.Invoke(null, convertedQParamValues) ?? throw new NullReferenceException("Website func returned null unexpectedly")); await (Task) (mi.Invoke(null, convertedQParamValues) ?? throw new NullReferenceException("Website func returned null unexpectedly"));
} else { } else {
@ -256,7 +257,7 @@ public sealed class HttpServer {
if (Path.GetRelativePath(v, staticResponsePath).Contains("..")) { if (Path.GetRelativePath(v, staticResponsePath).Contains("..")) {
requestLogger.Warning($"Blocked GET request to {reqPath} as somehow the target file does not lie inside the static serve folder? Are you using symlinks?"); requestLogger.Warning($"Blocked GET request to {reqPath} as somehow the target file does not lie inside the static serve folder? Are you using symlinks?");
await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.NotFound); await HandleDefaultErrorPageAsync(rc, HttpStatusCode.NotFound);
return; return;
} }
@ -265,7 +266,7 @@ public sealed class HttpServer {
using var f = File.OpenRead(staticResponsePath); using var f = File.OpenRead(staticResponsePath);
await f.CopyToAsync(rc.ListenerContext.Response.OutputStream); await f.CopyToAsync(rc.ListenerContext.Response.OutputStream);
} else { } else {
await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.NotFound); await HandleDefaultErrorPageAsync(rc, HttpStatusCode.NotFound);
} }
return; return;
} }
@ -281,19 +282,27 @@ public sealed class HttpServer {
} finally { } finally {
try { await rc.RespWriter.FlushAsync(); } catch (ObjectDisposedException) { } try { await rc.RespWriter.FlushAsync(); } catch (ObjectDisposedException) { }
rc.ListenerContext.Response.Close(); rc.ListenerContext.Response.Close();
}
LogRequest(); LogRequest();
} }
}
private static async Task HandleDefaultErrorPageAsync(RequestContext ctx, HttpStatusCode errorCode, string? statusDescription = null) => await HandleDefaultErrorPageAsync(ctx, (int) errorCode, statusDescription);
private static async Task HandleDefaultErrorPageAsync(RequestContext ctx, int errorCode) { private static async Task HandleDefaultErrorPageAsync(RequestContext ctx, int errorCode, string? statusDescription = null) {
ctx.SetStatusCode(errorCode); ctx.SetStatusCode(errorCode);
string desc = statusDescription != null ? $"\r\n{statusDescription}" : "";
await ctx.WriteLineToRespAsync($""" await ctx.WriteLineToRespAsync($"""
<body> <body>
<h1>Oh no, an error occurred!</h1> <h1>Oh no, an error occurred!</h1>
<p>Code: {errorCode}</p> <p>Code: {errorCode}</p>{desc}
</body> </body>
"""); """);
try {
if (statusDescription == null) {
await ctx.SetStatusCodeAndDisposeAsync(errorCode);
} else {
await ctx.SetStatusCodeAndDisposeAsync(errorCode, statusDescription);
}
} catch (ObjectDisposedException) { }
} }
} }

View File

@ -1,9 +1,11 @@
using System.Net; using System.Collections.ObjectModel;
using System.Net;
namespace SimpleHttpServer; namespace SimpleHttpServer;
public class RequestContext : IDisposable { public class RequestContext : IDisposable {
public HttpListenerContext ListenerContext { get; } public HttpListenerContext ListenerContext { get; }
public ReadOnlyDictionary<string, string> ParsedParameters { get; internal set; }
private StreamReader? reqReader; private StreamReader? reqReader;
public StreamReader ReqReader => reqReader ??= new(ListenerContext.Request.InputStream); public StreamReader ReqReader => reqReader ??= new(ListenerContext.Request.InputStream);
@ -29,6 +31,7 @@ public class RequestContext : IDisposable {
using (this) { using (this) {
SetStatusCode(status); SetStatusCode(status);
await WriteToRespAsync("\n\n"); await WriteToRespAsync("\n\n");
await RespWriter.FlushAsync();
} }
} }
@ -36,6 +39,7 @@ public class RequestContext : IDisposable {
using (this) { using (this) {
SetStatusCode((int) status); SetStatusCode((int) status);
await WriteToRespAsync("\n\n"); await WriteToRespAsync("\n\n");
await RespWriter.FlushAsync();
} }
} }
@ -45,11 +49,17 @@ public class RequestContext : IDisposable {
ListenerContext.Response.StatusCode = status; ListenerContext.Response.StatusCode = status;
ListenerContext.Response.StatusDescription = description; ListenerContext.Response.StatusDescription = description;
await WriteToRespAsync("\n\n"); await WriteToRespAsync("\n\n");
await RespWriter.FlushAsync();
} }
} }
public async Task SetStatusCodeAndDisposeAsync(HttpStatusCode status, string description) => await SetStatusCodeAndDisposeAsync((int) status, description); public async Task SetStatusCodeAndDisposeAsync(HttpStatusCode status, string description) => await SetStatusCodeAndDisposeAsync((int) status, description);
public async Task WriteRedirect302AndDisposeAsync(string url) {
ListenerContext.Response.AddHeader("Location", url);
await SetStatusCodeAndDisposeAsync(HttpStatusCode.Redirect);
}
void IDisposable.Dispose() { void IDisposable.Dispose() {
reqReader?.Dispose(); reqReader?.Dispose();
respWriter?.Dispose(); respWriter?.Dispose();