Compare commits
3 commits
c302da3713
...
bc8ce1a19a
Author | SHA1 | Date | |
---|---|---|---|
|
bc8ce1a19a | ||
|
e042d64e5e | ||
|
2a62fb2c7b |
8 changed files with 106 additions and 112 deletions
|
@ -112,6 +112,7 @@ public sealed class HttpServer
|
|||
while (client.Connected)
|
||||
{
|
||||
HttpRequest req = new HttpRequest(this, client, stream);
|
||||
HttpResponse? response;
|
||||
try
|
||||
{
|
||||
await req.ReadAllHeaders();
|
||||
|
@ -126,7 +127,7 @@ public sealed class HttpServer
|
|||
pathSpl = pathSpl.Slice(1);
|
||||
|
||||
//Execute
|
||||
HttpResponse? response = await Router.Handle(req, pathSpl, null);
|
||||
response = await Router.Handle(req, pathSpl, null);
|
||||
if (response != null)
|
||||
await req.Write(response);
|
||||
else
|
||||
|
@ -147,7 +148,8 @@ public sealed class HttpServer
|
|||
throw;
|
||||
}
|
||||
|
||||
if (req.Headers.TryGetValue("connection", out string? connectionValue) && connectionValue == "close")
|
||||
if ((response != null && response.StatusCode is >= 300 and < 400) ||
|
||||
req.Headers.TryGetValue("connection", out string? connectionValue) && connectionValue == "close")
|
||||
{
|
||||
client.Close();
|
||||
break;
|
||||
|
|
|
@ -11,7 +11,7 @@ public class CORS : IRouter
|
|||
{
|
||||
}
|
||||
|
||||
public Task<HttpResponse?> Handle(HttpRequest client, ArraySegment<string> path, ParameterCollection? baseParameters)
|
||||
public Task<HttpResponse?> Handle(HttpRequest client, ArraySegment<string> path, VariableCollection? baseParameters)
|
||||
{
|
||||
if (client.Method != HttpMethod.OPTIONS)
|
||||
return Task.FromResult<HttpResponse?>(null);
|
||||
|
|
|
@ -6,14 +6,14 @@ namespace MiniHTTP.Routing;
|
|||
/// An asynchronous endpoint handler.
|
||||
/// </summary>
|
||||
/// <param name="request">The request being served.</param>
|
||||
/// <param name="parameters">Parameters in the path.</param>
|
||||
/// <param name="variables">Variables provided in the path.</param>
|
||||
/// <returns>Returns a HTTP response if the request successfully hit. Returns null if the request misses the handler.</returns>
|
||||
public delegate Task<HttpResponse?> EndpointHandlerAsync(HttpRequest request, ParameterCollection parameters);
|
||||
public delegate Task<HttpResponse?> EndpointHandlerAsync(HttpRequest request, VariableCollection variables);
|
||||
|
||||
/// <summary>
|
||||
/// An synchronous endpoint handler.
|
||||
/// </summary>
|
||||
/// <param name="request">The request being served.</param>
|
||||
/// <param name="parameters">Parameters in the path.</param>
|
||||
/// <param name="variables">Variables provided in the path.</param>
|
||||
/// <returns>Returns a HTTP response if the request successfully hit. Returns null if the request misses the handler.</returns>
|
||||
public delegate HttpResponse? EndpointHandler(HttpRequest request, ParameterCollection parameters);
|
||||
public delegate HttpResponse? EndpointHandler(HttpRequest request, VariableCollection variables);
|
|
@ -7,5 +7,5 @@ namespace MiniHTTP.Routing;
|
|||
/// </summary>
|
||||
public interface IRouter
|
||||
{
|
||||
Task<HttpResponse?> Handle(HttpRequest client, ArraySegment<string> path, ParameterCollection? baseParameters);
|
||||
Task<HttpResponse?> Handle(HttpRequest client, ArraySegment<string> path, VariableCollection? baseParameters);
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
namespace MiniHTTP.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// A map of arguments passed as parameters in a HTTP path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Routes may contain parameters, denoted by a colon at the beginning. When a request is made, whatever is provided in that space becomes available in the parameter collection as an argument.
|
||||
/// If you have a route such as <c>/far/:example/bar</c>, and a client requests the path <c>/foo/quux/bar</c>, then the argument passed to the "example" parameter will be "quux".
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Arguments can be obtained from the collection like a <see cref="Dictionary{TKey, TValue}"/>. For example:
|
||||
/// <code>
|
||||
/// string id = parameters["id"];
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class ParameterCollection
|
||||
{
|
||||
public static readonly ParameterCollection Empty = new ParameterCollection();
|
||||
|
||||
/// <summary>
|
||||
/// A parameter collection to read from if this instance fails to fulfill a lookup.
|
||||
/// </summary>
|
||||
public readonly ParameterCollection? Fallback;
|
||||
|
||||
readonly Dictionary<string, string> Parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public string? this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out var value))
|
||||
return value;
|
||||
else if (Fallback == null)
|
||||
return null;
|
||||
else
|
||||
return Fallback[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
Parameters.Remove(key);
|
||||
else
|
||||
Parameters[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ParameterCollection(ParameterCollection? fallback = null)
|
||||
{
|
||||
Fallback = fallback;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Parameters.Clear();
|
||||
}
|
||||
}
|
|
@ -9,15 +9,17 @@ public class Router : IRouter
|
|||
{
|
||||
struct Route
|
||||
{
|
||||
public string[] Paths;
|
||||
public string Path;
|
||||
|
||||
public IRouter Router;
|
||||
|
||||
public Route(string[] paths, IRouter router)
|
||||
public Route(string path, IRouter router)
|
||||
{
|
||||
Paths = paths;
|
||||
Path = path;
|
||||
Router = router;
|
||||
}
|
||||
|
||||
public override string ToString() => Path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -42,85 +44,75 @@ public class Router : IRouter
|
|||
Endpoint = (client, parameters) => Task.FromResult(endpoint(client, parameters));
|
||||
}
|
||||
|
||||
internal async Task<HttpResponse?> Handle(HttpRequest client, ArraySegment<string> path, ParameterCollection? baseParameters)
|
||||
internal async Task<HttpResponse?> Handle(HttpRequest client, ArraySegment<string> path, VariableCollection? baseParameters)
|
||||
{
|
||||
if (Method != HttpMethod.Any && client.Method != Method)
|
||||
return null;
|
||||
|
||||
ParameterCollection parameters = new ParameterCollection(baseParameters);
|
||||
foreach (var route in Routes)
|
||||
if (Routes.Count > 0)
|
||||
{
|
||||
if (route.Paths.Length != 1 || route.Paths[0] != "*")
|
||||
VariableCollection parameters = new VariableCollection(baseParameters);
|
||||
foreach (var route in Routes)
|
||||
{
|
||||
if (route.Paths.Length > path.Count)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < route.Paths.Length; i++)
|
||||
if (route.Path != "*")
|
||||
{
|
||||
string key = route.Paths[i];
|
||||
if (key.Length == 0)
|
||||
string key = route.Path;
|
||||
|
||||
if (key.Length > 0 && key[0] == ':')
|
||||
{
|
||||
if (path[i].Length == 0)
|
||||
continue;
|
||||
else
|
||||
parameters[route.Path[1..]] = path[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!key.Equals(path[0], StringComparison.OrdinalIgnoreCase))
|
||||
goto skip;
|
||||
}
|
||||
|
||||
if (key[0] == ':')
|
||||
{
|
||||
parameters[route.Paths[i][1..]] = path[i];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!key.Equals(path[i], StringComparison.OrdinalIgnoreCase))
|
||||
goto skip;
|
||||
}
|
||||
|
||||
if (path.Count > 0)
|
||||
{
|
||||
HttpResponse? resp = await route.Router.Handle(client, path.Slice(1), parameters);
|
||||
if (resp != null)
|
||||
return resp;
|
||||
}
|
||||
|
||||
skip:
|
||||
parameters.Clear();
|
||||
}
|
||||
|
||||
HttpResponse? resp = await route.Router.Handle(client, path.Slice(route.Paths.Length), parameters);
|
||||
if (resp != null)
|
||||
return resp;
|
||||
|
||||
skip:
|
||||
parameters.Clear();
|
||||
}
|
||||
|
||||
if (Endpoint == null)
|
||||
return null;
|
||||
else
|
||||
return await Endpoint(client, baseParameters ?? ParameterCollection.Empty);
|
||||
return await Endpoint(client, baseParameters ?? VariableCollection.Empty);
|
||||
}
|
||||
|
||||
Task<HttpResponse?> IRouter.Handle(HttpRequest client, ArraySegment<string> path, ParameterCollection? baseParameters)
|
||||
Task<HttpResponse?> IRouter.Handle(HttpRequest client, ArraySegment<string> path, VariableCollection? baseParameters)
|
||||
=> Handle(client, path, baseParameters);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new route to the router.
|
||||
/// </summary>
|
||||
/// <param name="path">The subpath to the route.</param>
|
||||
/// <param name="pathSegment">The path segment to the route.</param>
|
||||
/// <param name="route">The router to use.</param>
|
||||
public void Use(string path, IRouter route)
|
||||
public void Use(string pathSegment, IRouter route)
|
||||
{
|
||||
string[] pathParams = path.Split('/', StringSplitOptions.TrimEntries);
|
||||
Routes.Add(new Route(pathParams, route));
|
||||
Routes.Add(new Route(pathSegment, route));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an endpoint to the router.
|
||||
/// </summary>
|
||||
/// <param name="method">The HTTP method to respond to.</param>
|
||||
/// <param name="path">The subpath to the endpoint.</param>
|
||||
/// <param name="pathSegment">The subpath to the endpoint.</param>
|
||||
/// <param name="endpoint">The endpoint handler which serves the request.</param>
|
||||
public void Add(HttpMethod method, string path, EndpointHandlerAsync endpoint) => Use(path, new Router(endpoint, method));
|
||||
public void Add(HttpMethod method, string pathSegment, EndpointHandlerAsync endpoint) => Use(pathSegment, new Router(endpoint, method));
|
||||
|
||||
/// <inheritdoc cref="Add(string, EndpointHandlerAsync)"/>
|
||||
public void Add(HttpMethod method, string path, EndpointHandler endpoint) => Use(path, new Router(endpoint, method));
|
||||
public void Add(HttpMethod method, string pathSegment, EndpointHandler endpoint) => Use(pathSegment, new Router(endpoint, method));
|
||||
|
||||
/// <summary>
|
||||
/// Adds a static file route to the router.
|
||||
/// </summary>
|
||||
public void Static(string directory) => Routes.Add(new Route([":asset"], new Static(directory, "asset")));
|
||||
|
||||
/// <inheritdoc cref="Static(string)"/>
|
||||
public void Static(string directory, string path, string assetParam) => Use(path, new Static(directory, assetParam));
|
||||
public void Static(string directory) => Routes.Add(new Route(":asset", new Static(directory, "asset")));
|
||||
}
|
|
@ -70,7 +70,7 @@ public partial class Static : IRouter
|
|||
AssetParameter = assetParameter;
|
||||
}
|
||||
|
||||
async Task<HttpResponse?> IRouter.Handle(HttpRequest client, ArraySegment<string> path, ParameterCollection? baseParameters)
|
||||
async Task<HttpResponse?> IRouter.Handle(HttpRequest client, ArraySegment<string> path, VariableCollection? baseParameters)
|
||||
{
|
||||
if (baseParameters == null)
|
||||
return null;
|
||||
|
|
58
MiniHTTP/Routing/VariableCollection.cs
Normal file
58
MiniHTTP/Routing/VariableCollection.cs
Normal file
|
@ -0,0 +1,58 @@
|
|||
namespace MiniHTTP.Routing;
|
||||
|
||||
/// <summary>
|
||||
/// A map of arguments passed as variables in a HTTP path.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Routes may contain variables, denoted by a colon at the beginning. When a request is made, whatever is provided in that space becomes available in the variable collection as an argument.
|
||||
/// If you have a route such as <c>/far/:example/bar</c>, and a client requests the path <c>/foo/quux/bar</c>, then the argument passed to the "example" variable will be "quux".
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Arguments can be obtained from the collection like a <see cref="Dictionary{TKey, TValue}"/>. For example:
|
||||
/// <code>
|
||||
/// string id = variables["id"];
|
||||
/// </code>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public class VariableCollection
|
||||
{
|
||||
public static readonly VariableCollection Empty = new VariableCollection();
|
||||
|
||||
/// <summary>
|
||||
/// A variable collection to read from if this instance fails to fulfill a lookup.
|
||||
/// </summary>
|
||||
public readonly VariableCollection? Fallback;
|
||||
|
||||
readonly Dictionary<string, string> Values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public string? this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Values.TryGetValue(key, out var value))
|
||||
return value;
|
||||
else if (Fallback == null)
|
||||
return null;
|
||||
else
|
||||
return Fallback[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
Values.Remove(key);
|
||||
else
|
||||
Values[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public VariableCollection(VariableCollection? fallback = null)
|
||||
{
|
||||
Fallback = fallback;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Values.Clear();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue