什么是中间件

中间件是装配到应用管道中用来处理请求和响应的软件组件。管道中的每一个组件都可以选择是否将请求移交给下一个组件,并且可以在管道中调用下一个组件之前或者之后执行指定的操作。请求委托被用于构建请求管道。请求委托会处理每一个HTTP请求。

请求委托通过在传递给Startup类中的Configure方法的IApplicationBuilder类型上使用Run,Map,Use扩展方法进行配置。一个单独的请求委托可以被指定为一个内嵌的匿名方法,或定义在一个可重用的类中。这些可重用的类就是中间件中间件组件。每个请求管道中的中间件负责调用管道中的下一个组件,或者在适当的时候将调用链短路。

通过IApplicationBuilder创建中间件管道

ASP.NET请求管道由一系列的请求委托所组成,一个接着一个被调用,如图所示,执行线程跟随着黑色箭头:

每一个委托在下一个委托调用之前或之后都有机会去执行操作。任何委托都可以选择停止传递请求到下一个委托,而自己处理该请求。这就是请求管道的短路,这种设计可以避免一些不必要的工作。例如,一个授权(Authorization)中间件只有在请求通过身份验证之后才能调用下一个委托;否则它就会被短路并返回”Not Authorized”的响应。异常处理委托必须在管道的早期被调用,这样它们就可以捕获到发生在管道内更深层次的异常。

打开默认的网站模板,Configure方法添加了如下中间件组件:

  1. 错误处理(针对开发环境和非开发环境)
  2. 静态文件服务器
  3. 身份验证
  4. MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseIdentity();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

上述代码(在非开发环境),UseExceptionHandler是第一个被添加到管道的中间件,因此会捕获之后调用中出现的任何异常。

Static File Module提供了无需授权检查的功能,由它服务的任何文件,包含在那些位于wwwroot文件夹中的文件都是可被公开访问的。如果你想让基于授权来提供这些文件:

  1. 将它们存放在wwwroot文件夹之外以及静态文件中间件可以访问的任何目录。
  2. 交给控制器方法,通过返回一个FileResult表示授权被应用的地方。

被静态文件模块处理的请求会在管道中被短路。如果请求不是通过静态文件模块来处理,那么它会被传给Identity module来执行身份验证。如果请求未通过验证,则管道将被短路。否则,将会调用管道的最后一站-MVC框架。

注:你添加中间件的顺序通常会影响它们对请求产生影响的顺序,然后在响应时会以相反的顺序。这对应用程序的安全、性能和功能都非常关键。在上面的代码中,Static File Module在管道的早期被调用,这样可以及时短路,避免了请求进行到不必要的组件中。身份验证中间件在任何需要身份验证的处理请求之前被添加进来。异常处理必须在其它中间件之前被注册以便捕获其它组件的异常。

最简单的ASP.NET应用设置一个简单的请求委托来处理所有的请求。这样就不是一个真实的请求管道,通过调用一个简单的匿名函数来响应每一个HTTP请求。

app.Run(async context =>
{
    await context.Response.WriteAsync("Hello, World!");
});

App.Run委托会终止管道,下面的盒子中,只有第一个委托会被运行。

public void Configure(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World!");
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World, Again!");
    });
}

将多个请求委托链接在一起,next参数表示管道内的下一个委托。你可以通过不调用next参数来终止(短路)管道。你通常可以在调用下一个委托之前和之后执行操作:

public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
    loggerfactory.AddConsole(minLevel: LogLevel.Information);
    var logger = loggerfactory.CreateLogger(_environment);
    app.Use(async (context, next) =>
    {
        logger.LogInformation("Handling request.");
        await next.Invoke();
        logger.LogInformation("Finished handling request.");
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

当应用程序运行的环境设置成LogInline时,ConfigureLogInline方法会被调用。

在上述例子中,调用await next.Invoke()将会调用下一个委托await context.Response.WriteAsync("Hello from " + _environment);。客户端会收到所期望的响应(“Hello from LogInline”)。

Run、Map和Use

你可以通过Run,MapUse来配置HTTP管道。Run方法将会短路管道(因为它不会调用next请求委托),因此Run方法应该只在管道结尾被调用。Run方法是一种惯例,有些中间件也会暴露它们自己的Run[Middleware]方法,你也必须在管道的末尾进行运行。下面两个中间件是相同的,其中一个使用Use版本的没有使用到next参数:

public void ConfigureEnvironmentOne(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

public void ConfigureEnvironmentTwo(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

Map*扩展被用于分支管道,当前的实现支持基于请求路径或使用谓词进行分支。Map扩展方法被用于匹配基于请求路径的请求委托。Map只接受一个路径和配置了一个单独中间件的管道的功能。在下面的例子中,任何基于/maptest基本路径的请求都会被HandleMapTest方法中所配置的管道所处理。

private static void HandleMapTest(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Map Test Successful");
    });
}

public void ConfigureMapping(IApplicationBuilder app)
{
    app.Map("/maptest", HandleMapTest);

}

当使用了Map,每一个请求所匹配的路径段将从HttpRequest.Path中移除,并附加到HttpRequest.PathBase中。

除了基于路径的映射外,MapWhen方法还支持基于谓词的中间件分支,允许以一种非常灵活的方式构造单独的管道。任何Func<HttpContext, bool>类型的谓词都可被用于将请求映射到一个新的管道分支。在下面的例子中,一个简单的谓词被用来检测字符变量branch是否存在:

private static void HandleBranch(IApplicationBuilder app)
{
    app.Run(async context =>
    {
        await context.Response.WriteAsync("Branch used.");
    });
}

public void ConfigureMapWhen(IApplicationBuilder app)
{
    app.MapWhen(context => {
        return context.Request.Query.ContainsKey("branch");
    }, HandleBranch);

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from " + _environment);
    });
}

上述配置中,任何包含branch的查询字符串的请求将使用定义在HandleBranch方法中的管道(将得到”Branch used.”的响应)。其它请求(不包含branch的查询字符串的请求)将被await context.Response.WriteAsync("Hello from " + _environment);所定义的委托所处理。

内置中间件

ASP.NET带来了下列中间件组件:

中间件 描述                 
身份验证(Authentication) 提供身份验证支持
跨域资源共享(CORS) 配置跨域资源共享
路由(Routing) 定义和约定请求路由
会话(Session) 对管理用户会话提供支持
静态文件(Static Files) 对服务静态文件和目录浏览提供支持

原文链接

Middleware

个人博客

我的个人博客