The Open Web Interface for .NET (OWIN) 源码阅读

katana开源许久,网上仍未搜索到对其源码的阅读总结,本人在工作中正好遇到数据处理流程框架设计,想来跟服务器处理requestresponse差不多,遂起了阅读katana源码,并借鉴其设计的想法,磕磕碰碰,困难重重,所幸有一些收获,与大家交流交流。

katana源码 https://katanaproject.codeplex.com/

owin官网 http://owin.org/

两个最重要的数据结构

1. Environment

IDictionary<string, object>

官方解释:

This data structure is responsible for storing all of the state necessary for processing an HTTP request and response, as well as any relevant server state. An OWIN-compatible Web server is responsible for populating the environment dictionary with data such as the body streams and header collections for an HTTP request and response. It is then the responsibility of the application or framework components to populate or update the dictionary with additional values and write to the response body stream.

Environment是在pipeline中流动的数据,代表着一个具体的requestresponse,后文会介绍在每个pipeline stage中会对这个dictionary中自己关心的数据进行处理,并在进入下一个stage的时候丢弃引用,采用的是原子操作,因而每个Environment只存在一个pipeline stage中。数据举例:

Required Key Name Value Description
Yes owin.RequestBody A Stream with the request body, if any. Stream.Null MAY be used as a placeholder if there is no request body. See Request Body.
Yes owin.RequestHeaders An IDictionary<string, string[]> of request headers. See Headers.
Yes owin.RequestMethod A string containing the HTTP request method of the request (e.g., “GET”, “POST”).
Yes owin.RequestPath A string containing the request path. The path MUST be relative to the “root” of the application delegate. See Paths.
Yes owin.RequestPathBase A string containing the portion of the request path corresponding to the “root” of the application delegate; see Paths.
Yes owin.RequestProtocol A string containing the protocol name and version (e.g. “HTTP/1.0” or “HTTP/1.1“).
Yes owin.RequestQueryString A string containing the query string component of the HTTP request URI, without the leading “?” (e.g., “foo=bar&amp;baz=quux“). The value may be an empty string.
Yes owin.RequestScheme A string containing the URI scheme used for the request (e.g., “http”, “https”); see URI Scheme.

2.AppFunc

Func<IDictionary<string, object>, Task>;

官方解释:

The second key element of OWIN is the application delegate. This is a function signature which serves as the primary interface between all components in an OWIN application. The definition for the application delegate is as follows:

The application delegate then is simply an implementation of the Func delegate type where the function accepts the environment dictionary as input and returns a Task. This design has several implications for developers:

  • There are a very small number of type dependencies required in order to write OWIN components. This greatly increases the accessibility of OWIN to developers.
  • The asynchronous design enables the abstraction to be efficient with its handling of computing resources, particularly in more I/O intensive operations.
  • Because the application delegate is an atomic unit of execution and because the environment dictionary is carried as a parameter on the delegate, OWIN components can be easily chained together to create complex HTTP processing pipelines.

这就是middleware,也是每个pipeline stage中具体的处理方法,采用异步调用的方式,由StartUp类进行注册,并生成一条链,实际上就是压进一个List中。

源码阅读,能学到很多东西,肯定有很多理解有偏差的地方,欢迎指正,我将从一个具体的middleware注册和StartUp的执行切入,大致勾勒一个pipeline的构造和流动过程。


OWIN中Environment初始化

按照官方文档的解释,Microsoft.Owin.Host.SystemWeb在启动的时候会进行一系列的初始化,具体的入口点隐藏太深无法寻找,我们假定现在流程已经到了Microsoft.Owin.Host.SystemWeb.OwinHttpHandler这里,这里将进行Environmentpipeline的初始化。本文所涉及的class大都在Microsoft.Owin.Host.SystemWeb命名空间下。

先看Environment的初始化,源码进行了精简,只留下重要部分,建议参考完整源码。

OwinHttpHandler被实例化,参考OwinHttpHandlerTests

var httpHandler = new OwinHttpHandler(string.Empty, OwinBuilder.Build(WasCalledApp));

先不考虑OwinBuilder.Build具体操作,httpHandler将开始处理request,即 public IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object extraData)将被调用,用于初始化一个基本的OwinAppContext,并根据httpContext参数初始化一个RequestContext,再将request信息合并进入OwinAppContext,从上下文开始执行

public IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object extraData) 
{
    …
    try 
    {
        OwinAppContext appContext = _appAccessor.Invoke();
        //初始化基础OwinAppContext
        Contract.Assert(appContext != null);
        // REVIEW: the httpContext.Request.RequestContext may be used here if public property unassigned?
        RequestContext requestContext = _requestContext ?? new RequestContext(httpContext, new RouteData());
        string requestPathBase = _pathBase;
        string requestPath = _requestPath ?? httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(1) + httpContext.Request.PathInfo;
        OwinCallContext callContext = appContext.CreateCallContext(
        //完善OwinAppContext信息
        requestContext,
                            requestPathBase,
                            requestPath,
                            callback,
                            extraData);
        try 
        {
            callContext.Execute();
            //   这是本文的重点,一个request进来之后,OWIN的处理流程已经开始
            // 启动以后的处理的源码都是可见的
        }
        …
                        return callContext.AsyncResult;
    }
    …
}

Excecute方法中进行Environment的再次初始化

internal void Execute() 
{
    CreateEnvironment();
    //再次初始化Environment
    …
}

这次初始化主要将上下文信息中将要进入pipeline流动的数据存入AspNetDictionary中,这其实是一个扩展的

IDictionary<string, object>,也就是前文所介绍的第一个最重要数据结构,之后middleware开始Invoke,也就是pipeline的第一个AppFunc开始执行,参数就是初始化完成的Environment

那么很自然有个问题,pipeline的第一个AppFunc是怎么来的呢?pipeline又是如何串起来的呢?这很自然就涉及到了前文所提到的第二个重要内容Func<IDictionary<string, object>, Task>Pipeline定义为接收Environment,返回一个TaskFunc,当第一个AppFunc(也就是Func<IDictionary<string, object>, Task>)执行之后返回的其实又是一个AppFunc,而这种链接关系是由OwinBuilder建立的。

OwinBuilder所做的事情主要寻找程序集中的StartUp方法,并根据其中注册pipeline的顺序将各个AppFunc串起来,这将是一个浩大的工程。


OwinBuilder源码阅读

源码参见Microsoft.Owin.Host.SystemWeb.OwinBuilder

通过前文知道,Build方法将被调用,其做的第一件事儿就是寻找Startup方法

internal static OwinAppContext Build()
{
 Action<IAppBuilder> startup = GetAppStartup();
 return Build(startup);
}

GetAppStartup方法主要完成从当前Assembly中寻找AppStartup方法,这也是为什么申明Startup,使其被调用有两种方法:

[assembly: OwinStartup(typeof(XX.Startup))]  //利用OwinStartupAtrribute来指导Startup
<appSettings>  
  <add key="owin:appStartup" value="StartupDemo.ProductionStartup" />
</appSettings>

//在webconfig中定义owin:appStartup键值对,以下将对负责搜索Startup方法的DefaultLoader的源码进行分析,来了解如何定位Startup方法的
internal static Action<IAppBuilder> GetAppStartup()
        {
            string appStartup = ConfigurationManager.AppSettings[Constants.OwinAppStartup];
            var loader = new DefaultLoader(new ReferencedAssembliesWrapper());
            IList<string> errors = new List<string>();
            Action<IAppBuilder> startup = loader.Load(appStartup ?? string.Empty, errors);

            if (startup == null)
            {
                throw new EntryPointNotFoundException(Resources.Exception_AppLoderFailure
                    + Environment.NewLine + " - " + string.Join(Environment.NewLine + " - ", errors)
                    + (IsAutomaticAppStartupEnabled ? Environment.NewLine + Resources.Exception_HowToDisableAutoAppStartup : string.Empty)
                    + Environment.NewLine + Resources.Exception_HowToSpecifyAppStartup);
            }
            return startup;
        }

上面源码展示了调用DefaultLoaderLoad方法来搜索Startup,而Startup是一个Action方法,即接受一个实现了IAppBuilder接口的实例作为参数,返回值为voidAction

    public Action<IAppBuilder> Load(string startupName, IList<string> errorDetails)
        {
            return LoadImplementation(startupName, errorDetails) ?? _next(startupName, errorDetails);
        }

Load方法实际上是对LoadImplementation的一个封装,如果寻找失败则使用_next进行寻找(实际上这会返回null,这不是重点)

private Action<IAppBuilder> LoadImplementation(string startupName, IList<string> errorDetails)
        {
            Tuple<Type, string> typeAndMethod = null;
            startupName = startupName ?? string.Empty;
            // Auto-discovery or Friendly name?
            if (!startupName.Contains(','))
            {
                typeAndMethod = GetDefaultConfiguration(startupName, errorDetails);    //通常会进入这一流程,如果startupName中包含逗号,则对应另一种申明方式
            }

            if (typeAndMethod == null && !string.IsNullOrWhiteSpace(startupName))    //这种申明方式为StartupName = “startupName,assemblyName”
            {
                typeAndMethod = GetTypeAndMethodNameForConfigurationString(startupName, errorDetails);    //对startupName和assemblyName进行分离,并找到对应的assembly加载
                                                                //其中的startupName
            }

            if (typeAndMethod == null)
            {
                return null;
            }

            Type type = typeAndMethod.Item1;
            // default to the "Configuration" method if only the type name was provided    //如果只提供了startup的type,则默认调用其中的Configuration方法
            string methodName = !string.IsNullOrWhiteSpace(typeAndMethod.Item2) ? typeAndMethod.Item2 : Constants.Configuration;

            Action<IAppBuilder> startup = MakeDelegate(type, methodName, errorDetails);    //直接调用startup方法或者做为一个middleware压入List中,后文会讲到具体实现
            if (startup == null)
            {
                return null;
            }

            return builder =>    //再对startup进行一次delegate封装,传入参数为builder,供上层调用
            {
                if (builder == null)
                {
                    throw new ArgumentNullException("builder");
                }

                object value;
                if (!builder.Properties.TryGetValue(Constants.HostAppName, out value) ||
                    String.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture)))
                {
                    builder.Properties[Constants.HostAppName] = type.FullName;    //获取并记录HostAppName
                }
                startup(builder);    //开始构造
            };
        }

