ASP.NET Core 3 源码解析 — [9]请求管道

尊重作者劳动成果,转载请注明出处,谢谢!

目录

1.请求管道

  1.1 请求上下文

  1.2 服务器

  1.3 中间件

2.中间件

  2.1 注册中间件

  2.2 构建请求管道

  2.3 IApplicationBuilder 扩展方法

3.实现解析

  3.1 设计模型

  3.2 ApplicationBuilder

  3.3 HostingApplication

  3.4 DefaultHttpContextFactory

1.请求管道

在 Web 主机一节中,我们简单介绍到了 ASP.NET Core 对 HTTP 请求的处理是由一个请求处理管道来完成的,位于管道最前面的是一个 Web 服务器,用于监听、接收并响应 HTTP 请求,Web 服务器后面是一组中间件,用于对接收到的请求进行处理。服务器接收到 HTTP 请求后,首先会根据其特性创建一个请求上下文,该上下文贯穿了整个处理过程,然后将上下文交给后续的中间件进行处理,每个中间处理完之后会将该上下文交由下一个中间件进行处理,直到最后一个中间件处理完成后再按相反的顺序进行返回。整个请求的处理过程就像是一个管道一样,如下图所示:

1.1 请求上下文

HttpContext 表示请求上下文,是一个抽象类,其中包含了请求信息、响应信息、依赖注入容器对象等,定义如下:

public abstract class HttpContext
{
    public abstract IFeatureCollection Features { get; }
    public abstract HttpRequest Request { get; }
    public abstract HttpResponse Response { get; }
    public abstract ConnectionInfo Connection { get; }
    public abstract WebSocketManager WebSockets { get; }
    public abstract ClaimsPrincipal User { get; set; }
    public abstract IDictionary<object, object> Items { get; set; }
    public abstract IServiceProvider RequestServices { get; set; }
    public abstract CancellationToken RequestAborted { get; set; }
    public abstract string TraceIdentifier { get; set; }
    public abstract ISession Session { get; set; }
    public abstract void Abort();
}

HttpRequest 表示请求的相关信息,是一个抽象类,定义如下:

public abstract class HttpRequest
{
    public abstract HttpContext HttpContext { get; }
    public abstract string Method { get; set; }
    public abstract string Scheme { get; set; }
    public abstract bool IsHttps { get; set; }
    public abstract HostString Host { get; set; }
    public abstract PathString PathBase { get; set; }
    public abstract PathString Path { get; set; }
    public abstract QueryString QueryString { get; set; }
    public abstract IQueryCollection Query { get; set; }
    public abstract string Protocol { get; set; }
    public abstract IHeaderDictionary Headers { get; }
    public abstract IRequestCookieCollection Cookies { get; set; }
    public abstract long? ContentLength { get; set; }
    public abstract string ContentType { get; set; }
    public abstract Stream Body { get; set; }
    public virtual PipeReader BodyReader { get => throw new NotImplementedException();  }
    public abstract bool HasFormContentType { get; }
    public abstract IFormCollection Form { get; set; }
    public abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = new CancellationToken());
    public virtual RouteValueDictionary RouteValues { get; set; }
}

HttpResponse 表示请求的响应信息,是一个抽象类,定义如下:

public abstract class HttpResponse
{
    private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)();
    private static readonly Func<object, Task> _disposeDelegate = disposable =>
    {
        ((IDisposable)disposable).Dispose();
        return Task.CompletedTask;
    };

    private static readonly Func<object, Task> _disposeAsyncDelegate = disposable => ((IAsyncDisposable)disposable).DisposeAsync().AsTask();
    public abstract HttpContext HttpContext { get; }
    public abstract int StatusCode { get; set; }
    public abstract IHeaderDictionary Headers { get; }
    public abstract Stream Body { get; set; }
    public virtual PipeWriter BodyWriter { get => throw new NotImplementedException(); }
    public abstract long? ContentLength { get; set; }
    public abstract string ContentType { get; set; }
    public abstract IResponseCookies Cookies { get; }
    public abstract bool HasStarted { get; }
    public abstract void OnStarting(Func<object, Task> callback, object state);
    public virtual void OnStarting(Func<Task> callback) => OnStarting(_callbackDelegate, callback);
    public abstract void OnCompleted(Func<object, Task> callback, object state);
    public virtual void RegisterForDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable);
    public virtual void RegisterForDisposeAsync(IAsyncDisposable disposable) => OnCompleted(_disposeAsyncDelegate, disposable);
    public virtual void OnCompleted(Func<Task> callback) => OnCompleted(_callbackDelegate, callback);
    public virtual void Redirect(string location) => Redirect(location, permanent: false);
    public abstract void Redirect(string location, bool permanent);
    public virtual Task StartAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); }
    public virtual Task CompleteAsync() { throw new NotImplementedException(); }
}

