ASP.NET Core URL Rewrite中间件

URL重写是基于一个或多个预置规则修改请求URL的行为。URL重写在资源位置和访问地址之间创建了一种抽象,这样二者之间就减少了紧密的联系。URL重写有多种适用的场景:

  • 临时或永久移动或替换服务器资源,同时为这些资源保持稳定的访问
  • 为不同应用程序或同一个应用程序的不同区域的拆分请求处理
  • 根据请求移除、添加、重新组织URL段(segment)
  • SEO优化
  • 允许使用友好的公共URL来帮助人们通过链接预测找到内容
  • 将不安全的请求重定向到安全端点
  • 图片防盗链

可以通过多种方式定义改变URL的规则,包括正则表达式、Apache mod_rewrite模块规则、IIS重写模块规则和自定义规则逻辑。本文介绍URL重写及说明如何在ASP.NET Core应用中使用URL重写中间件。

注意:URL重写可能会降低应用的性能,您应该尽可能的限制规则的数量和规则的复杂性。

URL重定向和URL重写

从字面意思上看URL重定向和URL重写的差异并不明显,但二者在提供资源给客户端方面都有重要意义。ASP.NET Core的URL重写中间件能够同时满足二者的需求。URL重定向是客户端操作,指示客户端在另一个地址访问资源,需要额外往返服务器。当客户端对资源发出请求时,返回到客户端的重定向URL将显示在浏览器的地址栏中。例如/resource被重定向到/different-resource时:客户端请求/resource,服务端响应客户端应在/different-resource获取资源,其响应的状态码会指示重定向是临时的还是永久的,然后客户端会向/different-resource发送一个新请求获取资源。
在这里插入图片描述
将请求重定向到其他URL时,可以指定重定向是永久还是临时。301(Moved Permanently)状态代码用于表明资源具有新的永久URL,并且希望客户端将来对该资源的所有请求都应使用新URL。当收到301状态码时客户端可以缓存响应。302(Found)状态码用于临时重定向,所以客户端不应该存储和重用该URL。状态码的含义请参考这里。URL重写是服务器端操作,用于从不同的资源地址提供资源。URL重写不需要额外的往返服务器,并且重写后的URL不会返回给客户端,也不会出现在客户端的地址栏中。当/resource被重写为/different-resource时:客户端请求/resource,服务端在内部从/different-resource获取资源并响应给客户端。尽管客户端也许可以从重写后的URL处获取资源,但客户端并不会收到资源存在于重写后URL的通知。
在这里插入图片描述

何时使用URL重写中间件

当无法在Windows Server上使用IIS重写模块、Apache服务器上的Apache mod_rewrite模块、Nginx上的URL重写或应用程序托管在HTTP.sys服务器(以前称为WebListener)上时,请使用URL重写中间件。推荐在IIS,Apache或Nginx中使用基于服务器的URL重写技术的主要原因是中间件不支持这些模块的全部功能,并且中间件的性能可能无法达到这些模块的性能。但是,这些服务器重写模块的某些功能不适用于ASP.NET Core项目,例如IIS Rewrite模块的IsFileIsDirectory。在这些情况下,请改用中间件。

包引用

要在项目中使用URL重写中间件,请添加Microsoft.AspNetCore.Rewrite包的引用。该功能适用于ASP.NET Core 1.1或更高版本的应用程序。

配置重写及重定向规则

通过RewriteOptions类实例的扩展方法来建立URL重写和重定向规则,按照你希望处理的顺序将这些规则链接起来,然后通过使用app.UseRewriter(options)将URL重写选项传递到请求管道,以下是几种重写、重定向的配置代码,后面会针对每种配置单独解释:

public void Configure(IApplicationBuilder app)
{
    using (StreamReader apacheModRewriteStreamReader = 
        File.OpenText("ApacheModRewrite.txt"))
    using (StreamReader iisUrlRewriteStreamReader = 
        File.OpenText("IISUrlRewrite.xml")) 
    {
        var options = new RewriteOptions()
            .AddRedirect("redirect-rule/(.*)", "redirected/$1")
            .AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2", 
                skipRemainingRules: true)
            .AddApacheModRewrite(apacheModRewriteStreamReader)
            .AddIISUrlRewrite(iisUrlRewriteStreamReader)
            .Add(MethodRules.RedirectXMLRequests)
            .Add(new RedirectImageRequests(".png", "/png-images"))
            .Add(new RedirectImageRequests(".jpg", "/jpg-images"));

        app.UseRewriter(options);
    }

    app.Run(context => context.Response.WriteAsync(
        $"Rewritten or Redirected Url: " +
        $"{context.Request.Path + context.Request.QueryString}"));
}

URL重定向

使用AddRedirect方法重定向请求,第一个参数为匹配请求URL的正则表达式,第二个参数为替换的文本,第三个参数(如果存在)指定状态码,如果未指定状态码,默认为302(Found)。

public void Configure(IApplicationBuilder app)
{
    var options = new RewriteOptions()
            .AddRedirect("redirect-rule/(.*)", "redirected/$1");

    app.UseRewriter(options);
}

打开浏览器的开发者工具,向/redirect-rule/1234/5678发送一个请求。重定向规则中的正则表达式将匹配请求路径,将路径替换为/redirected/1234/5678,服务端将重定向URL和302(Found)状态代码发送回客户端。客户端基于该URL发送新请求并将该URL显示到地址栏中,然后客户端收到一个200(OK)的响应。

警告:新建重定向规则时一定要谨慎,重定向规则将会对应用每一个请求都进行匹配,包括重定向后的URL。所以很容易不小心创建一个无限重定向循环。

发送一个请求:/redirect-rule/1234/5678,响应如下图:
在这里插入图片描述
重定向规则中正则表达式括号内的部分称为捕获组,表达式中点(.)的含义是匹配任何字符,星号(*)表示匹配之前的字符零次或者多次。因此,URL中最后两段/123/5678被(.*)捕获组所捕获,URL中位于redirect-rule/之后的任何值都将会被该组捕获。

在替换字符串中,捕获组将捕获的内容注入到($n)符号所在位置,其中$后的数字n代表捕获的序列号。第一个捕获组是$1,第二个是$2,以此类推。在上面的例子中,重定向规则中的正则表达式只有一个捕获组,所以替换字符串中只有一个$1,最终/redirect-rule/1234/5678被替换为/redirect-rule/1234/5678

URL重定向到安全站点

可使用AddRedirectToHttps方法将不安全的请求重定向到具有安全HTTPS协议的同一主机和路径,如果未提供状态码参数,中间件将使用默认值302(Found)。如果未提供端口号参数,中间件使用默认值null,这意味着客户端将使用https协议同时从443端口访问资源,下面的代码片段演示如何将重定向状态码设为301(Moved Permanently),同时将端口设为5001:

public void Configure(IApplicationBuilder app)
{
    var options = new RewriteOptions().AddRedirectToHttps(301, 5001);

    app.UseRewriter(options);
}

也可以使用AddRedirectToHttpsPermanent方法将不安全的请求重定向到具有安全HTTPS协议的同一主机和路径(端口443上的https://)。中间件将响应状态码设置为301(Moved Permanently)

public void Configure(IApplicationBuilder app)
{
    var options = new RewriteOptions().AddRedirectToHttpsPermanent();

    app.UseRewriter(options);
}

注意:在不需要其他重定向规则的情况下重定向到HTTPS时,建议使用HTTPS重定向中间件。请参考这里

URL重写

可使用AddRewrite方法创建重写规则,第一个参数为匹配请求URL的正则表达式,第二个参数是替换字符串,第三个参数skipRemainingRules: {true|false},表示如果当前规则生效是否要跳过其它的重写规则。

public void Configure(IApplicationBuilder app)
{
     var options = new RewriteOptions()
            .AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2", skipRemainingRules: true);

     app.UseRewriter(options);
    }
}

发送一个请求:/rewrite-rule/1234/5678,重定向请求及响应如下图:
在这里插入图片描述

我们注意到正则表达式开头是字符^,它的含义是匹配需要从URL路径的开头开始。在之前重定向例子中,正则表达式的开头并没有字符^,因此,路径中redirect-rule/之前的任何字符都可以成功匹配。

路径是否匹配
/redirect-rule/1234/5678
/my-cool-redirect-rule/1234/5678
/anotherredirect-rule/1234/5678

在重写规则中,正则表达式^rewrite-rule/(\d+)/(\d+)仅匹配以rewrite-rule/开头的路径,请注意二者之间的区别:

路径是否匹配
/rewrite-rule/1234/5678
/my-cool-rewrite-rule/1234/5678
/anotherrewrite-rule/1234/5678

在正则表达式^rewrite-rule/(\d+)/(\d+)中有两个捕获组:(\d+)/(\d+)\d表示匹配一个数字,加号(+)表示匹配之前的字符1次或者多次。因此,匹配的URL必须包含一个数字,后跟一个正斜杠,后跟另一个数字。捕获的内容将会被分别注入到重写字符串中的$1$2位置。所以请求URL/rewrite-rule/1234/5678将会被重写为/rewritten?var1=1234&var2=5678。如果原始请求中存在查询字符串,则在重写URL时会保留该查询字符串。URL重写不会有额外的服务器往返。如果资源存在,服务端获取资源内容并返回给客户端200(OK)状态码。因为客户端没有被重定向,所以浏览器地址栏中的地址不会改变。就客户端而言,是感知不到URL重写的。

注意:尽可能使用skipRemainingRules:true参数,因为匹配规则是一个昂贵的过程并增加了应用程序响应时间。为了更快的响应,请考虑以下建议:

将重写规则排序:从最常匹配的规则到最不常匹配的规则
规则匹配成功之后跳过剩余的规则

使用Apache mod_rewrite规则

使用AddApacheModRewrite方法应用Apache mod_rewrite规则,请确保规则文件已随应用程序部署至服务器。了解更多关于Apache mod_rewrite规则,请参考这里

public void Configure(IApplicationBuilder app)
{
   //StreamReader用于从ApacheModRewrite.txt规则文件中读取规则。
    using (StreamReader apacheModRewriteStreamReader = 
        File.OpenText("ApacheModRewrite.txt"))
    {
        var options = new RewriteOptions()
            .AddApacheModRewrite(apacheModRewriteStreamReader);

        app.UseRewriter(options);
    }
}

以下为ApacheModRewrite.txt的内容:

# Rewrite path with additional sub directory
RewriteRule ^/apache-mod-rules-redirect/(.*) /redirected?id=$1 [L,R=302]

示例应用程序将来自/apache-mod-rules-redirect/(.\*)的请求重定向到/redirected?id=$1,响应码为302(Found)。

中间件支持以下Apache mod_rewrite服务器变量:

  • CONN_REMOTE_ADDR
  • HTTP_ACCEPT
  • HTTP_CONNECTION
  • HTTP_COOKIE
  • HTTP_FORWARDED
  • HTTP_HOST
  • HTTP_REFERER
  • HTTP_USER_AGENT
  • HTTPS
  • IPV6
  • QUERY_STRING
  • REMOTE_ADDR
  • REMOTE_PORT
  • REQUEST_FILENAME
  • REQUEST_METHOD
  • REQUEST_SCHEME
  • REQUEST_URI
  • SCRIPT_FILENAME
  • SERVER_ADDR
  • SERVER_PORT
  • SERVER_PROTOCOL
  • TIME
  • TIME_DAY
  • TIME_HOUR
  • TIME_MIN
  • TIME_MON
  • TIME_SEC
  • TIME_WDAY
  • TIME_YEAR

使用IIS URL重写模块规则

使用AddIISUrlRewrite方法应用IIS URL重写规则,请确保规则文件已随应用程序部署至服务器。在Windows Server IIS上运行时,不要让中间件直接使用web.config文件,规格文件应该存储于web.config之外,以避免和IIS重写模块冲突。了解更多关于IIS重写模块的规则,请参考这里和这里。

public void Configure(IApplicationBuilder app)
{
  //StreamReader用于从IISUrlRewrite.xml规则文件中读取规则
    using (StreamReader iisUrlRewriteStreamReader = 
        File.OpenText("IISUrlRewrite.xml")) 
    {
        var options = new RewriteOptions()
            .AddIISUrlRewrite(iisUrlRewriteStreamReader);

        app.UseRewriter(options);
    }
} 

以下为IISUrlRewrite.xml的内容:

<rewrite>
  <rules>
    <rule name="Rewrite segment to id querystring" stopProcessing="true">
      <match url="^iis-rules-rewrite/(.*)$" />
      <action type="Rewrite" url="rewritten?id={R:1}" appendQueryString="false"/>
    </rule>
  </rules>
</rewrite>

示例应用程序将来自/iis-rules-rewrite/(.*)的请求重写为/rewritten?id=$1,响应码为200(OK)。

ASP.NET Core 2.x发布的中间件不支持以下IIS URL重写模块功能:

  • Outbound Rules
  • Custom Server Variables
  • Wildcards
  • LogRewrittenUrl

中间件支持以下IIS URL重写模块服务器变量:

  • CONTENT_LENGTH
  • CONTENT_TYPE
  • HTTP_ACCEPT
  • HTTP_CONNECTION
  • HTTP_COOKIE
  • HTTP_HOST
  • HTTP_REFERER
  • HTTP_URL
  • HTTP_USER_AGENT
  • HTTPS
  • LOCAL_ADDR
  • QUERY_STRING
  • REMOTE_ADDR
  • REMOTE_PORT
  • REQUEST_FILENAME
  • REQUEST_URI

注意:可以通过PhysicalFileProvider类获取IFileProvider。这种方法可以为重写规则文件的位置提供更大的灵活性。

PhysicalFileProvider fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());

基于方法的规则

使用Add(Action<RewriteContext> applyRule)在方法中实现自己的规则逻辑,RewriteContext公开HttpContext以方便在方法中使用,而context.Result决定了如何进行后续的管道处理。如下表:

context.Result行为
RuleResult.ContinueRules(默认行为)继续应用后续规则
RuleResult.EndResponse停止应用规则并发送响应
RuleResult.SkipRemainingRules停止应用规则并发送上下文(HttpContext)至下个中间件
public void Configure(IApplicationBuilder app)
{
    var options = new RewriteOptions()
            .Add(MethodRules.RedirectXMLRequests);

        app.UseRewriter(options);
}

//自定义的规则方法
public static void RedirectXMLRequests(RewriteContext context)
{
    var request = context.HttpContext.Request;

    // Because we're redirecting back to the same app, stop 
    // processing if the request has already been redirected
    if (request.Path.StartsWithSegments(new PathString("/xmlfiles")))
    {
        return;
    }

    if (request.Path.Value.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
    {
        var response = context.HttpContext.Response;
        response.StatusCode = StatusCodes.Status301MovedPermanently;
        context.Result = RuleResult.EndResponse;
        response.Headers[HeaderNames.Location] = 
            "/xmlfiles" + request.Path + request.QueryString;
    }
}

示例应用程序演示了将.xml结尾的请求路径重定向的自定义逻辑方法。如果对/file.xml发出请求,则会将其重定向到/xmlfiles/file.xml。响应码被设置为301 (Moved Permanently)。对于重定向来说,你必须显式指定响应的状态码,否则响应码将被默认为200(OK)且客户端也不会发生重定向。

发送一个请求:/file.xml,响应如下图:
在这里插入图片描述

基于IRule接口的规则

使用Add(IRule)在从IRule派生的类中实现您自己的规则逻辑。使用IRule的方式比使用基于方法的规则方法具有更好的灵活性,派生类可以包含构造函数,您可以在其中传递ApplyRule方法的参数。

public void Configure(IApplicationBuilder app)
{
   var options = new RewriteOptions()
           .Add(new RedirectImageRequests(".jpg", "/jpg-images"));

        app.UseRewriter(options);
}
public class RedirectImageRequests : IRule
{
    private readonly string _extension;
    private readonly PathString _newPath;

    public RedirectImageRequests(string extension, string newPath)
    {
        //此处省略了参数校验

        _extension = extension;
        _newPath = new PathString(newPath);
    }

    public void ApplyRule(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        if (request.Path.StartsWithSegments(new PathString(_newPath)))
        {
            return;
        }

        if (request.Path.Value.EndsWith(_extension, StringComparison.OrdinalIgnoreCase))
        {
            var response = context.HttpContext.Response;
            response.StatusCode = StatusCodes.Status301MovedPermanently;
            context.Result = RuleResult.EndResponse;
            response.Headers[HeaderNames.Location] = 
                _newPath + request.Path + request.QueryString;
        }
    }
}

发送一个请求:/image.png,响应内容如下:
在这里插入图片描述


ASP.NET Core中使用URL重写

ASP.NET Core 1.1 Preview 1 中新增了 URL Rewriting middleware ,终于可以进行 URL 重写了,实际使用体验一下。

首先要将 ASP.NET Core 项目升级至 .NET Core 1.1 Preview 1(参考 .NET跨平台之旅:将示例站点升级至 .NET Core 1.1 Preview 1 ),然后在 project.json 添加 “Microsoft.AspNetCore.Rewrite” 的引用并运行 donet restore 安装对应的nuget包。

"dependencies": {
    "Microsoft.AspNetCore.Rewrite": "1.0.0-preview1-final"
}

接着在 Startup.cs 的 Configure() 方法中通过 app.UseRewriter() 配置重写规则,比如要将 http://about.cnblogs.com/ 重写为 http://about.cnblogs.com/about/intro ,可以这么写:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IHostingEnvironment env)
{
    app.UseRewriter(new RewriteOptions().AddRewrite("^$", "about/intro", true));
}

AddRewrite的第1个参数("^$")是匹配重写前的URL的正则表达式,第2个参数("about/intro")是重写后的URL,第3个参数(true)表示匹配成功1条规则后是否放弃后续规则的匹配。

对于URL匹配,还是微软的老规矩,URL的开头没有斜杠,我个人比较喜欢以斜杠开头:

//不支持的写法
app.UseRewriter(new RewriteOptions().AddRewrite("^/$", "/about/intro", true));

需要注意的是 app.UseRewriter() 要放在 app.UseMvc(),app.UseMvcWithDefaultRoute(),app.UseRouter() 之前。


ASP.NET Core 2 学习笔记(八)URL重写

路由跟URL 重写的功能性略有不同。路由是将Request 找到对应的服务,而URL 重写是为了推卸责任转送Request
本篇将简单介绍下ASP.NET Core的URL重写(URL Rewrite)。

URL Rewrite 注册

URL Rewriting Middleware需要Microsoft.AspNetCore.Rewrite套件。
ASP.NET Core 2.0以上版本,预设是参考Microsoft.AspNetCore.All,已经包含Microsoft.AspNetCore.Rewrite,所以不用再安装。

要使用URL重写,在Startup.csConfigureIApplicationBuilder使用UseRewriter方法注册URL Rewriting Middleware

Startup.cs

// ...

public class Startup
{

    public void Configure(IApplicationBuilder app)
    {

        var rewrite = new RewriteOptions()
            .AddRewrite("about.aspx", "home/about", skipRemainingRules: true)
            .AddRedirect("first", "home/index");
        app.UseRewriter(rewrite);
        // ...

    }

}

通过RewriteOptions建立URL重写规则后,传入给URL Rewriting Middleware

URL重写规则,主要有分两种方式:

  • URL重写(URL Rewrite)
    上例的AddRewrite就是URL重写。
  • URL转址(URL Redirect)
    上例的AddRedirect就是URL转址。

URL 重写

URL 重写是属于Server 端的转换事件,当ClientRequest 来的时候,发现原网址已经被换掉了,就会自动回传新网址的内容。情境如下:
在这里插入图片描述
上例AddRewrite有用到三个参数,当URL符合参数1时,就将参数2路由的内容回传给Client
而参数3是用来加速URL匹配的参数,类似switchbreak。若将skipRemainingRules设为true,当找到匹配条件,就不再继续往下找符合其他参数1的规则。

  • 参数1支持正则表达式(Regular Expressions)。
    范例结果:
    在这里插入图片描述

URL 转址

URL 转址是属于Client 端的转换事件,当ClientRequest 来的时候,发现原网址已经被换掉了,Server 会先回传给Client 告知新网址,再由Client 重新Request 新网址。情境如下
在这里插入图片描述
AddRedirect的使用方式类似AddRewrite,当URL符合参数1时,就会回传参数2的URL给Client

  • 参数1同样支持正则表达式(Regular Expressions)。
    URL转址预设都是回传HTTP Status Code 302,也可以在参数3指定回传的HTTP Status Code
    通常转址的HTTP Status Code都是用301或302,URL转址对“人”的行为来说没有什么意义,反正就是帮忙从A转到B;主要差异是给“搜索引擎”理解的。

Startup.cs

// ...

public class Startup

{

    public void Configure(IApplicationBuilder app)

    {

        var rewrite = new RewriteOptions()

            .AddRedirect("first", "home/index", 301);

        app.UseRewriter(rewrite);

        // ...

    }

}
  • HTTP Status Code 301
    301是要让搜索引擎知道,该网址已经永久转移到另一个地方。通常用于网站搬家或网站改版,新旧版本路径不相同,要重新对应的情况。
    范例结果:
    在这里插入图片描述
  • HTTP Status Code 302
    302是告知搜索引擎,虽然这次被转址,但只是暂时性的。通常用于网站维护时,暂时原网址转移到别的地方,如维护公告页面。
    范例结果:
    在这里插入图片描述

正则表达式

AddRewriteAddRedirect都支持正则表达式的使用,且能把来源的URL通过正则表达式变成参数,带入新URL。

Startup.cs

// ...
public class Startup
{

    public void Configure(IApplicationBuilder app)
    {

        var rewrite = new RewriteOptions()
            .AddRedirect(@"products.aspx?id=(\w+)", "prosucts/$1", 301)
            .AddRedirect(@"api/(.*)/(.*)/(.*)", "api?p1=$1&p2=$2&p3=$3", 301);
        app.UseRewriter(rewrite);
        // ...
    }
}
  • 当连到http://localhost:5000/products.aspx?id=p123
    转址到http://localhost:5000/products/p123
  • 当连到http://localhost:5000/api/first/second/third
    转址到http://localhost:5000/api?p1=first&p2=second&p3=third

通过正则表达式做URL 转址,对于网站新旧改版来说,非常好用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值