由于参数startupName为最初定义的常量,其值为Constants.OwinAppStartup = "owin:AppStartup";所以很明显会调用GetDefaultConfiguration(startupName, errorDetails)方法进一步处理。

    private Tuple<Type, string> GetDefaultConfiguration(string friendlyName, IList<string> errors)
        {
            friendlyName = friendlyName ?? string.Empty;
            bool conflict = false;
            Tuple<Type, string> result = SearchForStartupAttribute(friendlyName, errors, ref conflict);

            if (result == null && !conflict && string.IsNullOrEmpty(friendlyName))
            {
                result = SearchForStartupConvention(errors);
            }

            return result;
        }

这个方法又是对SearchForStartupAttribute的一个封装

先了解一下OwinStartupAttribute

看上文使用到的构造函数

    public OwinStartupAttribute(Type startupType)
            : this(string.Empty, startupType, string.Empty)
        {
        }
    public OwinStartupAttribute(string friendlyName, Type startupType, string methodName)
        {
            if (friendlyName == null)
            {
                throw new ArgumentNullException("friendlyName");
            }
            if (startupType == null)
            {
                throw new ArgumentNullException("startupType");
            }
            if (methodName == null)
            {
                throw new ArgumentNullException("methodName");
            }

            FriendlyName = friendlyName;
            StartupType = startupType;
            MethodName = methodName;
        }

这里默认将FriendlyNameMethodName设置为空,即只记录了Startup类的Type,下面的SearchForStartupAttribute主要也是通过寻找OwinStartupAttribute中的StartupType 来获取Startup的。

private Tuple<Type, string> SearchForStartupAttribute(string friendlyName, IList<string> errors, ref bool conflict)
        {
            friendlyName = friendlyName ?? string.Empty;
            bool foundAnyInstances = false;
            Tuple<Type, string> fullMatch = null;
            Assembly matchedAssembly = null;
            foreach (var assembly in _referencedAssemblies)    // 遍历程序集
            {
                object[] attributes;
                try
                {
                    attributes = assembly.GetCustomAttributes(inherit: false);    // 获取程序集的所有自定义Attribute
                }
                catch (CustomAttributeFormatException)
                {
                    continue;
                }

                foreach (var owinStartupAttribute in attributes.Where(attribute => attribute.GetType().Name.Equals(Constants.OwinStartupAttribute, StringComparison.Ordinal)))    // 对获取到的Attribute进行过滤,只遍历OwinStartupAttribute,即是优先会 //对上文所说的第一种 Startup申明进行调用
                {
                    Type attributeType = owinStartupAttribute.GetType();    //采用反射机制,先获取Type
                    foundAnyInstances = true;

                    // Find the StartupType property.
                    PropertyInfo startupTypeProperty = attributeType.GetProperty(Constants.StartupType, typeof(Type));    //寻找属性名是StartupType,属性类型是Type的属性
                    if (startupTypeProperty == null)    //寻找失败,记录错误
                    {
                        errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.StartupTypePropertyMissing,
                            attributeType.AssemblyQualifiedName, assembly.FullName));
                        continue;
                    }

                    var startupType = startupTypeProperty.GetValue(owinStartupAttribute, null) as Type;    //获取StartupType属性的值,并转换为Type,为反射做准备
                    if (startupType == null)    //获取或者转换失败,记录错误
                    {
                        errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.StartupTypePropertyEmpty, assembly.FullName));
                        continue;
                    }

                    // FriendlyName is an optional property.
                    string friendlyNameValue = string.Empty;    //FriendlyName是可选项,作为对Startup类的别称,不是重点
                    PropertyInfo friendlyNameProperty = attributeType.GetProperty(Constants.FriendlyName, typeof(string));
                    if (friendlyNameProperty != null)
                    {
                        friendlyNameValue = friendlyNameProperty.GetValue(owinStartupAttribute, null) as string ?? string.Empty;
                    }

                    if (!string.Equals(friendlyName, friendlyNameValue, StringComparison.OrdinalIgnoreCase))    //如果未定义FriendlyName则默认是Empty,否则记录错误
                    {
                        errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.FriendlyNameMismatch,
                            friendlyNameValue, friendlyName, assembly.FullName));
                        continue;
                    }

                    // MethodName is an optional property.
                    string methodName = string.Empty;    同理MethodName也是可选项,如果为定义默认是Empty
                    PropertyInfo methodNameProperty = attributeType.GetProperty(Constants.MethodName, typeof(string));
                    if (methodNameProperty != null)
                    {
                        methodName = methodNameProperty.GetValue(owinStartupAttribute, null) as string ?? string.Empty;
                    }

                    if (fullMatch != null)    //表明已经寻找到一个Startup类,则冲突了,说明有重复申明Startup类
                    {
                        conflict = true;
                        errors.Add(string.Format(CultureInfo.CurrentCulture,
                            LoaderResources.Exception_AttributeNameConflict,
                            matchedAssembly.GetName().Name, fullMatch.Item1, assembly.GetName().Name, startupType, friendlyName));
                    }
                    else    //尚未寻找到Startup类,将StartupType和MethodName存为二元组,记录程序集
                    {
                        fullMatch = new Tuple<Type, string>(startupType, methodName);
                        matchedAssembly = assembly;
                    }
                }
            }

            if (!foundAnyInstances)    //未寻找到申明Startup的程序集,记录错误
            {
                errors.Add(LoaderResources.NoOwinStartupAttribute);
            }
            if (conflict)    //如果有冲突,返回null
            {
                return null;
            }
            return fullMatch;    //返回结果
        }

