将 asp.net core 2.0 的 URL 重定向为小写

14

我看到你可以在ASP.NET Core 2.0中配置路由以生成小写URL,如此处所述: https://dev59.com/fFkS5IYBdhLWcg3wJDQP#45777372

使用以下内容:

services.AddRouting(options => options.LowercaseUrls = true);

然而,尽管这样生成URL是可以的,但它似乎没有实际强制执行它们,也就是说,不会重定向任何非全部小写的URL到相应的小写URL(最好通过301重定向)。

我知道有人在通过大小写不同的URL访问我的网站,我希望它们全部是小写的,并且是永久的。

是否只能通过RewriteOptions和正则表达式进行标准重定向来实现此目的?如果是,应该使用什么适当的表达式:

var options = new RewriteOptions().AddRedirect("???", "????");

还有其他的方法吗?

5个回答

15

虽然这篇文章已经发布数月,但对于可能正在寻找相同解决方案的人来说,您可以添加实现IRule的复杂重定向,例如:

public class RedirectLowerCaseRule : IRule
{
    public int StatusCode { get; } = (int)HttpStatusCode.MovedPermanently;

    public void ApplyRule(RewriteContext context)
    {
        HttpRequest request = context.HttpContext.Request;
        PathString path = context.HttpContext.Request.Path;
        HostString host = context.HttpContext.Request.Host;

        if (path.HasValue && path.Value.Any(char.IsUpper) || host.HasValue && host.Value.Any(char.IsUpper))
        {
            HttpResponse response = context.HttpContext.Response;
            response.StatusCode = StatusCode;
            response.Headers[HeaderNames.Location] = (request.Scheme + "://" + host.Value + request.PathBase.Value + request.Path.Value).ToLower() + request.QueryString;
            context.Result = RuleResult.EndResponse; 
        }
        else
        {
            context.Result = RuleResult.ContinueRules;
        } 
    }
}

然后可以在你的Startup.cs中的Configure方法中应用它,如下所示:

new RewriteOptions().Add(new RedirectLowerCaseRule());

3
Microsoft.AspNetCore.Http中,有一个StatusCodes枚举类型,你可以使用它来代替(int)HttpStatusCode.XXX。每个枚举名称中都包含状态码数字,例如StatusCodes.Status301MovedPermanently - CtrlDot
5
您不希望将 request.QueryString() 转为小写。这个值中可能包含合法的大写字母。 - Herb Stahl
1
使用 request.PathBase + request.Path 隐式地转换为 request.PathBase.ToString() + request.Path.ToString(),这将导致非 ASCII 字符的转义。相反,请使用 request.PathBase.Value + request.Path.Value 以防止不必要的转义。 - DhyMik

1

这是一种略微不同的实现,也受到另一个帖子的启发。

public class RedirectLowerCaseRule : IRule
{
    public void ApplyRule(RewriteContext context)
    {
        HttpRequest request = context.HttpContext.Request;
        string url = request.Scheme + "://" + request.Host + request.PathBase + request.Path;
        bool isGet = request.Method.ToLowerInvariant().Contains("get");

        if ( isGet && url.Contains(".") == false && Regex.IsMatch(url, @"[A-Z]") )
        {
            HttpResponse response = context.HttpContext.Response;
            response.Clear();
            response.StatusCode = StatusCodes.Status301MovedPermanently;
            response.Headers[HeaderNames.Location] = url.ToLowerInvariant() + request.QueryString;
            context.Result = RuleResult.EndResponse;
        }
        else
        {
            context.Result = RuleResult.ContinueRules;
        }
    }
}

