-
-
Notifications
You must be signed in to change notification settings - Fork 177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add 405 handler #65
Add 405 handler #65
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
namespace Botwin | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Threading.Tasks; | ||
using FluentValidation; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
|
@@ -35,23 +37,58 @@ public static IApplicationBuilder UseBotwin(this IApplicationBuilder builder, Bo | |
ApplyGlobalAfterHook(builder, options); | ||
|
||
var routeBuilder = new RouteBuilder(builder); | ||
var systemRoutes = new List<(string verb, string route)>(); | ||
|
||
//Create a "startup scope" to resolve modules from | ||
using (var scope = builder.ApplicationServices.CreateScope()) | ||
{ | ||
var modules = scope.ServiceProvider.GetServices<BotwinModule>(); | ||
|
||
//Get all instances of BotwinModule to fetch and register declared routes | ||
foreach (var module in scope.ServiceProvider.GetServices<BotwinModule>()) | ||
foreach (var module in modules) | ||
{ | ||
var moduleType = module.GetType(); | ||
|
||
foreach (var route in module.Routes.Keys) | ||
{ | ||
routeBuilder.MapVerb(route.verb, route.path, CreateRouteHandler(route, moduleType)); | ||
|
||
var strippedPath = route.path.EndsWith("/") ? route.path.Substring(0, route.path.Length - 1) : route.path; | ||
systemRoutes.Add((route.verb, "/" + strippedPath)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be cheaper to leave off the leading slash here, and trim the leading slash from the current request later instead. |
||
} | ||
} | ||
|
||
builder.UseRouter(routeBuilder.Build()); | ||
|
||
return builder.Use((ctx, next) => GetMethodNotAllowedHandler(ctx, next, systemRoutes)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// This method is only called if it's a valid 404 or 405 | ||
/// </summary> | ||
/// <param name="context"></param> | ||
/// <param name="next"></param> | ||
/// <param name="systemRoutes"></param> | ||
/// <returns></returns> | ||
private static async Task GetMethodNotAllowedHandler(HttpContext context, Func<Task> next, IEnumerable<(string verb, string route)> systemRoutes) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this does not return a handler function (which is what I was originally suggesting) but is the handler, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah tried your approach but no joy |
||
{ | ||
//Call the final pipeline which gets ASP.Net Core status code, usually a 404 in this case | ||
await next(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are you continuing the pipeline here at the beginning? Shouldn’t this handler already be kind of the final one in the pipeline? Do you need to know that it produced a 404? Wouldn’t it be safe to assume that if this handler runs after the routing middleware, then a route was not found? (again: I have no actual idea about the routing middleware) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oddly before that is called the response status code is 200, if you call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t think you actually need to check the status code here though. This should not run if the router succeeded in the pipeline step before. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interestingly if I remove the |
||
|
||
return builder.UseRouter(routeBuilder.Build()); | ||
var strippedPath = context.Request.Path.Value.EndsWith("/") && context.Request.Path.Value.Length > 1 | ||
? context.Request.Path.Value.Substring(0, context.Request.Path.Value.Length - 1) | ||
: context.Request.Path.Value; | ||
|
||
//ASP.Net Core will set a 405 response to 404. Let's check if it's a valid 404 first otherwise if we know about the route it's most likely a 405 | ||
if (context.Response.StatusCode == 404 && systemRoutes.Any(x => x.route == strippedPath)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use HashSet or Dictionary for constant time lookup. Also, I don’t know if the router middleware is case insensitive itself but if it is, then pure string equality will not work (you will have to use the RouteComparer we came up with earlier). |
||
{ | ||
var verbsForPath = systemRoutes.Where(x => x.route == strippedPath).Select(y => y.verb); | ||
if (verbsForPath.All(x => x != context.Request.Method)) | ||
{ | ||
context.Response.StatusCode = 405; | ||
} | ||
} | ||
} | ||
|
||
private static RequestDelegate CreateRouteHandler((string verb, string path) route, Type moduleType) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what David would say about this (allocations!?!?!), but maybe just
route.path.TrimEnd('/')
?