在HTTP的语义中,重定向一般指的是服务端通过返回一个状态码为3XX的响应促使客户端像另一个地址再次发起请求,本章将此称为“客户端重定向“。既然有客户端重定向,自然就有服务端重定向,本章所谓的服务端重定向指的是在服务端通过改变请求路径将请求导向另一个终结点。ASP.NET下的重定向是通过RewriteMiddleware中间件实现的。(本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)

[S2501]客户端重定向 (源代码

[S2502]服务端重定向 (源代码

[S2503]采用IIS重写规则实现重定向(源代码

[S2504]采用Apache重写规则实现重定向(源代码

[S2505]基于HTTPS终结点的重定向(源代码

[S2501]客户端重定向

我们可以为RewriteMiddleware中间件定义客户端重定向规则使之返回一个Location报头指向重定向地址的3XX响应。客户端(比如浏览器)在接收到这样的响应后会根据状态码约定的语义向重定向地址重新发起请求,我们将这种由客户端对新的地址重新请求的方式称为“客户端重定向”。

下面演示的这个例子会将请求路径以“foo/**”为前缀的请求重定向到新的路径“/bar/**”。如代码片段所示,我们通过调用UseRewriter扩展方法注册了RewriteMiddleware中间件,该方法会将对应的RewriteOptions配置选项作为参数。我们直接调用构造函数创建的这个RewriteOptions对象,并调用其AddRedirect扩展方法添加了一个重定向规则,该方法定义了两个参数,前者(“^/foo/(.*)”)代表参与重定向的原始路径模式(正则表达式),后者(“baz/$1”)表示重定向目标地址模板,占位符“$1”表示在进行正则匹配时产生的首段捕获内容(前缀“foo/”后面的部分)。请求的URL会作为响应的内容。

using Microsoft.AspNetCore.Rewrite;

var app = WebApplication.Create();
var options = new RewriteOptions().AddRedirect("^foo/(.*)", "bar/$1");
app.UseRewriter(options);
app.MapGet("/{**foobar}", (HttpRequest request) =>$"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}");
app.Run();

演示程序注册了一个采用“/{**foobar}”路由模板的终结点,请求URL直接作为该终结点的响应内容。演示程序启动之后,所有路径以“/foo”为前缀的请求都会自动重定向到以“/bar”为前缀的地址。如果请求路径被设置为“/foo/abc/123”,最终将会被重定向到图1所示的“/bar/abc/123”路径下。

图1 客户端重定向

整个过程涉及HTTP报文交换更能体现客户端重定向的本质。如下所示的是整个过程涉及的两次报文交换,我们可以看出服务端第一次返回的是状态码为302的响应,根据映射规则生成的重定向地址体现在Location报头上。

GET http://localhost:5000/foo/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 302 Found
Content-Length: 0
Date: Wed, 22 Sep 2021 13:34:17 GMT
Server: Kestrel
Location: /bar/abc/123
GET http://localhost:5000/bar/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Date: Wed, 22 Sep 2021 13:34:17 GMT
Server: Kestrel
Content-Length: 33 http://localhost:5000/bar/abc/123

[S2502]服务端重定向

服务端重定向会在服务端通过重写请求路径的方式将请求重定向到新的终结点。对于前面演示的程序来说,我们只需要对它做简单的修改就能切换到服务端重定向。如下面的代码片段所示,在RewriteOptions对象被创建后,我们调用它的另一个AddRewrite扩展方法注册了一条服务端重定向(URL重写)规则,原始请求路径的正则表达式和重定向路径均保持不变。

using Microsoft.AspNetCore.Rewrite;

var app = WebApplication.Create();
var options = new RewriteOptions().
.AddRewrite(regex: "^foo/(.*)", replacement: "bar/$1", skipRemainingRules: true);
app.UseRewriter(options);
app.MapGet("/{**foobar}", (HttpRequest request) =>
$"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}");
app.Run();

改动的程序启动后,如果利用浏览器采用相同的路径(“/foo/abc/123”)对站点发起请求,我们会得到如图2所示的相同的响应内容。由于这次采用的是服务端重定向,整个过程只会涉及一次报文交换,所以浏览器的请求地址不会改变。

图2 服务端重定向

[S2503]采用IIS重写规则实现重定向

重定向是绝大部分Web服务器(比如IIS、Apache和Nginx等)都会提供的功能,但是不同的服务器类型针对重定向规则具有不同的定义方式。IIS中的重定向被称为“URL重写”,具体的URL重写规则采用XML格式进行定义,RewriteMiddleware中间件对它提供了原生的支持。我们将URL重写规则以如下的方式定义在创建的rewrite.xml文件中,并将该文件保存在演示项目的根目录下。

<rewrite>
<rules>
<rule name="foo">
<match url="^foo/(.*)" />
<action type="Redirect" url="baz/{R:1}" />
</rule>
<rule name="bar">
<match url="^bar/(.*)" />
<action type="Rewrite" url="baz/{R:1}" />
</rule>
</rules>
</rewrite>

如上所示的XML文件定义了两条指向目标地址“baz/{R:1}”的规则,这里的占位符“{R:1}”和前面定义的“$1”一样,都表示针对初始请求路径进行正则匹配时得到的第一段捕获内容。两条规则用来匹配原始路径的正则表达式分别定义为“^foo/(.*)”和“^bar/(.*)”。它们采用的Action类型也不相同,前者为“Redirect”,表示客户端重定向;后者为“Rewrite”,表示服务端重定向。

为了将采用XML文件定义的IIS重定向规则应用到演示程序中,我们对演示程序如下的修改。如代码片段所示,在RewriteOptions对象被创建出来后,我们调用了它的AddIISUrlRewrite扩展方法添加了IIS URL重写规则,该方法的两个参数分别表示用来读取规则文件的IFileProvider对象和规则文件针对该对象的路径。由于规则文件存储与项目根目录下,这也是ASP.NET应用“内容根目录”所在的位置,所以我们可以使用内容根目录对应的IFileProvider对象。

using Microsoft.AspNetCore.Rewrite;

var app = WebApplication.Create();
var options = new RewriteOptions().AddIISUrlRewrite(fileProvider: app.Environment.ContentRootFileProvider, filePath: "rewrite.xml");
app.UseRewriter(options);
app.MapGet("/{**foobar}", (HttpRequest request) =>$"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}");
app.Run();

改动的程序启动之后,我们针对添加的两条重定向规则发送了对应的请求,它们采用的请求路径分别为“/foo/abc/123”和“/bar/abc/123”。从图3所示的输出可以看出,这两个请求均被重定向到相同的目标路径“/baz/abc/123”。

图3 IIS重定向规则

由于发送的两个请求分别采用客户端和服务端重定向方式导向新的地址,所以浏览器针对前者显示的是重定向后的地址,对于后者则显示原始的地址。整个过程涉及到的如下三次报文交互更能说明两种重定向方式的差异,从报文内容我们可以进一步看出第一次采用的是响应状态码为301的永久重定向。

GET http://localhost:5000/foo/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 301 Moved Permanently
Content-Length: 0
Date: Wed, 22 Sep 2021 23:26:02 GMT
Server: Kestrel
Location: /baz/abc/123
GET http://localhost:5000/baz/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Date: Wed, 22 Sep 2021 23:26:02 GMT
Server: Kestrel
Content-Length: 33 http://localhost:5000/baz/abc/123
GET http://localhost:5000/bar/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Date: Wed, 22 Sep 2021 23:26:26 GMT
Server: Kestrel
Content-Length: 33 http://localhost:5000/baz/abc/123

[S2504]采用Apache重写规则实现重定向

上面我们演示了RewriteMiddleware中间件针对IIS重定向规则的支持,实际上该中间件还支持Apache的重定向模块mod_rewriter所采用的重定向规则定义形式,我们照例来做一个简单的演示。我们在项目根目录下添加了一个名为rewrite.config的配置文件,并在其中定义了如下两条重定向规则。

RewriteRule ^/foo/(.*) /baz/$1 [R=307]
RewriteRule ^/bar/(.*) - [F]

上面第一条规则利用R这个Flag将路径与正则表达式“^/foo/(.*)”相匹配的请求以重定向到新的路径“/baz/$1”,具体采用的是针对状态码307的临时客户端重定向。对于其路径与正则表达式“^/bar/(.*)”相匹配的请求,我们将它视为未经授权授权的请求,所以对应的规则采用F(Forbidden)这个Flag。为了让演示程序采用上述这个配置文件定义的Apache重定向规则,我们只需要按照如下的方式调用RewriteOptions 对象的AddApacheModRewrite扩展方法就可以了。

using Microsoft.AspNetCore.Rewrite;

var app = WebApplication.Create();
var options = new RewriteOptions().AddApacheModRewrite(fileProvider: app.Environment.ContentRootFileProvider, filePath: "rewrite.config");
app.UseRewriter(options);
app.MapGet("/{**foobar}", (HttpRequest request) =>$"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}");
app.Run();

改动的程序启动之后,我们针对添加的两条重定向规则发送了对应的请求,它们采用的请求路径分别为“/foo/abc/123”和“/bar/abc/123”。从图4所示的输出可以看出,第一个请求均被重定向到相同的目标路径“/baz/abc/123”,第二个请求返回一个状态码为403的响应。

图4Apache mod­_rewrite重定向规则

如下所示的是整个过程涉及到的三次报文交换。我们可以看出第一次请求得到的响应状态码正式我们在规则中显式设置的307。第二个请求由于被视为权限不足,服务端直接返回一个状态为“403 Forbidden”的响应。

GET http://localhost:5000/foo/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 307 Temporary Redirect
Content-Length: 0
Date: Wed, 22 Sep 2021 23:56:26 GMT
Server: Kestrel
Location: /baz/abc/123
GET http://localhost:5000/baz/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 200 OK
Date: Wed, 22 Sep 2021 23:56:26 GMT
Server: Kestrel
Content-Length: 33
GET http://localhost:5000/bar/abc/123 HTTP/1.1
Host: localhost:5000 HTTP/1.1 403 Forbidden
Content-Length: 0
Date: Wed, 22 Sep 2021 23:56:33 GMT
Server: Kestrel

[S2505]基于HTTPS终结点的重定向

将针对HTTP请求重定向到对应HTTPS终结点是一种常见的重定向场景。如下所示的演示针对路径“/foo”和“/bar”注册了两个终结点,它们均由注册的两个中间件构建的RequestDelegate委托作为处理器,其中一个就是调用UseRewriter扩展方法注册的RewriteMiddleware中间件,另一个中间件则是通过调用Run扩展方法注册的,后者依然将最终请求的URL作为响应的内容。

using Microsoft.AspNetCore.Rewrite;

var app = WebApplication.Create();
app.MapGet("/foo", CreateHandler(app, 302));
app.MapGet("/bar", CreateHandler(app, 307));
app.Run(); static RequestDelegate CreateHandler(IEndpointRouteBuilder endpoints, int statusCode)
{
var app = endpoints.CreateApplicationBuilder();
app
.UseRewriter(new RewriteOptions().AddRedirectToHttps(statusCode, 5001))
.Run(httpContext => {
var request = httpContext.Request;
var address =
$"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}";
return httpContext.Response.WriteAsync(address);
});
return app.Build();
}

两个终结点的处理器通过本地方法CreateHandler创建出来的。该方法调用当前WebApplication对象的CreateApplicationBuilder方法创建了一个新的IApplicationBuilder对象,并调用后者的UseRewriter扩展方法注册了RewriteMiddleware中间件。我们为该中间件提供的HTTPS重定向规则是通过调用RewriteOptions对象的AddRedirectToHttps扩展方法定义的,该方法时指定了重定向响应采用的状态码(302和307)和HTTPS终结点采用的端口号。改动的程序启动之后,针对两个终结点的HTTP请求(“http://localhost:5000/foo”和“http://localhost:5000/bar”)均以图5所示的形式被重定向到了对应的HTTPS终结点。

图5 HTTPS重定向

整个过程涉及到如下四次报文交换,我们可以看出我们通过调用AddRedirectToHttps扩展方法定义的规则采用的是客户端重定向。重定向响应采用了我们设置的状态码,分别是“302 Found”和“307 Temporary Redirect”。

GET http://localhost:5000/foo HTTP/1.1
Host: localhost:5000 HTTP/1.1 302 Found
Content-Length: 0
Date: Thu, 23 Sep 2021 12:10:51 GMT
Server: Kestrel
Location: https://localhost:5001/foo
GET https://localhost:5001/foo HTTP/1.1
Host: localhost:5001 HTTP/1.1 200 OK
Date: Thu, 23 Sep 2021 12:10:51 GMT
Server: Kestrel
Content-Length: 26 https://localhost:5001/foo
GET http://localhost:5000/bar HTTP/1.1
Host: localhost:5000 HTTP/1.1 307 Temporary Redirect
Content-Length: 0
Date: Thu, 23 Sep 2021 12:10:57 GMT
Server: Kestrel
Location: https://localhost:5001/bar
GET https://localhost:5001/bar HTTP/1.1
Host: localhost:5001 HTTP/1.1 200 OK
Date: Thu, 23 Sep 2021 12:10:57 GMT
Server: Kestrel
Content-Length: 26 https://localhost:5001/bar

ASP.NET Core 6框架揭秘实例演示[37]:重定向的N种实现方式的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[24]:中间件的多种定义方式

    ASP.NET Core的请求处理管道由一个服务器和一组中间件组成,位于 "龙头" 的服务器负责请求的监听.接收.分发和最终的响应,针对请求的处理由后续的中间件来完成.中间件最终体 ...

  2. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  3. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  4. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  5. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  6. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  7. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  8. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

  9. ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法

    为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...

  10. ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出

    针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...

随机推荐

  1. idea创建Spring项目时选择Maven还是Spring initializr?

    今天在使用idea创建Spring项目时不知道选择Maven还是Spring initializr 接着都实验了一下,其实本质是一样的,使用"spring initializr"创 ...

  2. 记一次 .NET 某企业 ERP网站系统 崩溃分析

    一:背景 1. 讲故事 前段时间收到了一个朋友的求助,说他的ERP网站系统会出现偶发性崩溃,找了好久也没找到是什么原因,让我帮忙看下,其实崩溃好说,用 procdump 自动抓一个就好,拿到 dump ...

  3. pandas之缺失值处理

    在一些数据分析业务中,数据缺失是我们经常遇见的问题,缺失值会导致数据质量的下降,从而影响模型预测的准确性,这对于机器学习和数据挖掘影响尤为严重.因此妥善的处理缺失值能够使模型预测更为准确和有效. 为什 ...

  4. 扎实打牢数据结构算法根基,从此不怕算法面试系列之004 week01 02-04 使用泛型实现线性查找法

    1.算法描述 在数组中逐个查找元素,即遍历. 2.上一篇文的实现结果 在 扎实打牢数据结构算法根基,从此不怕算法面试系列之003 week01 02-03 代码实现线性查找法中,我们实现了如下代码: ...

  5. AI人工智能简史

    AI人工智能简史 最近学习AI,顺便整理了一份AI人工智能简史,大家参考: 1951年 第一台神经网络机,称为SNARC: 1956年 达特茅斯学院会议,正式确立了人工智能的研究领域: 1966年 M ...

  6. 家用wife密码设置

    1.在浏览器上面输入ip地址:http://192.168.1.1/或http://192.168.0.1/出现路由器登陆窗口输入用户名跟密码.用户名默认一般为:admin,密码为空或为:admin ...

  7. Azure DevOps(一)基于 Net6.0 的 WPF 程序如何进行持续集成、持续编译

    一,引言 我们是否正在为如何快速的编译.部署客户端应用程序而烦恼?这也是博主最近遇到的问题.目前博主所在公司主要做项目级的定制化开发,多以 C/S 架构的 WPF 程序为主,每次到了协助开发团队给实施 ...

  8. 使用Ref还是Reactive?

    我喜欢Vue 3的Composition API,它提供了两种方法来为Vue组件添加响应式状态:ref和reactive.当你使用ref时到处使用.value是很麻烦的,但当你用reactive创建的 ...

  9. Comparator之用最少数量的箭引爆气球

    文章目录 前言 关于Comparator 原题 前言 今天刷个题,遇到一个很有趣的问题,关于Comparator的使用,感觉也是一个关于写代码的一些小细节的问题 关于Comparator Compar ...

  10. 关于PM系统以及OA系统的工作基本心态

    这个系统的目的是什么? 这个系统的初衷是好的,是一个信息化管理的数据科学系统,目的是更好的累计公司的业务数据. 但实际操作过程中,包括推广过程中,你能看到上层人员对于这个系统的态度,更像是一个个人企业 ...