我发现有用的更改如下:
使用ToLowerInvariant()代替ToLower()(请参见可能存在的问题此处
保留端口号。
绕过除GET之外的请求方法。
绕过带点的请求,假设静态文件如js/css/images等应保留任何大写字母。
使用Microsoft.AspNetCore.Http.StatusCodes

response.Headers[HeaderNames.Location] = UriHelper.Encode(new Uri((request.Scheme + "://" + host.Value + request.PathBase.Value + request.Path.Value).ToLower() + request.QueryString)); - Emilija Vilija Trečiokaitė

0

因为我还不能评论,所以作为答案添加。这是对Ben Maxfield答案的补充。

使用他的代码http://www.example.org/Example/example不会被重定向,因为没有检查大写字母的PathBase(即使它被用于构建新的小写URI)。

因此,基于他的代码,我最终使用了这个:

public class RedirectLowerCaseRule : IRule
{
    public int StatusCode { get; } = (int)HttpStatusCode.MovedPermanently;

    public void ApplyRule(RewriteContext context)
    {
        HttpRequest request = context.HttpContext.Request;
        PathString path = context.HttpContext.Request.Path;
        PathString pathbase = context.HttpContext.Request.PathBase;
        HostString host = context.HttpContext.Request.Host;

        if ((path.HasValue && path.Value.Any(char.IsUpper)) || (host.HasValue && host.Value.Any(char.IsUpper)) || (pathbase.HasValue && pathbase.Value.Any(char.IsUpper)))
        {
            Console.WriteLine("Redirect should happen");

            HttpResponse response = context.HttpContext.Response;
            response.StatusCode = StatusCode;
            response.Headers[HeaderNames.Location] = (request.Scheme + "://" + host.Value + request.PathBase + request.Path).ToLower() + request.QueryString;
            context.Result = RuleResult.EndResponse;
        }
        else
        {
            context.Result = RuleResult.ContinueRules;
        }
    }
}

1
请勿将“谢谢”作为答案。一旦您拥有足够的声望,您就可以投票支持有用的问题和答案。- 来自审核 - belwood
感谢您的评论,belwood!我编辑了我的答案。现在它看起来不像评论了,并且我添加了一个例子。 - DaBeSoft
1
使用 request.PathBase + request.Path 会隐式转换为 request.PathBase.ToString() + request.Path.ToString(),这将导致非 ASCII 字符的转义。请改用 request.PathBase.Value + request.Path.Value,以避免不必要的转义。 - DhyMik

0

我的看法...基于https://github.com/aspnet/AspNetCore/blob/master/src/Middleware/Rewrite/src/RedirectToWwwRule.cs

public class RedirectToLowercaseRule : IRule
{
    private readonly int _statusCode;

    public RedirectToLowercaseRule(int statusCode)
    {
        _statusCode = statusCode;
    }

    public void ApplyRule(RewriteContext context)
    {
        var req = context.HttpContext.Request;

        if (!req.Scheme.Any(char.IsUpper)
            && !req.Host.Value.Any(char.IsUpper)
            && !req.PathBase.Value.Any(char.IsUpper)
            && !req.Path.Value.Any(char.IsUpper))
        {
            context.Result = RuleResult.ContinueRules;
            return;
        }

        var newUrl = UriHelper.BuildAbsolute(req.Scheme.ToLowerInvariant(), new HostString(req.Host.Value.ToLowerInvariant()), req.PathBase.Value.ToLowerInvariant(), req.Path.Value.ToLowerInvariant(), req.QueryString);

        var response = context.HttpContext.Response;
        response.StatusCode = _statusCode;
        response.Headers[HeaderNames.Location] = newUrl;
        context.Result = RuleResult.EndResponse;
        context.Logger.RedirectedToLowercase();
    }
}

使用扩展方法:

public static class RewriteOptionsExtensions
{
    public static RewriteOptions AddRedirectToLowercase(this RewriteOptions options, int statusCode)
    {
        options.Add(new RedirectToLowercaseRule(statusCode));
        return options;
    }

    public static RewriteOptions AddRedirectToLowercase(this RewriteOptions options)
    {
        return AddRedirectToLowercase(options, StatusCodes.Status307TemporaryRedirect);
    }

    public static RewriteOptions AddRedirectToLowercasePermanent(this RewriteOptions options)
    {
        return AddRedirectToLowercase(options, StatusCodes.Status308PermanentRedirect);
    }
}

以及日志记录:

internal static class MiddlewareLoggingExtensions
{
    private static readonly Action<ILogger, Exception> _redirectedToLowercase = LoggerMessage.Define(LogLevel.Information, new EventId(1, "RedirectedToLowercase"), "Request redirected to lowercase");

    public static void RedirectedToLowercase(this ILogger logger)
    {
        _redirectedToLowercase(logger, null);
    }
}

使用方法:

app.UseRewriter(new RewriteOptions()
    .AddRedirectToLowercase());

其他考虑因素包括状态码的选择。 在扩展方法中,我使用了307和308,因为它们可以防止在请求期间更改请求方法(例如从GET到POST)。 但是,如果您希望允许该行为,则可以使用301和302。 有关详细信息,请参见HTTP 301与308状态代码有何区别?


-2

您确定要重定向吗?如果不是,而您的目标是主机和路径中没有大写字母,您可以使用以下IRule。这可以确保我在管道中查看路径时始终为小写。

public class RewriteLowerCaseRule : IRule
{
    public void ApplyRule(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        var host = request.Host;
        var pathBase = request.PathBase;
        var path = request.Path;
        if (host.HasValue)
        {
            if (host.Port == null)
            {
                request.Host = new HostString(host.Host.ToLower());
            }
            else
            {
                request.Host = new HostString(host.Host.ToLower(), (int) host.Port);
            }
        }

        if (pathBase.HasValue)
        {
            request.PathBase = new PathString(pathBase.Value.ToLower());
        }

        if (path.HasValue)
        {
            request.Path = new PathString(path.Value.ToLower());
            request.PathBase = new PathString(pathBase.Value.ToLower());
        }

        context.Result = RuleResult.ContinueRules;
    }
}

使用方法:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseRewriter(new RewriteOptions().Add(new RewriteLowerCaseRule()));
    ...
}

问题明确说明需要将包含任何大写字符的URL永久重定向为小写。这个答案并没有做到这一点,而且也没有提供任何新的东西比之前的答案更好。我也不确定在什么情况下您需要拥有非强制小写的URL,但在访问后端时强制小写,这似乎是一个代码方面的问题,如果被忘记或其他开发人员在团队中对它不了解,可能会导致难以找到的问题。 - Ben Maxfield
这是一个完美的替代方案,可以达到问题所要实现的目标。那个网站正在执行代码来评估传入的内容是否为大写字母,并进一步浪费互联网资源,通过发出浪费的永久重定向来再次评估它以确保其正确性。知道无法阻止大写请求的到来后,该网站应该接受它,修复它,并让请求通过。因此,在这里的正确答案是不要进行永久重定向。 - Herb Stahl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接