AppBuilder(四)【SignatureConversions】

風靈使發表於2018-07-09

原始碼參見

Microsoft.Owin.Builder.AppBuilder

Microsoft.Owin.Infrastructure.SignatureConversions

AppBuilder中遇到了_middleware三元組的Item1,微軟工程師稱之為signature不一致的問題,一種是AppFunc,一種是OwinMiddleware,因此需要用到SignatureConversions,這是在AppBuilder例項化的時候完成的工作,先看看AppBuilder的建構函式。

public AppBuilder()
        {
            _properties = new Dictionary<string, object>();    //初始化環境字典
            _conversions = new Dictionary<Tuple<Type, Type>, Delegate>();    //初始化_conversion字典
            _middleware = new List<Tuple<Type, Delegate, object[]>>();    //初始化_middleware連結串列

            _properties[Constants.BuilderAddConversion] = new Action<Delegate>(AddSignatureConversion);    //繫結AddSignatureConversion方法
            _properties[Constants.BuilderDefaultApp] = NotFound;    //繫結預設最後一步處理流程

            SignatureConversions.AddConversions(this);    //開始往_conversions中新增具體的處理方法
        }

實際的_conversions完成初始化由SignatureConversions.AddConversions完成。

public static class SignatureConversions
    {
        /// <summary>
        /// Adds adapters between <typeref name="Func&lt;IDictionary&lt;string,object&gt;, Task&gt;"/> and OwinMiddleware.
        /// </summary>
        /// <param name="app"></param>
        public static void AddConversions(IAppBuilder app)    //實際上是對Conversion1和Conversion2的包裝,呼叫的是AppBuilderExtension中的方法
        {
            app.AddSignatureConversion<AppFunc, OwinMiddleware>(Conversion1);    //完成從AppFunc到OwinMiddleware的轉換
            app.AddSignatureConversion<OwinMiddleware, AppFunc>(Conversion2);    //完成從OwinMiddleware到AppFunc的轉換
        }

        private static OwinMiddleware Conversion1(AppFunc next)
        {
            return new AppFuncTransition(next);
        }

        private static AppFunc Conversion2(OwinMiddleware next)
        {
            return new OwinMiddlewareTransition(next).Invoke;
        }
    }

來看看AddSignatureConversion,還是一層封裝

public static void AddSignatureConversion<T1, T2>(this IAppBuilder builder, Func<T1, T2> conversion)
        {
            AddSignatureConversion(builder, (Delegate)conversion);    //實際會呼叫下面的方法
        }
public static void AddSignatureConversion(this IAppBuilder builder, Delegate conversion)
        {
            if (builder == null)
            {
                throw new ArgumentNullException("builder");
            }
            object obj;
            if (builder.Properties.TryGetValue("builder.AddSignatureConversion", out obj))    //尋找AppBuilder建構函式中繫結的AddSignatureConversion,是Action<Delegate>
            {
                var action = obj as Action<Delegate>;    //還原為Action<Delegate>
                if (action != null)
                {
                    action(conversion);    //將conversion存入_conversion字典
                    return;
                }
            }
            throw new MissingMethodException(builder.GetType().FullName, "AddSignatureConversion");
        }

來看看這個Action<Delegate>在拿到private static OwinMiddleware Conversion1(AppFunc next)這個方法之後做了什麼。

private void AddSignatureConversion(Delegate conversion)
        {
            if (conversion == null)
            {
                throw new ArgumentNullException("conversion");
            }

            Type parameterType = GetParameterType(conversion);    //以Conversion1為例,這裡的parameterType為AppFunc,ReturnType為OwinMiddleware
            if (parameterType == null)
            {
                throw new ArgumentException(Resources.Exception_ConversionTakesOneParameter, "conversion");
            }
            Tuple<Type, Type> key = Tuple.Create(conversion.Method.ReturnType, parameterType);    //使用conversion的ReturnType和parameterType作為key,相當於簽名
            _conversions[key] = conversion;    //記錄conversion
        }

同理Conversion2也是這樣的操作,不過parameterTypeOwinMiddleware,而ReturnTypeAppFunc

解釋一下轉換原理,Conversion1這個方法return了一個AppFuncTransition例項,而AppFuncTransition繼承自OwinMiddleware,自然就完成了轉換。

Conversion2這個方法返回的是OwinMiddlewareTransition例項的Invoke方法,自然就是一個AppFunc

可見兩種簽名對應的是OwinMiddleware例項和AppFunc委託的相互轉換。

回顧AppBuilder(三)中的_middleware.Reverse遍歷操作:

第一次取到的是app.Use(decoupler)對應的middleware,第一次Convert操作完成了PipelineStage的切換,而且使得PreHandlerExcute這一StageEntryPointNotFound,新建的Authenticate這一StageNextStage指向PreHandlerExcute這一Stage,第二次Convert操作很快返回,現在app指向(AppFunc)IntegratedPipelineContext.ExitPointInvoked

第二次取到的是app.Use(typeof(CookieAuthenticationMiddleware), app, options)對應的CookieAuthenticationMiddleware,三元組解耦之後

Convert(neededSignature, app)的時候等同於Convert(typeof(OwinMiddleware), AppFunc)

signature.IsInstanceOfType(app)typeof(Delegate).IsAssignableFrom(signature)均會返回false,所以會進入本文的重點

