.net core 管道(Pipeline)是什么?

由上图可以看出,.net core 管道是请求抵达服务器到响应结果返回的中间的一系列的处理过程,如果我们简化一下成下图来看的话,.net core 的管道其实就是中间件的部分。微软中间件文档

为什么管道就是中间件的部分了呢?我是这么理解的,.net core 是通过Startup 类配置服务和应用的请求管道,所以狭义点来讲这个管道就是指的请求管道,就是我们今天要理解的中间件管道。

.net core 核心体系结构的特点就是一个中间件系统,它是处理请求和响应的代码段。中间件彼此链接,形成一个管道。传入的请求通过管道传递,其中每个中间件都有机会在将它们传递到下一个中间件之前对它们进行处理。传出响应也以相反的顺序通过管道传递。

PS:简单来讲就是请求开始到响应结束的中间的一大部分。你可以理解成 " 汽车销售 " (开始买车到提车的过程,但愿不会坐在奔驰车盖上哭),哈哈……

还有我们来看看,为什么我们要简化来看,在运行时 .net core 会预先注入一些必要的服务及依赖项,默认注入(ServiceCollection)的服务清单如下:

我们先断章取义地看,这里面有 Kestrel 处理请求,将接收到的请求内容(字符串流)转化成结构化的数据(HttpContext)供后面的中间件使用的服务。欸,服务哟。那其实也就是 Kestrel 服务也是中间件嘛。

而第一张图中的MVC本身也作为中间件来实现的。

还有一些相关的服务都可以看看上面截图的服务,而且旁边标注的生命周期的类型就是之前所讲到的 .net core 的三种注入模式 。

那么从程序入口来讲,过程是怎么样的呢?

从应用程序主入口 Main() --> WebHost --> UseStartup

/// <summary>
/// Specify the startup type to be used by the web host.
/// </summary>
/// <param name="hostBuilder">The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" /> to configure.</param>
/// <param name="startupType">The <see cref="T:System.Type" /> to be used.</param>
/// <returns>The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns>
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
string name = startupType.GetTypeInfo().Assembly.GetName().Name;
return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name).ConfigureServices((Action<IServiceCollection>)delegate(IServiceCollection services)
{
if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
{
ServiceCollectionServiceExtensions.AddSingleton(services, typeof(IStartup), startupType);
}
else
{
ServiceCollectionServiceExtensions.AddSingleton(services, typeof(IStartup), (Func<IServiceProvider, object>)delegate(IServiceProvider sp)
{
IHostingEnvironment requiredService = ServiceProviderServiceExtensions.GetRequiredService<IHostingEnvironment>(sp);
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, requiredService.get_EnvironmentName()));
});
}
});
}

上面的代码就可以解释说,会预先注入的必要的服务,在通过委托的方式,注入 Startup 里的服务。具体可以继续探究:

UseSetting:Add or replace a setting in the configuration.
/// <summary>
/// Add or replace a setting in the configuration.
/// </summary>
/// <param name="key">The key of the setting to add or replace.</param>
/// <param name="value">The value of the setting to add or replace.</param>
/// <returns>The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns>
public IWebHostBuilder UseSetting(string key, string value)
{
_config.set_Item(key, value);
return this;
}
ConfigureServices:Adds a delegate for configuring additional services for the host or web application. This may be called multiple times.
/// <summary>
/// Adds a delegate for configuring additional services for the host or web application. This may be called
/// multiple times.
/// </summary>
/// <param name="configureServices">A delegate for configuring the <see cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" />.</param>
/// <returns>The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns>
public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
{
if (configureServices == null)
{
throw new ArgumentNullException("configureServices");
}
return ConfigureServices(delegate(WebHostBuilderContext _, IServiceCollection services)
{
configureServices(services);
});
}

ConventionBasedStartup

public class ConventionBasedStartup : IStartup
{
private readonly StartupMethods _methods; public ConventionBasedStartup(StartupMethods methods)
{
_methods = methods;
} public void Configure(IApplicationBuilder app)
{
try
{
_methods.ConfigureDelegate(app);
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
throw;
}
} public IServiceProvider ConfigureServices(IServiceCollection services)
{
try
{
return _methods.ConfigureServicesDelegate(services);
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
{
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
throw;
}
}
}

OK,到这里就已经确定了,我们可控的是通过Startup注入我们所需的服务,就是Startup注入的中间件可以做所有的事情,如处理认证,错误,静态文件等等,并且如上面所说的 MVC 在 .net core 也是作为中间件实现的。

那么 .net core 给我们内置了多少中间件呢?如下图:

我们很经常用到的内置中间件有:

    app.UseExceptionHandler(); //异常处理
app.UseStaticFiles(); //静态文件
app.UseAuthentication(); //Auth验证
app.UseMvc(); //MVC

我们知道可以在启动类的 Configure 方法中配置 .net core 管道,通过调用 IApplicationBuilder 上的 Use*** 方法,就可以向管道添加一个中间件,被添加的顺序决定了请求遍历它们的顺序。因此,如上面添加内置中间件的顺序,传入的请求将首先遍历异常处理程序中间件,然后是静态文件中间件,然后是身份验证中间件,最终将由MVC中间件处理。

Use*** 方法实际上只是 .net core 提供给我们的“快捷方式”,以便更容易地构建管道。在幕后,它们最终都使用(直接或间接)这些关键字:Use 和 Run 。两者都向管道中添加了一个中间件,不同之处在于Run添加了一个终端中间件,即管道中的最后一个中间件。

那么有内置,就应该可以定制的。如何定制自己的中间件呢?上一些简陋的demo演示一下:

(1)无分支管道

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{ // Middleware A
app.Use(async (context, next) =>
{
Console.WriteLine("A (before)");
await next();
Console.WriteLine("A (after)");
}); // Middleware B
app.Use(async (context, next) =>
{
Console.WriteLine("B (before)");
await next();
Console.WriteLine("B (after)");
}); // Middleware C (terminal)
app.Run(async context =>
{
Console.WriteLine("C");
await context.Response.WriteAsync("Hello world");
}); }

打印结果:

A (before)
B (before)
C
B (after)
A (after)

那用管道图展示的话就是:

(2)有分支管道,当使用无分支的管道时,相当于就是一条线直走到底再返回响应结果。但一般情况下,我们都希望管道更具灵活性。创建有分支的管道就需要使用到 Map 扩展用作约定来创建管道分支。Map 是基于给定请求路径的匹配项来创建请求管道分支的,如果请求路径以给定的路径开头,就执行分支。那么就有两种类型有分支的管道:

1.无连结分支,上官方demo:

public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
} private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
} public void Configure(IApplicationBuilder app)
{
app.Map("/map1", HandleMapTest1); app.Map("/map2", HandleMapTest2); app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

结果:

以上无连结分支很容易就理解了,就是不同的路径跑不同的分支。如果是有参数匹配的话,就要使用 MapWhen,而 MapWhen 基于给定谓词的结果创建请求管道分支。Func<HttpContext, bool> 类型的任何谓词均可用于将请求映射到管道的新分支。 谓词用于检测查询字符串变量 branch 是否存在。

2.有连结(重新连接上主管道)分支,创建有连结分支管道就要使用到 UseWhen,上demo:

public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
Console.WriteLine("A (before)");
await next();
Console.WriteLine("A (after)");
}); app.UseWhen(
context => context.Request.Path.StartsWithSegments(new PathString("/foo")),
a => a.Use(async (context, next) =>
{
Console.WriteLine("B (before)");
await next();
Console.WriteLine("B (after)");
})); app.Run(async context =>
{
Console.WriteLine("C");
await context.Response.WriteAsync("Hello world");
});
}

像上面的代码,当请求不是以 " /foo " 开头的时候,结果为:

A (before)
C
A (after)

当请求是以 " /foo " 开头的时候,结果为:

A (before)
B (before)
C
B (after)
A (after)