前文讲到MakeDelegate(Type type, string methodName, IList<string> errors)主要作用是将寻找到的startup方法作为一个middleware压入List中,看其源码

private Action<IAppBuilder> MakeDelegate(Type type, string methodName, IList<string> errors)
        {
            MethodInfo partialMatch = null;
            foreach (var methodInfo in type.GetMethods())
            {
                if (!methodInfo.Name.Equals(methodName))
                {
                    continue;
                }

                // void Configuration(IAppBuilder app)    //检测Startup类中的Configuration方法的参数和返回值,这种为默认的方法,也是新建MVC时默认的方法
                if (Matches(methodInfo, false, typeof(IAppBuilder)))    //方法无返回值(void),参数为(IAppBuilder)
                {
                    object instance = methodInfo.IsStatic ? null : _activator(type);    //如果为静态方法,则不需要实例,否则实例化一个Startup对象
                    return builder => methodInfo.Invoke(instance, new[] { builder });    //返回一个Lambda形式的delegate,实际上就是调用Startup的Configuration(IAppBuilder)方法
                }

                // object Configuration(IDictionary<string, object> appProperties)    //另一种Configuration方法,参数为Environment,返回object
                if (Matches(methodInfo, true, typeof(IDictionary<string, object>)))
                {
                    object instance = methodInfo.IsStatic ? null : _activator(type);    //由于传入参数为Dictionary,所以将这个Configuration方法压入middleware的List中
                    return builder => builder.Use(new Func<object, object>(_ => methodInfo.Invoke(instance, new object[] { builder.Properties })));
                }    //builder.Use传入参数是一个Func<object,object>的delegate,实际上就是一个middleware,不过因为在初始化阶段,所以不需要进入下一个stage

                // object Configuration()    //无参数,返回object
                if (Matches(methodInfo, true))
                {
                    object instance = methodInfo.IsStatic ? null : _activator(type);
                    return builder => builder.Use(new Func<object, object>(_ => methodInfo.Invoke(instance, new object[0])));
                }

                partialMatch = partialMatch ?? methodInfo;    //记录找到但不符合三种定义的Configuration方法
            }

            if (partialMatch == null)    //未找到的Configuration,记录错误
            {
                errors.Add(string.Format(CultureInfo.CurrentCulture,
                    LoaderResources.MethodNotFoundInClass, methodName, type.AssemblyQualifiedName));
            }
            else    找到Configuration,但不符合三种定义,记录错误
            {
                errors.Add(string.Format(CultureInfo.CurrentCulture, LoaderResources.UnexpectedMethodSignature,
                    methodName, type.AssemblyQualifiedName));
            }
            return null;
        }

总结:OwinBuilder主要完成对Startup类的寻找,并调用其中的Configuration方法,Configuration有三种签名(传入参数与返回结果),将其封装成一个方法返回给上层,供上层调用。接下来就是最重要的工作,调用Startup中的Configuration具体做了什么,每个middleware是如何注入到pipeline中的,这就是AppBuilder主要做的工作了。

阅读更多

没有更多推荐了,返回首页