1.2 服务器

IServer 表示服务器,定义如下:

public interface IServer : IDisposable
{
    IFeatureCollection Features { get; }

    Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken);

    Task StopAsync(CancellationToken cancellationToken);
}

StartAsync 方法用于启动 Web 服务器,并开始处理 HTTP 请求,该方法需要传入一个 IHttpApplication<TContext> 类型的参数,IHttpApplication<TContext> 接口表示 Web 服务器对 HTTP 请求的处理流程,即对 IServer 接口接收到的原始请求的处理,包括创建请求上下文,将请求上下文交由中间件处理,最后释放该上下文,都是通过 IHttpApplication<TContext> 接口来实现的。StopAsync 方法用于停止 Web 服务器。IHttpApplication<TContext> 接口的定义如下:

public interface IHttpApplication<TContext>
{
    TContext CreateContext(IFeatureCollection contextFeatures);

    Task ProcessRequestAsync(TContext context);

    void DisposeContext(TContext context, Exception exception);
}

CreateContext 方法用于从原始的请求中创建出对应的请求上下文对象,接着由 ProcessRequestAsync 方法对该上下文进行处理,最后由 DisposeContext 方法释放该上下文信息。

IServer 接口有一个 IFeatureCollection 类型的属性,IFeatureCollection 接口用于存取与服务和请求上下文相关的特性信息,其本质是一个字典类型。定义如下:

[DefaultMember("Item")]
public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>, IEnumerable
{
    object this[Type key] { get; set; }

    bool IsReadOnly { get; }
    int Revision { get; }

    TFeature Get<TFeature>();
    void Set<TFeature>(TFeature instance);
}

 在创建请求上下文对象时,CreateContext 方法会根据服务器上的特性集合来创建具体的请求上下文对象(继承自 HttpContext)。

1.3 中间件

从上图中我们知道,一个中间件要对请求进行处理,需要接收一个请求上下文的对象,并将请求上下文“传递”给下一个中间件。ASP.NET Core 专门定义了一个 RequestDelegate 委托类型来表示对请求的处理,如下代码所示:

public delegate Task RequestDelegate(HttpContext context);

所以,对于中间件的定义,ASP.NET Core 使用一个 Func<RequestDelegate, RequestDelegate> 的委托类型来进行描述,该委托类型接收一个 RequestDelegate 类型的参数,并返回了新的 RequestDelegate 对象,即下一个中间件的输出作为前一个中间件的输入参数。通过这种“链接”方式,将所有的中间件连接了起来,形成了一个完整的请求管道。通过上面的描述,最终的请求管道也表现为一个 RequestDelegate 对象。

2.中间件

请求管道中最重要的组成部分就是中间件,一个 ASP.NET Core 应用简单点来说就是中间件的组合,要使我们的应用具备处理请求的能力,我们应该根据需要注册相应的中间件。ASP.NET Core 为我们提供了丰富的中间件,如缓存、会话、认证、授权等,我们也可以根据需求定义自己的中间件,并将其注册到管道中来实现对请求的处理。在实际项目开发中,对于中间件的注册,主要是通过 Startup 启动类来完成的。根据约定的命名规则,框架会自动调用 Startup 启动类的 Configure 方法来完成对中间件的注册,该方法默认的第一个参数为 Action<IApplicationBuilder> 委托类型,实际上我们正是通过 IApplicationBuilder 接口来完成对中间件的注册的。IApplicationBuilder 接口的定义如下:

public interface IApplicationBuilder
{
    IServiceProvider ApplicationServices { get; set; }

    IFeatureCollection ServerFeatures { get; }

    IDictionary<string, object> Properties { get; }

    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

    IApplicationBuilder New();

    RequestDelegate Build();
}

2.1 注册中间件

2.1.1 注册委托类型中间件

Use 方法用于注册中间件,其参数是一个 Func<RequestDelegate, RequestDelegate> 类型。

