diff --git a/SimpleHttpServer/HttpServer.cs b/SimpleHttpServer/HttpServer.cs index b8e77f9..339ff2d 100644 --- a/SimpleHttpServer/HttpServer.cs +++ b/SimpleHttpServer/HttpServer.cs @@ -213,11 +213,11 @@ public sealed class HttpServer { foreach (var queryKV in queryStringArgs) { var queryKVSplitted = queryKV.Split('='); if (queryKVSplitted.Length != 2) { - await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.BadRequest, "Malformed request URL parameters"); + await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest, "Malformed request URL parameters"); return; } 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; } } @@ -230,20 +230,21 @@ public sealed class HttpServer { if (stringToTypeParameterConverters[qparamInfo.type].TryConvertFromString(qparamValue, out object objRes)) { convertedQParamValues[i] = objRes; } else { - await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.BadRequest); + await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest); return; } } else { if (qparamInfo.isOptional) { convertedQParamValues[i] = null!; } else { - await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.BadRequest, $"Missing required query parameter {qparamName}"); + await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest, $"Missing required query parameter {qparamName}"); return; } } } } convertedQParamValues[0] = rc; + rc.ParsedParameters = parsedQParams.AsReadOnly(); await (Task) (mi.Invoke(null, convertedQParamValues) ?? throw new NullReferenceException("Website func returned null unexpectedly")); } else { @@ -256,7 +257,7 @@ public sealed class HttpServer { 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?"); - await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.NotFound); + await HandleDefaultErrorPageAsync(rc, HttpStatusCode.NotFound); return; } @@ -265,7 +266,7 @@ public sealed class HttpServer { using var f = File.OpenRead(staticResponsePath); await f.CopyToAsync(rc.ListenerContext.Response.OutputStream); } else { - await rc.SetStatusCodeAndDisposeAsync(HttpStatusCode.NotFound); + await HandleDefaultErrorPageAsync(rc, HttpStatusCode.NotFound); } return; } @@ -281,19 +282,27 @@ public sealed class HttpServer { } finally { try { await rc.RespWriter.FlushAsync(); } catch (ObjectDisposedException) { } 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); + string desc = statusDescription != null ? $"\r\n{statusDescription}" : ""; await ctx.WriteLineToRespAsync($"""

Oh no, an error occurred!

-

Code: {errorCode}

+

Code: {errorCode}

{desc} """); + try { + if (statusDescription == null) { + await ctx.SetStatusCodeAndDisposeAsync(errorCode); + } else { + await ctx.SetStatusCodeAndDisposeAsync(errorCode, statusDescription); + } + } catch (ObjectDisposedException) { } } } \ No newline at end of file diff --git a/SimpleHttpServer/RequestContext.cs b/SimpleHttpServer/RequestContext.cs index 5e076db..420b5fe 100644 --- a/SimpleHttpServer/RequestContext.cs +++ b/SimpleHttpServer/RequestContext.cs @@ -1,9 +1,11 @@ -using System.Net; +using System.Collections.ObjectModel; +using System.Net; namespace SimpleHttpServer; public class RequestContext : IDisposable { public HttpListenerContext ListenerContext { get; } + public ReadOnlyDictionary ParsedParameters { get; internal set; } private StreamReader? reqReader; public StreamReader ReqReader => reqReader ??= new(ListenerContext.Request.InputStream); @@ -29,6 +31,7 @@ public class RequestContext : IDisposable { using (this) { SetStatusCode(status); await WriteToRespAsync("\n\n"); + await RespWriter.FlushAsync(); } } @@ -36,6 +39,7 @@ public class RequestContext : IDisposable { using (this) { SetStatusCode((int) status); await WriteToRespAsync("\n\n"); + await RespWriter.FlushAsync(); } } @@ -45,11 +49,17 @@ public class RequestContext : IDisposable { ListenerContext.Response.StatusCode = status; ListenerContext.Response.StatusDescription = description; await WriteToRespAsync("\n\n"); + await RespWriter.FlushAsync(); } } 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() { reqReader?.Dispose(); respWriter?.Dispose();