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. 状态码的含义请参考这里 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html .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 模块的 IsFile 和 IsDirectory. 在这些情况下, 请改用中间件.
包引用
要在项目中使用 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 重定向中间件. 请参考这里 https://docs.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-2.1#require-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 规则, 请参考这里 https://httpd.apache.org/docs/2.4/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 重写模块的规则, 请参考这里 https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/using-url-rewrite-module-20 和这里 https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-configuration-reference .
- 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, 响应内容如下:
尾语
本来主要内容来自于微软官方英文文档, 点击这里查看原文 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-2.1&tabs=aspnetcore2x , 在翻译过程中, 对有些句式和词汇进行了加工, 以期望更加流畅和符合我们的阅读习惯, 其中应该有不少不得体的地方, 还请各位大神见谅并指出, 如有一丝帮助, 万分荣幸.
来源: https://www.cnblogs.com/luohelc/p/url_rewrite.html