Note
此版本不是本文的最新版本。 有关当前版本,请参阅 本文的 .NET 10 版本。
Warning
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅 本文的 .NET 10 版本。
作者:Kirk Larkin 和 Rick Anderson
本文介绍 URL 重写,并介绍了如何在 ASP.NET Core 应用中使用 URL 重写中间件。
URL 重写是根据一个或多个预定义规则修改请求 URL 的行为。 URL 重写会在资源位置和地址之间创建一个抽象,使位置和地址不紧密相连。 在以下几种方案中,URL 重写很有价值:
- 暂时或永久移动或替换服务器资源,并维护这些资源的稳定定位符。
- 将请求处理拆分到不同应用中,或同一应用的不同区域中。
- 删除、添加或重新组织传入请求上的 URL 段。
- 为搜索引擎优化 (SEO) 优化公开 URL。
- 允许使用友好的公共 URL 来帮助访问者预测请求资源后返回的内容。
- 将不安全请求重定向到安全终结点。
- 防止热链接,外部站点会通过热链接将其他站点的资产链接到其自己的内容,从而利用托管在其他站点上的静态资产。
URL 重写可能会降低应用的性能。 请限制规则的数量和复杂度。
URL 重定向和 URL 重写
URL 重定向和 URL 重写之间的用词差异很细微,但这对于向客户端提供资源具有重要意义 。 ASP.NET Core的 URL 重写中间件能够满足两者的需求。
URL 重定向涉及客户端操作,指示客户端访问与客户端最初请求地址不同的资源。 这需要与服务器进行一次往返通信。 客户端对资源发出新请求时,返回客户端的重定向 URL 会出现在浏览器地址栏。
如果 /resource 被重定向到 ,则服务器作出响应,指示客户端应在 /different-resource 获取资源,所提供的状态代码指示重定向是临时的还是永久的。
将请求重定向到不同 URL 时,通过使用响应指定状态代码来指示重定向是永久还是临时:
如果资源有一个新的永久性 URL,并且所有将来的资源请求都应使用新的 URL,则使用
301 - Moved Permanently状态代码。 收到 301 状态代码时,客户端可能会缓存响应并重用这段代码。如果重定向是临时的或一般情况下会更改的,则使用
302 - Found状态代码。 302 状态码表示客户端不应存储该 URL,也不应在将来使用它。
有关状态代码的详细信息,请参阅 RFC 9110:状态代码定义。
URL 重写是一种服务器端操作,它会从不同于客户端所请求资源地址的另一个资源地址提供资源。 重写 URL 不需要往返服务器。 重写的 URL 不会返回客户端,也不会出现在浏览器地址栏。
如果 /resource 被重写为 /different-resource,服务器会内部获取并返回位于 /different-resource 的资源。
尽管客户端可能能够检索已重写 URL 处的资源,但是,客户端发出请求并收到响应时,并不知道已重写 URL 处存在的资源。
URL 重写示例应用
使用 示例应用探索 URL 重写中间件的功能。 该应用程序会应用重定向和重写规则,并显示在几种场景下重定向后或重写后的 URL。
何时使用 URL 重写中间件
如果以下方法不令人满意,请使用 URL 重写中间件:
如果应用是托管在 HTTP.sys 服务器上,请使用 URL 重写中间件。
使用 IIS、Apache 和 Nginx 中的基于服务器的 URL 重写技术的主要原因:
中间件不支持这些模块的完整功能。
服务器模块的一些功能不适用于 ASP.NET Core 项目,例如 IIS 重写模块的
IsFile和IsDirectory约束。 在这些情况下,请改为使用中间件。中间件性能与模块性能不匹配。
基准测试是能够确定地知道哪种方法对性能的降低影响最大,或者降低的性能是否可忽略不计的唯一方法。
扩展和选项
通过使用扩展方法为每条重写规则创建 RewriteOptions 类的一个实例,建立 URL 重写和重定向规则。 将多个规则按照其应被处理的顺序串联起来。 使用 UseRewriter 将 RewriteOptions 添加到请求管道时,它会被传递到 URL 重写中间件:
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
在上述代码中,MethodRules 是用户定义的类。 有关详细信息,请参阅本文中的 RewriteRules.cs。
授权和 URL 重写
当规则更改请求路径时,路由将评估新路径,并且可以选择与原始路径匹配的终结点不同的终结点。 授权必须对最终选择的端点运行。 否则,不会应用该终结点的授权要求。
调用 UseRewriter 并使用授权保护任何端点的应用必须显式添加身份验证和授权中间件,并且必须在 UseRewriter 之后添加它:
app.UseRewriter(options);
app.UseAuthentication();
app.UseAuthorization();
当应用显式调用 UseRouting 时,请使用以下顺序:
app.UseRewriter(options);
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
以下两种配置针对为原始路径选择的终结点(而不是重写路径)评估授权:
- 依赖于在注册相应服务时自动添加的身份验证和授权中间件,而无需调用
UseAuthentication和UseAuthorization。 - 在调用
UseRewriter之前调用UseAuthentication和UseAuthorization。
Important
如前所示,在 UseRewriter 之后显式添加并按顺序排列身份验证和授权中间件。 使应用保持更新到最新支持的修补程序版本,以确保框架的内置授权行为包括最新的修补程序。
将不带 www 的域名重定向到带 www 的域名
有三个选项可允许应用程序将非 www 请求重定向到 www:
AddRedirectToWwwPermanent:如果请求是非
www,则将请求永久重定向到www子域。 使用 Status308PermanentRedirect 状态代码进行重定向。AddRedirectToWww:如果传入请求是非
www,则将请求重定向到www子域。 使用 Status307TemporaryRedirect 状态代码进行重定向。 重载允许提供响应状态代码。 使用 StatusCodes 类的字段实现状态代码分配。
URL 重定向
使用 AddRedirect 将请求重定向。 第一个参数包含用于匹配传入 URL 路径的 .NET 正则表达式 (Regex)。 第二个参数是替换字符串。 第三个参数(如有)指定状态代码。 如果没有指定状态代码,则状态代码默认为 302 - 已找到,指示资源暂时移动或替换。
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
在启用了开发人员工具的浏览器中,向路径为 /redirect-rule/1234/5678 的示例应用发出请求。 正则表达式匹配 redirect-rule/(.*) 上的请求路径,且该路径会被 /redirected/1234/5678 替代。 重定向 URL 以“302 (已找到)”状态代码发回客户端。 浏览器会在浏览器地址栏中出现的重定向 URL 上发出新请求。 由于示例应用中没有任何规则与重定向 URL 匹配:
- 第二个请求从应用收到200 - OK响应。
- 响应正文显示了重定向 URL。
当 URL 被重定向时,会发生一次与服务器之间的往返。
Warning
建立重定向规则时务必小心。 系统会根据应用的每个请求(包括重定向后的请求)对重定向规则进行评估。 很容易意外造成无限重定向循环。
括号内的表达式部分称为“捕获组”。 表达式中的点号(.)表示匹配任意字符。 星号 (*) 表示 将前面的字符匹配零次或多次。 因此,URL 的最后两个路径段 1234/5678 由捕获组 (.*) 捕获。 在请求 URL 中提供的位于 redirect-rule/ 之后的任何值均由此单个捕获组捕获。
在替换字符串中,将捕获组注入带有美元符号 ($)、后跟捕获序列号的字符串中。 第一个捕获组的值通过 $1 获取,第二个通过 $2 获取,正则表达式中的其余捕获组也依此顺序类推。
redirect-rule/(.*) 的重定向规则正则表达式中只有一个捕获组,因此替换字符串中只有一个注入组,即 $1。 如果应用此规则,URL 将变为 /redirected/1234/5678。
尝试在浏览器工具的“网络”选项卡上使用 /redirect-rule/1234/5678。
重定向规则的安全性
中间件扩展替换字符串中的捕获组和变量,然后将结果解释为重定向目标。 像 (.*) 这样的宽泛捕获,在受信任的本地前缀将目标限制住的情况下,本身并不一定不安全。 当请求派生的文本可以控制完整目标、构造 scheme:// 前缀或确定绝对 URI 的颁发机构(主机)时,规则不安全。
// Unsafe: $1 can supply a complete redirect destination.
options.AddRedirect("legacy/(.*)", "$1");
// Safe: The trusted prefix keeps the destination within the app.
options.AddRedirect("legacy/(.*)", "/archive/$1");
// Safe: The scheme and authority are fixed.
options.AddRedirect("legacy/(.*)", "https://example.com/$1");
// Unsafe unless $1 is validated against an allow list of hosts.
options.AddRedirect("go/(.*)", "https://$1/path");
在应用中重定向时,请使用固定的本地前缀。 对于外部重定向,请使用固定主机或针对允许列表验证主机。 已导入的 IIS URL Rewrite 和 Apache mod_rewrite 规则也会展开反向引用和变量。 导入规则不会验证其生成的替换,因此请使用相同的条件查看导入的规则。
具体取决于 API、导入规则选项以及替换设置,原始查询字符串可能会被保留,或追加到目标地址中。 在审查源自请求的数据是否会影响重定向时,应将查询参数考虑在内。 有关验证重定向目标的详细信息,请参阅阻止 ASP.NET Core中的开放重定向攻击。
URL 重定向到安全端点
使用 AddRedirectToHttps 将 HTTP 请求重定向到采用 HTTPS 协议的相同主机和路径。 如不提供状态代码,则中间件默认为“302(已找到)”。 如果未提供端口:
- 中间件默认为
null。 - 方案更改为
https(HTTPS 协议),客户端访问端口 443 上的资源。
下面的示例展示了如何将状态代码设置为 301 - Moved Permanently,以及如何将端口更改为 Kestrel 在 localhost 上使用的 HTTPS 端口。 在生产环境中,HTTPS 端口设置为 null:
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
int? localhostHTTPSport = null;
if (app.Environment.IsDevelopment())
{
localhostHTTPSport = Int32.Parse(Environment.GetEnvironmentVariable(
"ASPNETCORE_URLS")!.Split(new Char[] { ':', ';' })[2]);
}
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
// localhostHTTPport not needed for production, used only with localhost.
.AddRedirectToHttps(301, localhostHTTPSport)
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
使用 AddRedirectToHttpsPermanent 将不安全的请求重定向到端口 443 上的采用安全 HTTPS 协议的相同主机和路径。 中间件将状态代码设置为 301 - Moved Permanently。
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
Note
当重定向到安全终结点时,无需其他重定向规则,我们建议使用 HTTPS 重定向中间件。 有关详细信息,请参阅强制使用 HTTPS。
此示例应用演示了如何使用 AddRedirectToHttps 或 AddRedirectToHttpsPermanent。 在 http://redirect6.azurewebsites.net/iis-rules-rewrite/xyz 向应用发出不安全的 HTTP 请求。 在测试 localhost 上的 HTTP 到 HTTPS 重定向时:
- 使用 HTTP URL,它具有与 HTTPS URL 不同的端口。 HTTP URL 位于
Properties/launchSettings.json文件中。 - 从
s删除https://localhost/{port}会失败,因为 localhost 不会对 HTTPS 端口做出 HTTP 响应。
下图显示了使用前面的代码请求 http://redirect6.azurewebsites.net/iis-rules-rewrite/xyz 的 F12 浏览器工具的图像:
URL 重写
使用 AddRewrite 创建重写 URL 的规则。 第一个参数包含用于匹配传入 URL 路径的正则表达式。 第二个参数是替换字符串。 第三个参数 skipRemainingRules: {true|false} 指示如果当前规则适用,中间件是否要跳过其他重写规则。
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
尝试将请求发送到 https://redirect6.azurewebsites.net/rewrite-rule/1234/5678
表达式开头的脱字号 (^) 意味着匹配从 URL 路径的开头处开始。
在前面的重定向规则 redirect-rule/(.*) 的示例中,正则表达式的开头没有脱字号 (^)。 因此,路径中 redirect-rule/ 之前的任何字符都可能成功匹配。
| Path | Match |
|---|---|
/redirect-rule/1234/5678 |
Yes |
/my-cool-redirect-rule/1234/5678 |
Yes |
/anotherredirect-rule/1234/5678 |
Yes |
重写规则 ^rewrite-rule/(\d+)/(\d+) 只能与以 rewrite-rule/ 开头的路径匹配。 注意下表中的匹配差异。
| Path | Match |
|---|---|
/rewrite-rule/1234/5678 |
Yes |
/my-cool-rewrite-rule/1234/5678 |
No |
/anotherrewrite-rule/1234/5678 |
No |
在表达式的 ^rewrite-rule/ 部分之后,有两个捕获组 (\d+)/(\d+)。
\d 表示匹配一个数字。 加号 (+) 表示 匹配前面的字符一次或多次。 因此,URL 必须包含数字加正斜杠加另一个数字的形式。 这些捕获组以 $1 和 $2 的形式注入重写 URL 中。 重写规则替换字符串将捕获组放入查询字符串中。 重写 /rewrite-rule/1234/5678 的请求路径,返回 /rewritten?var1=1234&var2=5678 处的资源。 如果原始请求中存在查询字符串,则重写 URL 时会保留此字符串。
无需往返服务器来返回资源。 如果资源存在,系统会提取资源并以“200(正常)”状态代码返回给客户端。 因为客户端不会被重定向,所以浏览器地址栏中的 URL 不会发生更改。 客户端无法检测到服务器上发生的 URL 重写操作。
URL 重写和重定向的性能提示
若要获得最快的响应速度:
- 按照从最频繁匹配的规则到最不频繁匹配的规则排列重写规则。
- 尽可能使用
skipRemainingRules: true,因为匹配规则在计算上很昂贵并且增加了应用响应时间。 如果出现匹配项且无需处理任何其他规则,则跳过剩余规则的处理。
Warning
恶意用户可以提供难以处理的输入到 RegularExpressions,从而导致 拒绝服务攻击。 ASP.NET Core 框架 API 使用 RegularExpressions 实现超时机制。 例如,_regexTimeout和 RedirectRule的正则表达式超时时间 () 均设置为 1 秒。
Note
指向 .NET 引用源的文档链接通常会加载存储库的默认分支,该分支代表正在进行的 .NET 下一版本的开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉菜单。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
Apache mod_rewrite
使用 AddApacheModRewrite 应用 Apache mod_rewrite 规则。 请确保将规则文件与应用一起部署。 有关 mod_rewrite 规则的详细信息和示例,请参阅 Apache mod_rewrite。
StreamReader 用于读取 ApacheModRewrite.txt 规则文件中的规则:
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
示例应用将请求从 /apache-mod-rules-redirect/(.\*) 重定向到 /redirected?id=$1。 响应状态代码为“302 (已找到)”。
# Rewrite path with additional sub directory
RewriteRule ^/apache-mod-rules-redirect/(.*) /redirected?id=$1 [L,R=302]
尝试将请求发送到 https://redirect6.azurewebsites.net/apache-mod-rules-redirect/1234
Apache 中间件支持下列 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
Note
指向 .NET 引用源的文档链接通常会加载存储库的默认分支,该分支代表正在进行的 .NET 下一版本的开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉菜单。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
IIS URL 重写模块规则
若要使用适用于 IIS URL 重写模块的同一规则集,使用 AddIISUrlRewrite。 请确保将规则文件与应用一起部署。 当在 Windows Server IIS 上运行时,请勿指示中间件使用应用的 web.config 文件。 使用 IIS 时,应将这些规则存储在应用的 web.config 文件之外,以避免与 IIS 重写模块发生冲突。 有关 IIS URL 重写模块规则的详细信息和示例,请参阅 Using Url Rewrite Module 2.0(使用 URL 重写模块 2.0)和 URL Rewrite Module Configuration Reference(URL 重写模块配置引用)。
StreamReader 用于读取 IISUrlRewrite.xml 规则文件中的规则:
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
示例应用将请求从 /iis-rules-rewrite/(.*) 重写为 /rewritten?id=$1。 以“200 (正常)”状态代码作为响应发送到客户端。
<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>
尝试将请求发送到 https://redirect6.azurewebsites.net/iis-rules-rewrite/xyz
如果应用具有配置了服务器级别规则的活动 IIS 重写模块,会对应用产生不利影响:
- 请考虑禁用应用的 IIS 重写模块。
- 有关详细信息,请参阅禁用 IIS 模块。
不支持的功能
中间件不支持以下 IIS URL 重写模块功能:
- 出站规则
- 自定义服务器变量
- 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
Note
指向 .NET 引用源的文档链接通常会加载存储库的默认分支,该分支代表正在进行的 .NET 下一版本的开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉菜单。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)。
IFileProvider 可通过 PhysicalFileProvider 获取。 这种方法可为重写规则文件的位置提供更大的灵活性。 请确保将重写规则文件部署到所提供路径的服务器中。
var fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());
基于方法的规则
使用 Add 在方法中实现自定义规则逻辑。
Add 公开 RewriteContext,这使 HttpContext 可用于重定向方法。
RewriteContext.Result 属性决定了如何处理其他管道进程。 将值设置为下表中的 RuleResult 字段之一。
| 重写上下文结果 | Action |
|---|---|
RuleResult.ContinueRules(默认值) |
继续应用规则。 |
RuleResult.EndResponse |
停止应用规则并发送响应。 |
RuleResult.SkipRemainingRules |
停止应用规则并将上下文发送给下一个中间件。 |
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
示例应用演示了如何对以 .xml 结尾的路径的请求进行重定向。 向 /file.xml 发出请求时:
- 请求被重定向到
/xmlfiles/file.xml - 状态代码设置为
301 - Moved Permanently。 当浏览器发出新请求/xmlfiles/file.xml时,静态文件中间件会从 wwwroot/xmlfiles 文件夹向客户端提供服务。 对于重定向,请显式设置响应的状态代码。 否则,将返回 200 - OK 状态代码,并且不会在客户端发生重定向。
RewriteRules.cs:
public static void RedirectXmlFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
// Because the client is redirecting back to the same app, stop
// processing if the request has already been redirected.
if (request.Path.StartsWithSegments(new PathString("/xmlfiles")) ||
request.Path.Value==null)
{
return;
}
if (request.Path.Value.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = (int) HttpStatusCode.MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
"/xmlfiles" + request.Path + request.QueryString;
}
}
此方法还可以重写请求。 示例应用演示了如何重写任何文本文件请求的路径以从 wwwroot 文件夹中提供 file.txt 文本文件 。 静态文件中间件根据更新的请求路径为文件提供服务:
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
RewriteRules.cs:
public static void RewriteTextFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
if (request.Path.Value != null &&
request.Path.Value.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
context.Result = RuleResult.SkipRemainingRules;
request.Path = "/file.txt";
}
}
基于 IRule 的规则
使用 Add 在实现 IRule 接口的类中使用规则逻辑。 与使用基于方法的规则方法相比,IRule 提供了更大的灵活性。 实现类可能包含构造函数,可在其中传入 ApplyRule 方法的参数。
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
检查示例应用中 extension 和 newPath 的参数值是否符合多个条件。
extension 必须包含一个值,并且该值必须是 .png、.jpg 或 .gif。 如果 newPath 无效,则会引发 ArgumentException。 如果发出针对 image.png 的请求,请求将重定向到 /png-images/image.png。 如果发出针对 image.jpg 的请求,请求将重定向到 /jpg-images/image.jpg。 状态代码设置为 301 - Moved Permanently,context.Result 设置为停止处理规则并发送响应。
public class RedirectImageRequests : IRule
{
private readonly string _extension;
private readonly PathString _newPath;
public RedirectImageRequests(string extension, string newPath)
{
if (string.IsNullOrEmpty(extension))
{
throw new ArgumentException(nameof(extension));
}
if (!Regex.IsMatch(extension, @"^\.(png|jpg|gif)$"))
{
throw new ArgumentException("Invalid extension", nameof(extension));
}
if (!Regex.IsMatch(newPath, @"(/[A-Za-z0-9]+)+?"))
{
throw new ArgumentException("Invalid path", nameof(newPath));
}
_extension = extension;
_newPath = new PathString(newPath);
}
public void ApplyRule(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(_newPath)) ||
request.Path.Value == null)
{
return;
}
if (request.Path.Value.EndsWith(_extension, StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = (int) HttpStatusCode.MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
_newPath + request.Path + request.QueryString;
}
}
}
Try:
- PNG 请求:
https://redirect6.azurewebsites.net/image.png - JPG 请求:
https://redirect6.azurewebsites.net/image.jpg
正则表达式示例
| Goal | 正则表达式字符串 & 匹配示例 |
替换字符串 & 输出示例 |
|---|---|---|
| 将路径重写为查询字符串 | ^path/(.*)/(.*)/path/abc/123 |
path?var1=$1&var2=$2/path?var1=abc&var2=123 |
| 去掉尾部反斜杠 | ^path2/(.*)/$/path2/xyz/ |
$1/path2/xyz |
| 强制添加尾部反斜杠 | ^path3/(.*[^/])$/path3/xyz |
$1//path3/xyz/ |
| 避免重写特定请求 |
^(.*)(?<!\.axd)$ 或 ^(?!.*\.axd$)(.*)$是: /path4/resource.htm不: /path4/resource.axd |
rewritten/$1/rewritten/resource.htm/resource.axd |
| 重新排列 URL 段 | path5/(.*)/(.*)/(.*)path5/1/2/3 |
path5/$3/$2/$1path5/3/2/1 |
| 替换 URL 段 | ^path6/(.*)/segment2/(.*)^path6/segment1/segment2/segment3 |
path6/$1/replaced/$2/path6/segment1/replaced/segment3 |
上表中的链接使用部署到 Azure 的以下代码:
using Microsoft.AspNetCore.Rewrite;
using RewriteRules;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
using (StreamReader apacheModRewriteStreamReader =
File.OpenText("ApacheModRewrite.txt"))
using (StreamReader iisUrlRewriteStreamReader =
File.OpenText("IISUrlRewrite.xml"))
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent()
.AddRedirect("redirect-rule/(.*)", "redirected/$1")
.AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
skipRemainingRules: true)
// Rewrite path to QS.
.AddRewrite(@"^path/(.*)/(.*)", "path?var1=$1&var2=$2",
skipRemainingRules: true)
// Skip trailing slash.
.AddRewrite(@"^path2/(.*)/$", "path2/$1",
skipRemainingRules: true)
// Enforce trailing slash.
.AddRewrite(@"^path3/(.*[^/])$", "path3/$1/",
skipRemainingRules: true)
// Avoid rewriting specific requests.
.AddRewrite(@"^path4/(.*)(?<!\.axd)$", "rewritten/$1",
skipRemainingRules: true)
// Rearrange URL segments
.AddRewrite(@"^path5/(.*)/(.*)/(.*)", "path5/$3/$2/$1",
skipRemainingRules: true)
// Replace a URL segment
.AddRewrite(@"^path6/(.*)/segment2/(.*)", "path6/$1/replaced/$2",
skipRemainingRules: true)
.AddApacheModRewrite(apacheModRewriteStreamReader)
.AddIISUrlRewrite(iisUrlRewriteStreamReader)
.Add(MethodRules.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
app.Run();
在前面的大多数正则表达式示例中,文本 path 用于为部署的示例提供独特的可测试重写规则。 通常,正则表达式不包括 path。 例如,请参阅下面的正则表达式示例表。
本文档介绍 URL 重写,并说明如何在 ASP.NET Core 应用中使用 URL 重写中间件。
URL 重写是根据一个或多个预定义规则修改请求 URL 的行为。 URL 重写会在资源位置和地址之间创建一个抽象,使位置和地址不紧密相连。 在以下几种方案中,URL 重写很有价值:
- 暂时或永久移动或替换服务器资源,并维护这些资源的稳定定位符。
- 将请求处理拆分到不同应用中,或同一应用的不同区域中。
- 删除、添加或重新组织传入请求上的 URL 段。
- 为搜索引擎优化 (SEO) 优化公开 URL。
- 允许使用友好的公共 URL 来帮助访问者预测请求资源后返回的内容。
- 将不安全请求重定向到安全终结点。
- 防止热链接,外部站点会通过热链接将其他站点的资产链接到其自己的内容,从而利用托管在其他站点上的静态资产。
Note
URL 重写可能会降低应用的性能。 如果可行,应限制规则的数量和复杂度。
URL 重定向和 URL 重写
URL 重定向和 URL 重写之间的用词差异很细微,但这对于向客户端提供资源具有重要意义 。 ASP.NET Core的 URL 重写中间件能够满足两者的需求。
URL 重定向涉及客户端操作,指示客户端访问与客户端最初请求地址不同的资源。 这需要与服务器进行一次往返通信。 客户端对资源发出新请求时,返回客户端的重定向 URL 会出现在浏览器地址栏。
如果 /resource 被重定向到 ,则服务器作出响应,指示客户端应在 /different-resource 获取资源,所提供的状态代码指示重定向是临时的还是永久的。
将请求重定向到不同 URL 时,通过使用响应指定状态代码来指示重定向是永久还是临时:
如果资源有一个新的永久性 URL,并且你希望指示客户端所有将来的资源请求都使用新 URL,则使用
301 - Moved Permanently状态代码。 收到 301 状态代码时,客户端可能会缓存响应并重用这段代码。302 - 已找到状态代码用于重定向是临时的或通常可能会发生变化的情况。 302 状态码表示客户端不应存储该 URL,也不应在将来使用它。
有关状态代码的详细信息,请参阅 RFC 9110:状态代码定义。
URL 重写是一种服务器端操作,它会从不同于客户端所请求资源地址的另一个资源地址提供资源。 重写 URL 不需要往返服务器。 重写的 URL 不会返回客户端,也不会出现在浏览器地址栏。
如果 /resource 被重写为 /different-resource,服务器会内部获取并返回位于 /different-resource 的资源。
尽管客户端可能能够检索已重写 URL 处的资源,但是,客户端发出请求并收到响应时,并不知道已重写 URL 处存在的资源。
URL 重写示例应用
可以使用 示例应用浏览 URL 重写中间件的功能。 该应用程序会应用重定向和重写规则,并显示在几种场景下重定向后或重写后的 URL。
何时使用 URL 重写中间件
如果无法使用以下方法,请使用 URL 重写中间件:
如果应用是托管在 HTTP.sys 服务器上,请使用 URL 重写中间件。
使用 IIS、Apache 和 Nginx 中的基于服务器的 URL 重写技术的主要原因:
中间件不支持这些模块的完整功能。
服务器模块的一些功能不适用于 ASP.NET Core 项目,例如 IIS 重写模块的
IsFile和IsDirectory约束。 在这些情况下,请改为使用中间件。中间件性能与模块性能不匹配。
基准测试是确定哪种方法会最大程度降低性能或降低的性能是否可忽略不计的唯一方法。
Package
URL 重写中间件由Microsoft提供。AspNetCore.Rewrite 包,该包隐式包含在 ASP.NET Core应用中。
扩展和选项
通过使用扩展方法为每条重写规则创建 RewriteOptions 类的实例,建立 URL 重写和重写定向规则。 按所需的处理顺序将多个规则串联起来。 使用 UseRewriter 将 RewriteOptions 添加到请求管道时,它会被传递到 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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
将不带 www 的域名重定向到带 www 的域名
有三个选项可允许应用程序将非 www 请求重定向到 www:
AddRedirectToWwwPermanent:如果请求是非
www,则将请求永久重定向到www子域。 使用 Status308PermanentRedirect 状态代码进行重定向。AddRedirectToWww:如果传入请求是非
www,则将请求重定向到www子域。 使用 Status307TemporaryRedirect 状态代码进行重定向。 重载允许提供响应状态代码。 使用 StatusCodes 类的字段实现状态代码分配。
URL 重定向
使用 AddRedirect 将请求重定向。 第一个参数包含用于匹配传入 URL 路径的正则表达式。 第二个参数是替换字符串。 第三个参数(如有)指定状态代码。 如不指定状态代码,则状态代码默认为“302 (已找到)”,指示资源暂时移动或替换。
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
在启用了开发人员工具的浏览器中,向路径为 /redirect-rule/1234/5678 的示例应用发出请求。 正则表达式匹配 redirect-rule/(.*) 上的请求路径,且该路径会被 /redirected/1234/5678 替代。 重定向 URL 以“302 (已找到)”状态代码发回客户端。 浏览器会在浏览器地址栏中出现的重定向 URL 上发出新请求。 由于示例应用中没有任何规则与重定向 URL 匹配:
- 第二个请求从应用收到200 - OK响应。
- 响应正文显示了重定向 URL。
当 URL 被重定向时,会发生一次与服务器之间的往返。
Warning
建立重定向规则时务必小心。 系统会根据应用的每个请求(包括重定向后的请求)对重定向规则进行评估。 很容易意外创建一个无限重定向循环。
原始请求:/redirect-rule/1234/5678
括号内的表达式部分称为“捕获组”。 表达式中的点号(.)表示匹配任意字符。 星号 (*) 表示 将前面的字符匹配零次或多次。 因此,URL 的最后两个路径段 1234/5678 由捕获组 (.*) 捕获。 在请求 URL 中提供的位于 redirect-rule/ 之后的任何值均由此单个捕获组捕获。
在替换字符串中,将捕获组注入带有美元符号 ($)、后跟捕获序列号的字符串中。 第一个捕获组的值通过 $1 获取,第二个通过 $2 获取,你的正则表达式中的后续捕获组也依次类推。 示例应用的重定向规则正则表达式中只有一个捕获组,因此替换字符串中只有一个注入组,即 $1。 如果应用此规则,URL 将变为 /redirected/1234/5678。
URL 重定向到安全端点
使用 AddRedirectToHttps 将 HTTP 请求重定向到采用 HTTPS 协议的相同主机和路径。 如不提供状态代码,则中间件默认为“302(已找到)”。 如果未提供端口:
- 中间件默认为
null。 - 方案更改为
https(HTTPS 协议),客户端访问端口 443 上的资源。
下面的示例展示了如何将状态代码设置为 301 - Moved Permanently 并将端口更改为 5001。
public void Configure(IApplicationBuilder app)
{
var options = new RewriteOptions()
.AddRedirectToHttps(301, 5001);
app.UseRewriter(options);
}
使用 AddRedirectToHttpsPermanent 将不安全的请求重定向到端口 443 上的采用安全 HTTPS 协议的相同主机和路径。 中间件将状态代码设置为 301 - Moved Permanently。
public void Configure(IApplicationBuilder app)
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent();
app.UseRewriter(options);
}
Note
当重定向到安全终结点时,无需其他重定向规则,我们建议使用 HTTPS 重定向中间件。 有关详细信息,请参阅强制使用 HTTPS主题。
示例应用能够演示如何使用 AddRedirectToHttps 或 AddRedirectToHttpsPermanent。 将扩展方法添加到 RewriteOptions。 在任何 URL 上向应用发出不安全的请求。 消除自签名证书不受信任的浏览器安全警告,或创建例外以信任证书。
使用 AddRedirectToHttps(301, 5001) 的原始请求:http://localhost:5000/secure
使用 AddRedirectToHttpsPermanent 的原始请求:http://localhost:5000/secure
URL 重写
使用 AddRewrite 创建重写 URL 的规则。 第一个参数包含用于匹配传入 URL 路径的正则表达式。 第二个参数是替换字符串。 第三个参数 skipRemainingRules: {true|false} 指示如果当前规则适用,中间件是否要跳过其他重写规则。
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
原始请求:/rewrite-rule/1234/5678
表达式开头的脱字号 (^) 意味着匹配从 URL 路径的开头处开始。
在前面的重定向规则 redirect-rule/(.*) 的示例中,正则表达式的开头没有脱字号 (^)。 因此,路径中 redirect-rule/ 之前的任何字符都可能成功匹配。
| Path | Match |
|---|---|
/redirect-rule/1234/5678 |
Yes |
/my-cool-redirect-rule/1234/5678 |
Yes |
/anotherredirect-rule/1234/5678 |
Yes |
重写规则 ^rewrite-rule/(\d+)/(\d+) 只能与以 rewrite-rule/ 开头的路径匹配。 注意下表中的匹配差异。
| Path | Match |
|---|---|
/rewrite-rule/1234/5678 |
Yes |
/my-cool-rewrite-rule/1234/5678 |
No |
/anotherrewrite-rule/1234/5678 |
No |
在表达式的 ^rewrite-rule/ 部分之后,有两个捕获组 (\d+)/(\d+)。
\d 表示匹配一个数字。 加号 (+) 表示 匹配前面的字符一次或多次。 因此,URL 必须包含数字加正斜杠加另一个数字的形式。 这些捕获组以 $1 和 $2 的形式注入重写 URL 中。 重写规则替换字符串将捕获组放入查询字符串中。 重写 /rewrite-rule/1234/5678 的请求路径,获取 /rewritten?var1=1234&var2=5678 处的资源。 如果原始请求中存在查询字符串,则重写 URL 时会保留此字符串。
无需往返服务器来获取资源。 如果资源存在,系统会提取资源并以“200(正常)”状态代码返回给客户端。 因为客户端不会被重定向,所以浏览器地址栏中的 URL 不会发生更改。 客户端无法检测到服务器上发生的 URL 重写操作。
Note
尽可能使用 skipRemainingRules: true,因为匹配规则在计算上很昂贵并且增加了应用响应时间。 要实现最快的应用响应速度:
- 按照从最频繁匹配的规则到最不频繁匹配的规则排列重写规则。
- 如果出现匹配项且无需处理任何其他规则,则跳过剩余规则的处理。
Apache mod_rewrite
使用 AddApacheModRewrite 应用 Apache mod_rewrite 规则。 请确保将规则文件与应用一起部署。 有关 mod_rewrite 规则的详细信息和示例,请参阅 Apache mod_rewrite。
StreamReader 用于读取 ApacheModRewrite.txt 规则文件中的规则:
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
示例应用将请求从 /apache-mod-rules-redirect/(.\*) 重定向到 /redirected?id=$1。 响应状态代码为“302 (已找到)”。
# Rewrite path with additional sub directory
RewriteRule ^/apache-mod-rules-redirect/(.*) /redirected?id=$1 [L,R=302]
原始请求:/apache-mod-rules-redirect/1234
中间件支持下列 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 重写模块规则
若要使用适用于 IIS URL 重写模块的同一规则集,使用 AddIISUrlRewrite。 请确保将规则文件与应用一起部署。 当在 Windows Server IIS 上运行时,请勿指示中间件使用应用的 web.config 文件。 使用 IIS 时,应将这些规则存储在应用的 web.config 文件之外,以避免与 IIS 重写模块发生冲突。 有关 IIS URL 重写模块规则的详细信息和示例,请参阅 Using Url Rewrite Module 2.0(使用 URL 重写模块 2.0)和 URL Rewrite Module Configuration Reference(URL 重写模块配置引用)。
StreamReader 用于读取 IISUrlRewrite.xml 规则文件中的规则:
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
示例应用将请求从 /iis-rules-rewrite/(.*) 重写为 /rewritten?id=$1。 以“200 (正常)”状态代码作为响应发送到客户端。
<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/1234
如果已启用 IIS Rewrite Module,并且配置了会以不希望的方式影响应用的服务器级规则,则可以为某个应用禁用 IIS Rewrite Module。 有关详细信息,请参阅禁用 IIS 模块。
不支持的功能
中间件不支持以下 IIS URL 重写模块功能:
- 出站规则
- 自定义服务器变量
- 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
Note
也可通过 IFileProvider 获取 PhysicalFileProvider。 这种方法可为重写规则文件的位置提供更大的灵活性。 请确保将重写规则文件部署到所提供路径的服务器中。
PhysicalFileProvider fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());
基于方法的规则
使用 Add 在方法中实现自己的规则逻辑。
Add 公开 RewriteContext,这使 HttpContext 可用于方法中。
RewriteContext.Result 决定如何处理其他管道进程。 将值设置为下表中的 RuleResult 字段之一。
| 重写上下文结果 | Action |
|---|---|
RuleResult.ContinueRules(默认值) |
继续应用规则。 |
RuleResult.EndResponse |
停止应用规则并发送响应。 |
RuleResult.SkipRemainingRules |
停止应用规则并将上下文发送给下一个中间件。 |
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
示例应用演示了如何对以 .xml 结尾的路径的请求进行重定向。 如果发出针对 /file.xml 的请求,请求将重定向到 /xmlfiles/file.xml。 状态代码设置为 301 - Moved Permanently。 当浏览器发出新请求 /xmlfiles/file.xml时,静态文件中间件会从 wwwroot/xmlfiles 文件夹向客户端提供服务。 对于重定向,请显式设置响应的状态代码。 否则,将返回 200 - OK 状态代码,并且不会在客户端发生重定向。
RewriteRules.cs:
public static void RedirectXmlFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
// Because the client is 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 = (int) HttpStatusCode.MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
"/xmlfiles" + request.Path + request.QueryString;
}
}
此方法还可以重写请求。 示例应用演示了如何重写任何文本文件请求的路径以从 wwwroot 文件夹中提供 file.txt 文本文件 。 静态文件中间件根据更新的请求路径为文件提供服务:
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
RewriteRules.cs:
public static void RewriteTextFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
if (request.Path.Value.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
context.Result = RuleResult.SkipRemainingRules;
request.Path = "/file.txt";
}
}
基于 IRule 的规则
使用 Add 在实现 IRule 接口的类中使用规则逻辑。 与使用基于方法的规则方法相比,IRule 提供了更大的灵活性。 实现类可能包含构造函数,可在其中传入 ApplyRule 方法的参数。
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
检查示例应用中 extension 和 newPath 的参数值是否符合多个条件。
extension 必须包含一个值,并且该值必须是 .png、.jpg 或 .gif。 如果 newPath 无效,则会引发 ArgumentException。 如果发出针对 image.png 的请求,请求将重定向到 /png-images/image.png。 如果发出针对 image.jpg 的请求,请求将重定向到 /jpg-images/image.jpg。 状态代码设置为 301 - Moved Permanently,context.Result 设置为停止处理规则并发送响应。
public class RedirectImageRequests : IRule
{
private readonly string _extension;
private readonly PathString _newPath;
public RedirectImageRequests(string extension, string newPath)
{
if (string.IsNullOrEmpty(extension))
{
throw new ArgumentException(nameof(extension));
}
if (!Regex.IsMatch(extension, @"^\.(png|jpg|gif)$"))
{
throw new ArgumentException("Invalid extension", nameof(extension));
}
if (!Regex.IsMatch(newPath, @"(/[A-Za-z0-9]+)+?"))
{
throw new ArgumentException("Invalid path", nameof(newPath));
}
_extension = extension;
_newPath = new PathString(newPath);
}
public void ApplyRule(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(_newPath)))
{
return;
}
if (request.Path.Value.EndsWith(_extension, StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = (int) HttpStatusCode.MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
_newPath + request.Path + request.QueryString;
}
}
}
原始请求:/image.png
原始请求:/image.jpg
正则表达式示例
| Goal | 正则表达式字符串 & 匹配示例 |
替换字符串 & 输出示例 |
|---|---|---|
| 将路径重写为查询字符串 | ^path/(.*)/(.*)/path/abc/123 |
path?var1=$1&var2=$2/path?var1=abc&var2=123 |
| 去掉尾部反斜杠 | (.*)/$/path/ |
$1/path |
| 强制添加尾部反斜杠 | (.*[^/])$/path |
$1//path/ |
| 避免重写特定请求 |
^(.*)(?<!\.axd)$ 或 ^(?!.*\.axd$)(.*)$是: /resource.htm不: /resource.axd |
rewritten/$1/rewritten/resource.htm/resource.axd |
| 重新排列 URL 段 | path/(.*)/(.*)/(.*)path/1/2/3 |
path/$3/$2/$1path/3/2/1 |
| 替换 URL 段 | ^(.*)/segment2/(.*)/segment1/segment2/segment3 |
$1/replaced/$2/segment1/replaced/segment3 |
本文档介绍 URL 重写,并说明如何在 ASP.NET Core 应用中使用 URL 重写中间件。
URL 重写是根据一个或多个预定义规则修改请求 URL 的行为。 URL 重写会在资源位置和地址之间创建一个抽象,使位置和地址不紧密相连。 在以下几种方案中,URL 重写很有价值:
- 暂时或永久移动或替换服务器资源,并维护这些资源的稳定定位符。
- 将请求处理拆分到不同应用中,或同一应用的不同区域中。
- 删除、添加或重新组织传入请求上的 URL 段。
- 为搜索引擎优化 (SEO) 优化公开 URL。
- 允许使用友好的公共 URL 来帮助访问者预测请求资源后返回的内容。
- 将不安全请求重定向到安全终结点。
- 防止热链接,外部站点会通过热链接将其他站点的资产链接到其自己的内容,从而利用托管在其他站点上的静态资产。
Note
URL 重写可能会降低应用的性能。 如果可行,应限制规则的数量和复杂度。
URL 重定向和 URL 重写
URL 重定向和 URL 重写之间的用词差异很细微,但这对于向客户端提供资源具有重要意义 。 ASP.NET Core的 URL 重写中间件能够满足两者的需求。
URL 重定向涉及客户端操作,指示客户端访问与客户端最初请求地址不同的资源。 这需要与服务器进行一次往返通信。 客户端对资源发出新请求时,返回客户端的重定向 URL 会出现在浏览器地址栏。
如果 /resource 被重定向到 ,则服务器作出响应,指示客户端应在 /different-resource 获取资源,所提供的状态代码指示重定向是临时的还是永久的。
将请求重定向到不同 URL 时,通过使用响应指定状态代码来指示重定向是永久还是临时:
如果资源有一个新的永久性 URL,并且你希望指示客户端所有将来的资源请求都使用新 URL,则使用
301 - Moved Permanently状态代码。 收到 301 状态代码时,客户端可能会缓存响应并重用这段代码。302 - 已找到状态代码用于重定向是临时的或通常可能会发生变化的情况。 302 状态码表示客户端不应存储该 URL,也不应在将来使用它。
有关状态代码的详细信息,请参阅 RFC 9110:状态代码定义。
URL 重写是一种服务器端操作,它会从不同于客户端所请求资源地址的另一个资源地址提供资源。 重写 URL 不需要往返服务器。 重写的 URL 不会返回客户端,也不会出现在浏览器地址栏。
如果 /resource 被重写为 /different-resource,服务器会内部获取并返回位于 /different-resource 的资源。
尽管客户端可能能够检索已重写 URL 处的资源,但是,客户端发出请求并收到响应时,并不知道已重写 URL 处存在的资源。
URL 重写示例应用
可以使用 示例应用浏览 URL 重写中间件的功能。 该应用程序会应用重定向和重写规则,并显示在几种场景下重定向后或重写后的 URL。
何时使用 URL 重写中间件
如果无法使用以下方法,请使用 URL 重写中间件:
此外,如果应用程序在 HTTP.sys 服务器(旧称 WebListener)上托管,请使用中间件。
使用 IIS、Apache 和 Nginx 中的基于服务器的 URL 重写技术的主要原因:
中间件不支持这些模块的完整功能。
服务器模块的一些功能不适用于 ASP.NET Core 项目,例如 IIS 重写模块的
IsFile和IsDirectory约束。 在这些情况下,请改为使用中间件。中间件性能与模块性能不匹配。
基准测试是确定哪种方法会最大程度降低性能或降低的性能是否可忽略不计的唯一方法。
Package
要在项目中包含中间件,请在项目文件中添加对 Microsoft.AspNetCore.App 元数据包的包引用,该文件包含 Microsoft.AspNetCore.Rewrite 包。
不使用 Microsoft.AspNetCore.App 元包时,向 Microsoft.AspNetCore.Rewrite 包添加项目引用。
扩展和选项
通过使用扩展方法为每条重写规则创建 RewriteOptions 类的实例,建立 URL 重写和重写定向规则。 按所需的处理顺序将多个规则串联起来。 使用 UseRewriter 将 RewriteOptions 添加到请求管道时,它会被传递到 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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
将不带 www 的域名重定向到带 www 的域名
有三个选项可允许应用程序将非 www 请求重定向到 www:
AddRedirectToWwwPermanent:如果请求是非
www,则将请求永久重定向到www子域。 使用 Status308PermanentRedirect 状态代码进行重定向。AddRedirectToWww:如果传入请求是非
www,则将请求重定向到www子域。 使用 Status307TemporaryRedirect 状态代码进行重定向。 重载允许提供响应状态代码。 使用 StatusCodes 类的字段实现状态代码分配。
URL 重定向
使用 AddRedirect 将请求重定向。 第一个参数包含用于匹配传入 URL 路径的正则表达式。 第二个参数是替换字符串。 第三个参数(如有)指定状态代码。 如不指定状态代码,则状态代码默认为“302 (已找到)”,指示资源暂时移动或替换。
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
在启用了开发人员工具的浏览器中,向路径为 /redirect-rule/1234/5678 的示例应用发出请求。 正则表达式匹配 redirect-rule/(.*) 上的请求路径,且该路径会被 /redirected/1234/5678 替代。 重定向 URL 以“302 (已找到)”状态代码发回客户端。 浏览器会在浏览器地址栏中出现的重定向 URL 上发出新请求。 由于示例应用中没有任何规则与重定向 URL 匹配:
- 第二个请求从应用收到200 - OK响应。
- 响应正文显示了重定向 URL。
当 URL 被重定向时,会发生一次与服务器之间的往返。
Warning
建立重定向规则时务必小心。 系统会根据应用的每个请求(包括重定向后的请求)对重定向规则进行评估。 很容易意外创建一个无限重定向循环。
原始请求:/redirect-rule/1234/5678
括号内的表达式部分称为“捕获组”。 表达式中的点号(.)表示匹配任意字符。 星号 (*) 表示 将前面的字符匹配零次或多次。 因此,URL 的最后两个路径段 1234/5678 由捕获组 (.*) 捕获。 在请求 URL 中提供的位于 redirect-rule/ 之后的任何值均由此单个捕获组捕获。
在替换字符串中,将捕获组注入带有美元符号 ($)、后跟捕获序列号的字符串中。 第一个捕获组的值通过 $1 获取,第二个通过 $2 获取,你的正则表达式中的后续捕获组也依次类推。 示例应用的重定向规则正则表达式中只有一个捕获组,因此替换字符串中只有一个注入组,即 $1。 如果应用此规则,URL 将变为 /redirected/1234/5678。
URL 重定向到安全端点
使用 AddRedirectToHttps 将 HTTP 请求重定向到采用 HTTPS 协议的相同主机和路径。 如不提供状态代码,则中间件默认为“302(已找到)”。 如果未提供端口:
- 中间件默认为
null。 - 方案更改为
https(HTTPS 协议),客户端访问端口 443 上的资源。
下面的示例展示了如何将状态代码设置为 301 - Moved Permanently 并将端口更改为 5001。
public void Configure(IApplicationBuilder app)
{
var options = new RewriteOptions()
.AddRedirectToHttps(301, 5001);
app.UseRewriter(options);
}
使用 AddRedirectToHttpsPermanent 将不安全的请求重定向到端口 443 上的采用安全 HTTPS 协议的相同主机和路径。 中间件将状态代码设置为 301 - Moved Permanently。
public void Configure(IApplicationBuilder app)
{
var options = new RewriteOptions()
.AddRedirectToHttpsPermanent();
app.UseRewriter(options);
}
Note
当重定向到安全终结点时,无需其他重定向规则,我们建议使用 HTTPS 重定向中间件。 有关详细信息,请参阅强制使用 HTTPS主题。
示例应用能够演示如何使用 AddRedirectToHttps 或 AddRedirectToHttpsPermanent。 将扩展方法添加到 RewriteOptions。 在任何 URL 上向应用发出不安全的请求。 消除自签名证书不受信任的浏览器安全警告,或创建例外以信任证书。
使用 AddRedirectToHttps(301, 5001) 的原始请求:http://localhost:5000/secure
使用 AddRedirectToHttpsPermanent 的原始请求:http://localhost:5000/secure
URL 重写
使用 AddRewrite 创建重写 URL 的规则。 第一个参数包含用于匹配传入 URL 路径的正则表达式。 第二个参数是替换字符串。 第三个参数 skipRemainingRules: {true|false} 指示如果当前规则适用,中间件是否要跳过其他重写规则。
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
原始请求:/rewrite-rule/1234/5678
表达式开头的脱字号 (^) 意味着匹配从 URL 路径的开头处开始。
在前面的重定向规则 redirect-rule/(.*) 的示例中,正则表达式的开头没有脱字号 (^)。 因此,路径中 redirect-rule/ 之前的任何字符都可能成功匹配。
| Path | Match |
|---|---|
/redirect-rule/1234/5678 |
Yes |
/my-cool-redirect-rule/1234/5678 |
Yes |
/anotherredirect-rule/1234/5678 |
Yes |
重写规则 ^rewrite-rule/(\d+)/(\d+) 只能与以 rewrite-rule/ 开头的路径匹配。 注意下表中的匹配差异。
| Path | Match |
|---|---|
/rewrite-rule/1234/5678 |
Yes |
/my-cool-rewrite-rule/1234/5678 |
No |
/anotherrewrite-rule/1234/5678 |
No |
在表达式的 ^rewrite-rule/ 部分之后,有两个捕获组 (\d+)/(\d+)。
\d 表示匹配一个数字。 加号 (+) 表示 匹配前面的字符一次或多次。 因此,URL 必须包含数字加正斜杠加另一个数字的形式。 这些捕获组以 $1 和 $2 的形式注入重写 URL 中。 重写规则替换字符串将捕获组放入查询字符串中。 重写 /rewrite-rule/1234/5678 的请求路径,获取 /rewritten?var1=1234&var2=5678 处的资源。 如果原始请求中存在查询字符串,则重写 URL 时会保留此字符串。
无需往返服务器来获取资源。 如果资源存在,系统会提取资源并以“200(正常)”状态代码返回给客户端。 因为客户端不会被重定向,所以浏览器地址栏中的 URL 不会发生更改。 客户端无法检测到服务器上发生的 URL 重写操作。
Note
尽可能使用 skipRemainingRules: true,因为匹配规则在计算上很昂贵并且增加了应用响应时间。 要实现最快的应用响应速度:
- 按照从最频繁匹配的规则到最不频繁匹配的规则排列重写规则。
- 如果出现匹配项且无需处理任何其他规则,则跳过剩余规则的处理。
Apache mod_rewrite
使用 AddApacheModRewrite 应用 Apache mod_rewrite 规则。 请确保将规则文件与应用一起部署。 有关 mod_rewrite 规则的详细信息和示例,请参阅 Apache mod_rewrite。
StreamReader 用于读取 ApacheModRewrite.txt 规则文件中的规则:
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
示例应用将请求从 /apache-mod-rules-redirect/(.\*) 重定向到 /redirected?id=$1。 响应状态代码为“302 (已找到)”。
# Rewrite path with additional sub directory
RewriteRule ^/apache-mod-rules-redirect/(.*) /redirected?id=$1 [L,R=302]
原始请求:/apache-mod-rules-redirect/1234
中间件支持下列 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 重写模块规则
若要使用适用于 IIS URL 重写模块的同一规则集,使用 AddIISUrlRewrite。 请确保将规则文件与应用一起部署。 当在 Windows Server IIS 上运行时,请勿指示中间件使用应用的 web.config 文件。 使用 IIS 时,应将这些规则存储在应用的 web.config 文件之外,以避免与 IIS 重写模块发生冲突。 有关 IIS URL 重写模块规则的详细信息和示例,请参阅 Using Url Rewrite Module 2.0(使用 URL 重写模块 2.0)和 URL Rewrite Module Configuration Reference(URL 重写模块配置引用)。
StreamReader 用于读取 IISUrlRewrite.xml 规则文件中的规则:
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
示例应用将请求从 /iis-rules-rewrite/(.*) 重写为 /rewritten?id=$1。 以“200 (正常)”状态代码作为响应发送到客户端。
<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/1234
如果已启用 IIS Rewrite Module,并且配置了会以不希望的方式影响应用的服务器级规则,则可以为某个应用禁用 IIS Rewrite Module。 有关详细信息,请参阅禁用 IIS 模块。
不支持的功能
与 ASP.NET Core 2.x 一同发布的中间件不支持以下 IIS URL 重写模块功能:
- 出站规则
- 自定义服务器变量
- 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
Note
也可通过 IFileProvider 获取 PhysicalFileProvider。 这种方法可为重写规则文件的位置提供更大的灵活性。 请确保将重写规则文件部署到所提供路径的服务器中。
PhysicalFileProvider fileProvider = new PhysicalFileProvider(Directory.GetCurrentDirectory());
基于方法的规则
使用 Add 在方法中实现自己的规则逻辑。
Add 公开 RewriteContext,这使 HttpContext 可用于方法中。
RewriteContext.Result 决定如何处理其他管道进程。 将值设置为下表中的 RuleResult 字段之一。
| 重写上下文结果 | Action |
|---|---|
RuleResult.ContinueRules(默认值) |
继续应用规则。 |
RuleResult.EndResponse |
停止应用规则并发送响应。 |
RuleResult.SkipRemainingRules |
停止应用规则并将上下文发送给下一个中间件。 |
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
示例应用演示了如何对以 .xml 结尾的路径的请求进行重定向。 如果发出针对 /file.xml 的请求,请求将重定向到 /xmlfiles/file.xml。 状态代码设置为 301 - Moved Permanently。 当浏览器发出新请求 /xmlfiles/file.xml时,静态文件中间件会从 wwwroot/xmlfiles 文件夹向客户端提供服务。 对于重定向,请显式设置响应的状态代码。 否则,将返回 200 - OK 状态代码,并且不会在客户端发生重定向。
RewriteRules.cs:
public static void RedirectXmlFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
// Because the client is 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 = (int) HttpStatusCode.MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
"/xmlfiles" + request.Path + request.QueryString;
}
}
此方法还可以重写请求。 示例应用演示了如何重写任何文本文件请求的路径以从 wwwroot 文件夹中提供 file.txt 文本文件 。 静态文件中间件根据更新的请求路径为文件提供服务:
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
RewriteRules.cs:
public static void RewriteTextFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
if (request.Path.Value.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
context.Result = RuleResult.SkipRemainingRules;
request.Path = "/file.txt";
}
}
基于 IRule 的规则
使用 Add 在实现 IRule 接口的类中使用规则逻辑。 与使用基于方法的规则方法相比,IRule 提供了更大的灵活性。 实现类可能包含构造函数,可在其中传入 ApplyRule 方法的参数。
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.RedirectXmlFileRequests)
.Add(MethodRules.RewriteTextFileRequests)
.Add(new RedirectImageRequests(".png", "/png-images"))
.Add(new RedirectImageRequests(".jpg", "/jpg-images"));
app.UseRewriter(options);
}
app.UseStaticFiles();
app.Run(context => context.Response.WriteAsync(
$"Rewritten or Redirected Url: " +
$"{context.Request.Path + context.Request.QueryString}"));
}
检查示例应用中 extension 和 newPath 的参数值是否符合多个条件。
extension 必须包含一个值,并且该值必须是 .png、.jpg 或 .gif。 如果 newPath 无效,则会引发 ArgumentException。 如果发出针对 image.png 的请求,请求将重定向到 /png-images/image.png。 如果发出针对 image.jpg 的请求,请求将重定向到 /jpg-images/image.jpg。 状态代码设置为 301 - Moved Permanently,context.Result 设置为停止处理规则并发送响应。
public class RedirectImageRequests : IRule
{
private readonly string _extension;
private readonly PathString _newPath;
public RedirectImageRequests(string extension, string newPath)
{
if (string.IsNullOrEmpty(extension))
{
throw new ArgumentException(nameof(extension));
}
if (!Regex.IsMatch(extension, @"^\.(png|jpg|gif)$"))
{
throw new ArgumentException("Invalid extension", nameof(extension));
}
if (!Regex.IsMatch(newPath, @"(/[A-Za-z0-9]+)+?"))
{
throw new ArgumentException("Invalid path", nameof(newPath));
}
_extension = extension;
_newPath = new PathString(newPath);
}
public void ApplyRule(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(_newPath)))
{
return;
}
if (request.Path.Value.EndsWith(_extension, StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = (int) HttpStatusCode.MovedPermanently;
context.Result = RuleResult.EndResponse;
response.Headers[HeaderNames.Location] =
_newPath + request.Path + request.QueryString;
}
}
}
原始请求:/image.png
原始请求:/image.jpg
正则表达式示例
| Goal | 正则表达式字符串 & 匹配示例 |
替换字符串 & 输出示例 |
|---|---|---|
| 将路径重写为查询字符串 | ^path/(.*)/(.*)/path/abc/123 |
path?var1=$1&var2=$2/path?var1=abc&var2=123 |
| 去掉尾部反斜杠 | (.*)/$/path/ |
$1/path |
| 强制添加尾部反斜杠 | (.*[^/])$/path |
$1//path/ |
| 避免重写特定请求 |
^(.*)(?<!\.axd)$ 或 ^(?!.*\.axd$)(.*)$是: /resource.htm不: /resource.axd |
rewritten/$1/rewritten/resource.htm/resource.axd |
| 重新排列 URL 段 | path/(.*)/(.*)/(.*)path/1/2/3 |
path/$3/$2/$1path/3/2/1 |
| 替换 URL 段 | ^(.*)/segment2/(.*)/segment1/segment2/segment3 |
$1/replaced/$2/segment1/replaced/segment3 |