.NetCore中间件源码研究

Posted 蓝夏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.NetCore中间件源码研究相关的知识,希望对你有一定的参考价值。

中间件介绍

中间件是在应用程序管道处理请求和响应的一个链

每个组件都可以在请求处理前后做一些操作,并决定是否将请求交给下一个组件处理

如果一个中间件没有把请求交给下一个中间件,称之为管道短路

中间件的默认实现类在 Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder 中

中间件配置

配置中间件的方式很多,包括UseMiddlewareUseRun等等

但大部分配置方式都是扩展方法,最终调用的函数只有 Use(Func<RequestDelegate, RequestDelegate> middleware)

核心代码

public delegate Task RequestDelegate(HttpContext context);

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
    _components.Add(middleware);
    return this;
}

public RequestDelegate Build()
{
    RequestDelegate requestDelegate = delegate(HttpContext context)
    {
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    };
    foreach (Func<RequestDelegate, RequestDelegate> item in _components.Reverse())
    {
        requestDelegate = item(requestDelegate);
    }
    return requestDelegate;
}

这是ApplicationBuilder中的核心代码,一眼看上去很简单。但是这么这么多层的委托嵌套难以阅读,我们接下来将他们拆开来看

RequestDelegate

这个委托不用多说,参数为HttpContext上下文,返回一个Task

Func<RequestDelegate, RequestDelegate>

这个就有意思了,他的入参是Next,返回值是当前要执行的委托,不要理解反了。

我们在Startup的Configure中写入以下代码

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
     app.Use((RequestDelegate next) => async (HttpContext context) =>
             {
                 Console.WriteLine("before1");
                 await next.Invoke(context);
                 Console.WriteLine("after1");
             });

     app.Use((RequestDelegate next) => async (HttpContext context) =>
             {
                 Console.WriteLine("before2");
                 await next.Invoke(context);
                 Console.WriteLine("after2");
             });
     app.Use((RequestDelegate next) => async (HttpContext context) =>
             {
                 if (context.Request.Path == "/hello")
                 {
                     context.Response.StatusCode = 200;
                     await context.Response.WriteAsync("hello");
                     return;
                 }
                 await next.Invoke(context);
             });
 }

执行结果如下

before1
before2
hello
after2
after1

如果仍然难以看懂,那我们继续拆分

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    Func<RequestDelegate, RequestDelegate> func1 = (RequestDelegate next) =>
    {
        return async context =>
        {
            {
                Console.WriteLine("before1");
                await next.Invoke(context);
                Console.WriteLine("after1");
            }
        };
    };

    Func<RequestDelegate, RequestDelegate> func2 = (RequestDelegate next) =>
    {
        return async context =>
        {
            {
                Console.WriteLine("before2");
                await next.Invoke(context);
                Console.WriteLine("after2");
            }
        };
    };

    Func<RequestDelegate, RequestDelegate> func3 = (RequestDelegate next) =>
    {
        return async context =>
        {
            {
                if (context.Request.Path == "/hello")
                {
                    Console.WriteLine("hello");
                    context.Response.StatusCode = 200;
                    await context.Response.WriteAsync("hello");
                    return;
                }
                await next.Invoke(context);
            }
        };
    };

    app.Use(func1);
    app.Use(func2);
    app.Use(func3);
}

接下来我们看一下中间件的构建过程,来解释为什么会是上面的输出结果

//Use()函数负责向_components列表中插入委托,此时_components中存在的委托顺序为 func1,func2,func3

//接下来我们主要看Build()函数

//1.这是build函数中定义的最后一个短路委托,不会再向下调用
RequestDelegate requestDelegate = delegate(HttpContext context)
{
    context.Response.StatusCode = 404;
    return Task.CompletedTask;
};

//2._components.Reverse()将注入的委托顺序反转,执行循环
// 反转后的顺序变成了 func3 ,func2, func1
// 第一次1循环得到结果: requestDelegate=func3(requestDelegate)
// 第一次2循环得到结果: requestDelegate=func2(func3(requestDelegate))
// 第一次3循环得到结果: requestDelegate=func1(func2(func3(requestDelegate)))

//3. 结合我们的代码,最终执行顺序如下

requestDelegate = async (context) =>
             {
                 Console.WriteLine("before1");
                     Console.WriteLine("before2");
                         if (context.Request.Path == "/hello")
                         {
                             Console.WriteLine("hello");
                             context.Response.StatusCode = 200;
                             await context.Response.WriteAsync("hello");
                             return;
                         }
                             context.Response.StatusCode = 404;
                     Console.WriteLine("after2");
                 Console.WriteLine("after1");
             };

//4.最终将requestDelegate返回

所以在接收到请求时,中间件的处理顺序就会按照我们定义的顺序来执行啦,经过上面的介绍,是不是感觉So Easy呐!

以上是关于.NetCore中间件源码研究的主要内容,如果未能解决你的问题,请参考以下文章

服务器在管道中的“龙头”地位

.NET Core技术研究-中间件的由来和使用

.net core 中的经典设计模式的应用

无法更新 View Pager 中的中间片段。

学习ASP.NET Core,怎能不了解请求处理管道[2]: 服务器在管道中的“龙头”地位

.NetCore技术研究-一套代码同时支持.NET Framework和.NET Core