1、名词解释
中间件
中间件是一种装配到应用管道以处理请求和响应的软件,选择是否将请求传递到管道中的下一个组件,可在管道中的下一个组件前后执行工作。
请求管道
请求处理管道由一个Server和一组有序排列的中间件构成。Server负责监听、接收请求和响应,而中间件则负责处理请求和响应之间的所有工作。
请求委托
请求委托(RequestDelegate)是ASP.NET Core中用于处理HTTP请求的一个委托类型。 它是一个函数,代表一项请求处理任务,接受一个HttpContext对象作为参数,并返回一个Task。每个中间件都承载着独立的请求处理任务,通过RequestDelegate来表示,使得每个中间件可以执行特定的操作,并在必要时将请求传递给管道中的下一个中间件。
管道短路
当委托不将请求传递给下一个委托时,它被称为“让请求管道短路”。通常需要短路,因为这样可以避免不必要的工作。
终端中间件
如果中间件让请求处理管道短路,并阻止下游中间件进一步处理请求,它被称为“终端中间件”。
Endpoint
在中间件中,Endpoint是指客户端和服务器之间通信的一个具体点,通常表示一个特定的URL路径和HTTP方法组合。 Endpoint是路由系统中的一个基本单位,用于处理客户端的请求。每个Endpoint都有一个唯一的标识符,通常是一个URL路径和HTTP方法(如GET、POST等)的组合。
2、创建管道
使用 WebApplication 创建中间件管道:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
app.Use(async (context, next) =>
{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
app.Run();
请求管道包含一系列请求委托,依次调用,每个委托处理之后又依次反向返回,如下流程图:
添加中间件的方法
- Use()
- Run()
- Map()
- UseWhen()
- MapWhen()
可以看看下图Use()方法的参数,为什么是委托?为什么在使用Use()方法时需要加async关键字
Run委托
Run 委托不会收到 next 参数。 第一个 Run 委托始终为终端,用于终止管道。 Run 是一种约定。 某些中间件组件可能会公开在管道末尾运行的 Run[Middleware] 方法:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Use(async (context, next) =>
{
// Do work that can write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
//顺序第一个run委托终止管道,终端中间件
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
//如果在 Run 委托之后添加了另一个 Use 或 Run 委托,则不会调用该委托。
app.Run();
中间件顺序
下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。 你可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。
常用场景中间件
以下 Program.cs 代码将为常见应用场景添加中间件组件:
- 异常/错误处理
- 当应用在开发环境中运行时:
- 开发人员异常页中间件 (UseDeveloperExceptionPage) 报告应用运行时错误。
- 数据库错误页中间件 (UseDatabaseErrorPage) 报告数据库运行时错误。
- 当应用在生产环境中运行时:
- 异常处理程序中间件 (UseExceptionHandler) 捕获以下中间件中引发的异常。
- HTTP 严格传输安全协议 (HSTS) 中间件 (UseHsts) 添加 Strict-Transport-Security 标头。
- HTTPS 重定向中间件 (UseHttpsRedirection) 将 HTTP 请求重定向到 HTTPS。
- 静态文件中间件 (UseStaticFiles) 返回静态文件,并简化进一步请求处理。
- Cookie 策略中间件 (UseCookiePolicy) 使应用符合欧盟一般数据保护条例 (GDPR) 规定。
- 用于路由请求的路由中间件 (UseRouting)。
- 身份验证中间件 (UseAuthentication) 尝试对用户进行身份验证,然后才会允许用户访问安全资源。
- 用于授权用户访问安全资源的授权中间件 (UseAuthorization)。
- 会话中间件 (UseSession) 建立和维护会话状态。 如果应用使用会话状态,请在 Cookie 策略中间件之后和 MVC 中间件之前调用会话中间件。
- 用于将 Razor Pages 终结点添加到请求管道的终结点路由中间件(带有 MapRazorPages 的 UseEndpoints)。
3、对中间件管道进行分支
Map()
Map 扩展用作约定来创建管道分支。 Map 基于给定请求路径的匹配项来创建请求管道分支。 如果请求路径以给定路径开头,则执行分支。
使用 Map 时,将从 HttpRequest.Path 中删除匹配的路径段,并针对每个请求将该路径段追加到 HttpRequest.PathBase。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//map创建新管道分支
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
//中间件,委托方法
static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}
上述代码的请求路径匹配执行记录,可明显区分管道分支,如下:
Map支持嵌套:
//嵌套类似于同时匹配多个段
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
Map支持同时匹配多个段:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//嵌套类似于同时匹配多个段
app.Map("/map1/seg1", HandleMultiSeg);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
static void HandleMultiSeg(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
MapWhen()
MapWhen 基于给定谓词的结果创建请求管道分支。 Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
//contains谓词用于检测查询字符串变量 branch 是否存在
app.MapWhen(context => context.Request.Query.ContainsKey("branch"), HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}
UseWhen()
UseWhen 也基于给定谓词的结果创建请求管道分支。 与 MapWhen 不同的是,如果这个分支不发生短路或包含终端中间件,则会重新加入主管道:
在下面的示例中,为所有请求写入 Hello from non-Map delegate. 响应。 如果请求中包含查询字符串变量 branch,则在重新加入主管道之前会记录其值。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
appBuilder => HandleBranchAndRejoin(appBuilder));
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
void HandleBranchAndRejoin(IApplicationBuilder app)
{
var logger = app.ApplicationServices.GetRequiredService<ILogger<Program>>();
app.Use(async (context, next) =>
{
var branchVer = context.Request.Query["branch"];
logger.LogInformation("Branch used = {branchVer}", branchVer);
// Do work that doesn't write to the Response.
await next();
// Do other work that doesn't write to the Response.
});
}
结尾
知识点介绍稍微粗略,主要的知识都可以通过微软官方文档进行学习,后续如我有更深入的理解会继续完善文章,学习知识点希望先了解是什么?用到知识点之后,一方面是加深知识点记忆,另一方面是有更深入的理解之后再来写为什么?