Normalize routes
This fixes an issue where routes are stored in an inconsistent format, potentially breaking workflow HTTP resumption due to bookmark hash mismatches.
This commit is contained in:
parent
c63369b567
commit
366ce2fabd
|
|
@ -1,4 +1,8 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Expressions.Models;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Http.Bookmarks;
|
||||
|
|
@ -436,7 +440,7 @@ public class HttpEndpoint : Trigger<HttpRequest>
|
|||
private IEnumerable<object> GetBookmarkPayloads(ExpressionExecutionContext context)
|
||||
{
|
||||
// Generate bookmark data for path and selected methods.
|
||||
var path = context.Get(Path);
|
||||
var normalizedRoute = context.Get(Path)!.NormalizeRoute();
|
||||
var methods = SupportedMethods.GetOrDefault(context) ?? new List<string> { HttpMethods.Get };
|
||||
var authorize = Authorize.GetOrDefault(context);
|
||||
var policy = Policy.GetOrDefault(context);
|
||||
|
|
@ -444,7 +448,7 @@ public class HttpEndpoint : Trigger<HttpRequest>
|
|||
var requestSizeLimit = RequestSizeLimit.GetOrDefault(context);
|
||||
|
||||
return methods
|
||||
.Select(x => new HttpEndpointBookmarkPayload(path!, x.ToLowerInvariant(), authorize, policy, requestTimeout, requestSizeLimit))
|
||||
.Select(x => new HttpEndpointBookmarkPayload(normalizedRoute, x.ToLowerInvariant(), authorize, policy, requestTimeout, requestSizeLimit))
|
||||
.Cast<object>()
|
||||
.ToArray();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,5 +10,5 @@ public interface IRouteMatcher
|
|||
/// <summary>
|
||||
/// Matches a given request path against the specified route template.
|
||||
/// </summary>
|
||||
RouteValueDictionary? Match(string routeTemplate, string requestPath);
|
||||
RouteValueDictionary? Match(string routeTemplate, string route);
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Elsa.Http.Contracts;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -5,8 +7,27 @@ namespace Elsa.Http.Contracts;
|
|||
/// </summary>
|
||||
public interface IRouteTable : IEnumerable<string>
|
||||
{
|
||||
void Add(string path);
|
||||
void Remove(string path);
|
||||
void AddRange(IEnumerable<string> paths);
|
||||
void RemoveRange(IEnumerable<string> paths);
|
||||
/// <summary>
|
||||
/// Adds a route to the table.
|
||||
/// </summary>
|
||||
/// <param name="route">The route to add.</param>
|
||||
void Add(string route);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a route from the table.
|
||||
/// </summary>
|
||||
/// <param name="route">The route to remove.</param>
|
||||
void Remove(string route);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a range of routes to the table.
|
||||
/// </summary>
|
||||
/// <param name="routes">The routes to add.</param>
|
||||
void AddRange(IEnumerable<string> routes);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a range of routes from the table.
|
||||
/// </summary>
|
||||
/// <param name="routes">The routes to remove.</param>
|
||||
void RemoveRange(IEnumerable<string> routes);
|
||||
}
|
||||
14
src/modules/Elsa.Http/Extensions/RouteExtensions.cs
Normal file
14
src/modules/Elsa.Http/Extensions/RouteExtensions.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// ReSharper disable once CheckNamespace
|
||||
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for strings that represent a route.
|
||||
/// </summary>
|
||||
public static class RouteExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Normalizes a route by ensuring a leading slash, removing any trailing slash and converting the path to lowercase.
|
||||
/// </summary>
|
||||
public static string NormalizeRoute(this string path) => $"/{path.Trim('/').ToLowerInvariant()}";
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Http.Contracts;
|
||||
using Elsa.Http.Models;
|
||||
using Elsa.Http.Options;
|
||||
|
|
@ -10,6 +13,8 @@ using Microsoft.Extensions.Options;
|
|||
using System.Net;
|
||||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Http.Bookmarks;
|
||||
using Elsa.Workflows.Core;
|
||||
|
|
@ -85,7 +90,7 @@ public class WorkflowsMiddleware
|
|||
public async Task InvokeAsync(HttpContext httpContext)
|
||||
{
|
||||
var path = GetPath(httpContext);
|
||||
var basePath = _options.BasePath;
|
||||
var basePath = _options.BasePath?.ToString().NormalizeRoute();
|
||||
|
||||
// If the request path does not match the configured base path to handle workflows, then skip.
|
||||
if (!string.IsNullOrWhiteSpace(basePath))
|
||||
|
|
@ -97,7 +102,7 @@ public class WorkflowsMiddleware
|
|||
}
|
||||
|
||||
// Strip the base path.
|
||||
path = path[basePath.Value.Value!.Length..];
|
||||
path = path[basePath.Length..];
|
||||
}
|
||||
|
||||
var matchingPath = GetMatchingRoute(path);
|
||||
|
|
@ -249,7 +254,7 @@ public class WorkflowsMiddleware
|
|||
}
|
||||
}
|
||||
|
||||
private string GetPath(HttpContext httpContext) => httpContext.Request.Path.Value!.ToLowerInvariant();
|
||||
private string GetPath(HttpContext httpContext) => httpContext.Request.Path.Value!.NormalizeRoute();
|
||||
|
||||
private async Task<bool> HandleNoWorkflowsFoundAsync(HttpContext httpContext, ICollection<WorkflowMatch> workflowMatches, PathString? basePath)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
// Taken & adapted from https://github.com/markvincze/Stubbery/blob/main/src/Stubbery/RequestMatching/RouteMatcher.cs
|
||||
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Http.Contracts;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
|
@ -10,13 +11,14 @@ namespace Elsa.Http.Services;
|
|||
public class RouteMatcher : IRouteMatcher
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public RouteValueDictionary? Match(string routeTemplate, string requestPath)
|
||||
public RouteValueDictionary? Match(string routeTemplate, string route)
|
||||
{
|
||||
var normalizedRoute = route.NormalizeRoute();
|
||||
var template = TemplateParser.Parse(routeTemplate);
|
||||
var matcher = new TemplateMatcher(template, GetDefaults(template));
|
||||
var values = new RouteValueDictionary();
|
||||
|
||||
return matcher.TryMatch(requestPath, values) ? values : null;
|
||||
return matcher.TryMatch(normalizedRoute, values) ? values : null;
|
||||
}
|
||||
|
||||
private static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Http.Contracts;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -25,30 +27,35 @@ public class RouteTable : IRouteTable
|
|||
private ConcurrentDictionary<string, string> Routes => _cache.GetOrCreate(Key, _ => new ConcurrentDictionary<string, string>())!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(string path)
|
||||
public void Add(string route)
|
||||
{
|
||||
if (path.Contains("//"))
|
||||
if (route.Contains("//"))
|
||||
{
|
||||
_logger.LogWarning("Path cannot contain double slashes. Ignoring path: {Path}", path);
|
||||
_logger.LogWarning("Path cannot contain double slashes. Ignoring path: {Path}", route);
|
||||
return;
|
||||
}
|
||||
|
||||
Routes.TryAdd(path, path);
|
||||
var normalizedRoute = route.NormalizeRoute();
|
||||
Routes.TryAdd(normalizedRoute, normalizedRoute);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(string path) => Routes.TryRemove(path, out _);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddRange(IEnumerable<string> paths)
|
||||
public void Remove(string route)
|
||||
{
|
||||
foreach (var path in paths) Add(path);
|
||||
var normalizedRoute = route.NormalizeRoute();
|
||||
Routes.TryRemove(normalizedRoute, out _);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveRange(IEnumerable<string> paths)
|
||||
public void AddRange(IEnumerable<string> routes)
|
||||
{
|
||||
foreach (var path in paths) Remove(path);
|
||||
foreach (var route in routes) Add(route);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveRange(IEnumerable<string> routes)
|
||||
{
|
||||
foreach (var route in routes) Remove(route);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
Loading…
Reference in a new issue