foreach (var conversion in _conversions)    //經過推斷會呼叫Conversion1
            {
                Type returnType = conversion.Key.Item1;    //returnType為OwinMiddleware
                Type parameterType = conversion.Key.Item2;    //parameterType為AppFunc
                if (parameterType.IsInstanceOfType(app) &&
                    signature.IsAssignableFrom(returnType))
                {
                    return conversion.Value.DynamicInvoke(app);    //等同於呼叫new AppFuncTransition(app)
                }
            }

_conversions字典中有兩個conversion,分別為Conversion1Conversion2,由於我們需要從AppFuncOwinMiddleware的轉換,經過引數和返回值的檢查,會呼叫Conversion1進行轉換例項化了一個AppFuncTransition,引數為app

來看看AppFuncTransition

internal sealed class AppFuncTransition : OwinMiddleware
    {
        private readonly AppFunc _next;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="next"></param>
        public AppFuncTransition(AppFunc next) : base(null)    //呼叫的是這個建構函式,base(null)父物件例項化一個空的middleware
        {
            _next = next;    //使_next指向app
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public override Task Invoke(IOwinContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            return _next(context.Environment);
        }
    }

可見上面的程式碼巧妙的返回一個Next=null的OwinMiddleware,而利用了(AppFunc)_next來記錄連結關係,當呼叫這個OwinMiddleware的Invoke方法的時候,實際執行的還是_next(context.Environment),等同於還是執行的AppFunc(context.Envrionment),與原來並沒有什麼區別。

再看看OwinMiddlewareTransition

internal sealed class OwinMiddlewareTransition
    {
        private readonly OwinMiddleware _next;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="next"></param>
        public OwinMiddlewareTransition(OwinMiddleware next)
        {
            _next = next;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="environment">OWIN environment dictionary which stores state information about the request, response and relevant server state.</param>
        /// <returns></returns>
        public Task Invoke(IDictionary<string, object> environment)
        {
            return _next.Invoke(new OwinContext(environment));
        }
    }

我們需要的是OwinMiddlewareTransition.Invoke方法,這是一個AppFunc,也是Conversion2返回的,當呼叫這個Invoke方法的時候實際執行的是_next.Invoke(new OwinContext(environment)),等同於執行OwinMiddleware.Invoke(new OwinContext(envrionment)),與原來也並沒有什麼區別。

這裡可以看出雖然例項和方法之間實現了轉換,但因為都會呼叫Invoke方法,與不轉換之前並沒有什麼區別,不改變執行的邏輯,只是改變了承載這個Invoke方法的載體而已,這也是pipelinemiddlewarestage更換能夠無縫銜接的原因。

現在我們知道經過Conversion1轉換之後,app更新為Convert的返回值,由一個AppFunc變成了一個OwinMiddleware

app = middlewareDelegate.DynamicInvoke(invokeParameters)執行的時候等同於執行OwinMiddleware.Invoke(OwinMiddleware, IAppBuilder app, CookieAuthenticationOptions options),返回一個CookieAuthenticationMiddleware

對應的建構函式為

public CookieAuthenticationMiddleware(OwinMiddleware next, IAppBuilder app, CookieAuthenticationOptions options) : base(next, options)

最終達到的效果是CookieAuthenticationMiddleware執行Next.Invoke(conetxt)方法,實際上是執行的IntegratedPipelineContext.ExitPointInvoked(context.Environment),執行PipelineStage的切換工作。

此時appCookieAuthenticationMiddleware的例項,同理這次的app = Convert(neededSignature, app)會很快返回,app不變。

至此已經可以解釋很多東西了。

1 為什麼要反向遍歷?

因為每個OwinMiddleware的建構函式的第一個引數或者Func<AppFunc,AppFunc>的引數都是一個next,指向下一個要執行的元件,那麼這個next不應該為空,而且要真實有效,反向遍歷會先生成後面OwinMiddleware或者Func,然後用其作為前一個的引數,這能保證構造的pipeline的有效性。

2 OwinMiddleware或者Func是如何串起來的?

如上所述,每個OwinMiddleware或者Func的第一個引數都是一個nextOwinMiddlewareFunc的方法都會呼叫其Invoke方法,不同的是OwinMiddlewareInvoke是一個可以重寫的方法,引數為OwinContext,而FuncDelegate,其Invoke方法等同執行這個Func,引數為Envrionment。在Invoke中做了自己的工作之後,執行next.Invoke方法,並返回其結果,這樣就串起來了。

3 PipelineStage是如何切換的?

這將是下一節所要涉及的內容,每個PipelineStage都記錄了NextStagePipeline排程部分可以在所有非同步處理完成之後啟用NextStage,這主要是未開源的System.Web.Application來完成排程的,採用了事件的機制。

總結,每個PipelineStage有個EntryPointExitPoint,他們以及他們之前的其他OwinMiddleware或者Func通過next串聯起來,執行的時候,由HttpApplication觸發相應的事件。pipeline能流動的關鍵因素是每個元件對於下一元件都有合法有效引用,所以採用反向遍歷的方法來重建,Func呼叫下一Funcnext.Invoke(environment)OwinMiddleware呼叫下一OwinMiddlewareNext.Invoke(context),所以conversion主要是OwinMiddleware或者Func看到的next都是跟自己一個型別的。OwinMiddleware為了與Func一致,都採用了Invoke作為入口。