正如您所看到的,中间件管道背后的思想非常简单,但是非常强大。大多数功能都是 .net core(身份验证、静态文件、缓存、MVC等)作为中间件实现。当然,编写自己的代码也很容易!

后面可以进阶写自己的中间件,官方文档

.net core 中间件管道底层剖析的更多相关文章

  1. net core 中间件管道

    net core 中间件管道 .net core 管道(Pipeline)是什么? 由上图可以看出,.net core 管道是请求抵达服务器到响应结果返回的中间的一系列的处理过程,如果我们简化一下成下 ...

  2. ASP.NET Core真实管道详解[1]:中间件是个什么东西?

    ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 <ASP.NET Core管道深度剖析[共4篇]> 中围绕着一个经过极度简化的模拟 ...

  3. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  4. .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法

    .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...

  5. .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类

    .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类 0x00 为什么要引入扩展方法 有的中间件功能比较简单,有的则比较复杂,并且依赖其它组件.除 ...

  6. .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理

    .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理 0x00 问题的产生 管道是.NET Core中非常关键的一个概念,很多重要的组件都以中间件的形式存在,包括权限管理.会话管理 ...

  7. ASP.NET Core管道深度剖析[共4篇]

    之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比 ...

  8. ASP.NET Core管道深度剖析(4):管道是如何建立起来的?

    在<管道是如何处理HTTP请求的?>中,我们对ASP.NET Core的请求处理管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.这样一 ...

  9. ASP.NET Core管道深度剖析(3):管道是如何处理HTTP请求的?

    我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但是就具体的实现来说,由于其中涉及很多对象的交互,我想很少人能够地把它弄清楚.为了让读者 ...

随机推荐

  1. 【转】Javascript全局变量var与不var的区别

    相信你对全局变量一定不陌生,在函数作用域里用a=1这种形式定义的变量会是一个全局变量,在全局作用域里,用下面3种形式都可以创建对全局可见的命名: <script> var a = 1; b ...

  2. Hibernate Session总结

    现在我们可以在 IDEA 下新建一个 Hibernate 项目,接着上次内容这次主要总结一下 Hibernate 的 Session,及其核心方法. Session 概述 Session 接口是 Hi ...

  3. maven安装和配置及创建maven项目

    (1)下载maven,下载成功后,解压到本地磁盘 里面包含这几项 (2)配置maven环境变量MAVEN_HOME.path (3)最后检验配置是否成功:用win键+R,来打开命令行提示符窗口,即Do ...

  4. Java 面试知识点解析(三)——JVM篇

    前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大 ...

  5. SpringMVC中的文件上传

    1. 配置图片服务器 一般图片会单独保存在图片服务器上, 本文为简化处理, 在Tomcat中配置一个路劲用于专门存放图片 在tomcat上配置图片虚拟目录,在tomcat下conf/server.xm ...

  6. 数据库连接池dbcp和c3po的区别

    1 DBCP   DBCP是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件. 2.C3P0 是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate ...

  7. mysql管理工具navicat的快捷键

    1. ctrl + q  或者 ctrl+n: 打开新查询窗口 2. ctrl + r: 运行当前窗口内的所有语句 3. ctrl + shit + r: 只运行选中的语句 4. ctrl + w: ...

  8. 【一通百通】Bash的单双括号建议:多用[[]], 少用[]

    一. bash [  ] 单双括号 基本要素: Ø  [ ] 两个符号左右都要有空格分隔 Ø  内部操作符与操作变量之间要有空格:如  [  “a”  =  “b”  ] Ø  字符串比较中,> ...

  9. KVM内核文档阅读笔记

    KVM在内核中有丰富的文档,位置在Documentation/virtual/kvm/. 00-INDEX:整个目录的索引及介绍文档. api.txt:KVM用户空间API,所谓的API主要是通过io ...

  10. SSIS 检查点

    在SSIS中,检查点实际上是一个记录系统,用于记录控制流中Task组件的执行状态.通过合理地配置Checkpoint,在Package运行出错之后,重新执行Package,可以跳过上一次已经成功执行的 ...