2.1.2  注册强类型和约定名称中间件

UseMiddleware 方法是 IApplicationBuilder 的扩展方法,用于注册强类型或约定名称的中间件,如下代码所示:

public static class UseMiddlewareExtensions
{
    internal const string InvokeMethodName = "Invoke";
    internal const string InvokeAsyncMethodName = "InvokeAsync";

    private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);

    public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
    {
        return app.UseMiddleware(typeof(TMiddleware), args);
    }

    public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
    {
        if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
        {
            // IMiddleware doesn't support passing args directly since it's
            // activated from the container
            if (args.Length > 0)
            {
                throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
            }

            return UseMiddlewareInterface(app, middleware);
        }

        var applicationServices = app.ApplicationServices;
        return app.Use(next =>
        {
            var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
            var invokeMethods = methods.Where(m =>
                string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
                || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
                ).ToArray();

            if (invokeMethods.Length > 1)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
            }

            if (invokeMethods.Length == 0)
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
            }

            var methodInfo = invokeMethods[0];
            if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType))
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
            }

            var parameters = methodInfo.GetParameters();
            if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
            {
                throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
            }

            var ctorArgs = new object[args.Length + 1];
            ctorArgs[0] = next;
            Array.Copy(args, 0, ctorArgs, 1, args.Length);
            var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
            if (parameters.Length == 1)
            {
                return (RequestDelegate)methodInfo.CreateDelegate(typeof(RequestDelegate), instance);
            }

            var factory = Compile<object>(methodInfo, parameters);

            return context =>
            {
                var serviceProvider = context.RequestServices ?? applicationServices;
                if (serviceProvider == null)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                }

                return factory(instance, context, serviceProvider);
            };
        });
    }

    private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
    {
        return app.Use(next =>
        {
            return async context =>
            {
                var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));
                if (middlewareFactory == null)
                {
                    // No middleware factory
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                }

                var middleware = middlewareFactory.Create(middlewareType);
                if (middleware == null)
                {
                    // The factory returned null, it's a broken implementation
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
                }

                try
                {
                    await middleware.InvokeAsync(context, next);
                }
                finally
                {
                    middlewareFactory.Release(middleware);
                }
            };
        });
    }

    private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodInfo, ParameterInfo[] parameters)
    {
        var middleware = typeof(T);

        var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");
        var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
        var instanceArg = Expression.Parameter(middleware, "middleware");

        var methodArguments = new Expression[parameters.Length];
        methodArguments[0] = httpContextArg;
        for (int i = 1; i < parameters.Length; i++)
        {
            var parameterType = parameters[i].ParameterType;
            if (parameterType.IsByRef)
            {
                throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));
            }

            var parameterTypeExpression = new Expression[]
            {
                providerArg,
                Expression.Constant(parameterType, typeof(Type)),
                Expression.Constant(methodInfo.DeclaringType, typeof(Type))
            };

            var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);
            methodArguments[i] = Expression.Convert(getServiceCall, parameterType);
        }

        Expression middlewareInstanceArg = instanceArg;
        if (methodInfo.DeclaringType != typeof(T))
        {
            middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodInfo.DeclaringType);
        }

        var body = Expression.Call(middlewareInstanceArg, methodInfo, methodArguments);

        var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);

        return lambda.Compile();
    }

    private static object GetService(IServiceProvider sp, Type type, Type middleware)
    {
        var service = sp.GetService(type);
        if (service == null)
        {
            throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));
        }

        return service;
    }
}

UseMiddleware 方法首先判断注册的中间件是否实现于 IMiddleware 接口,如果实现了该接口,则代表注册的中间件是强类型的,该接口定义了唯一的 InvokeAsync 方法,用于处理请求上下文对象,并决定是否将请求传递给下一个中间件进行处理。IMiddleware 接口的定义如下:

public interface IMiddleware
{
    Task InvokeAsync(HttpContext context, RequestDelegate next);
}

 

 

2.1.3 注册约定名称中间件

UseMiddleware

2.2 构建请求管道

Build

2.3 IApplicationBuilder 扩展方法

Run 

UseWhen

UsePathBase

3.实现解析

3.1 设计模型

3.2 ApplicationBuilder

 

3.3 HostingApplication

 

3.4 DefaultHttpContextFactory

 

posted @ 2020-06-23 17:45  chenyuxin  阅读(489)  评论(0)    收藏  举报