zoukankan      html  css  js  c++  java
  • Owin Katana 的底层源码分析

    最近看了一下开源项目asp.net katana,感觉公开的接口非常的简洁优雅,channel 9 说是受到node.js的启发设计的,Katana是一个比较老的项目,现在已经整合到asp.net core中。

    github克隆下来的项目,这个博客专门是从代码角度去理解katana项目,所以本篇随笔针对已经对OWIN有所了解的人,如果只是入门的话可以跑一下MSDN的源码再来阅读本篇文章。

    代码结构如上,简单分析一下各个文件夹的含义,这对于理解katana项目的整体结构有一个大的轮廓。

      .build文件夹顾名思义就是编译的文件夹,在没使用vs的时候你可以单击build.cmd 去编译这个项目,十分的方便。

      .Nuget就是包管理工具的配置文件,这个我们可以忽略。同理.Prerelease。

      Development是本次的研究重点,当你打开这个文件夹的时候你会发现一个类库Microsoft.Owin的类库,这个是OWIN组件的经典实现。

      FunctionTests是单元测试的类库

      Hosting 是server的抽象层,OWIN 将服务器进行抽象化,Hosting 就是能够管理Server的一层,像WebApp就能开启一个httplister服务,详细稍后再讲。

      Middleware是一些中间件的实现,在katana已经将管道模型虚拟化成中间件

      Performance 和Sandbox 是微软的一些测试工具

      Security 是微软已经写好的验证中间件,其中包括JWT和Oauth的验证方式

      Server 就是服务器的实现

      Owin.Analysis是我本人建的web程序用来debug

    上面已经介绍了各个文件夹所对应的功能,相必大部分人都是一脸蒙蔽,但是不用担心,下面就来看看具体的代码,当然是从最小的例子出发。点击 getting started with owin and katan 你就能跳到MSDN得到最小的例子。里面的一系列操作就为了添加下面的一个类和几个reference.现在我们看一下这个类。

     1 using Microsoft.Owin;
     2 
     3 [assembly: OwinStartup(typeof(Owin.Analysis.Startup))]
     4 namespace Owin.Analysis
     5 {
     6     public class Startup
     7     {
     8         public void Configuration(IAppBuilder app)
     9         {
    10             app.Run(context =>
    11             {
    12                 context.Response.ContentType = "text/plain";
    13                 return context.Response.WriteAsync("Hello World");
    14             });
    15         }
    16     }
    17 }

    看起来这个代码十分的优雅,添加几个reference和一个类就让请求到达Hello World。我们先分析这个类,首先程序集特性OwinStartupAtribute将当前类保存在元数据中。然后写了一个Configuration方法,获取一个IAppBuilder 参数调用Run方法,Run方法传递一个委托进去,我们的处理逻辑就在这一个委托里。

    这里面我们分析一下核心接口IAppBuilder的经典实现者AppBuilder,IAppBuilder的接口如下,

    using System;
    using System.Collections.Generic;
    
    namespace Owin
    {
        public interface IAppBuilder
        {
            IDictionary<string, object> Properties { get; }//请求的参数
    
            object Build(Type returnType);//中间件链接
            IAppBuilder New();//创建一个新的对象
            IAppBuilder Use(object middleware, params object[] args);//注册中间件
        }
    }

    好的我们来分析一下AppBuilder中间件的注册实现。在app.Run 打完break point你就可以进入app.use方法,首先在AppBuilderUseExtensions这个类里对use的入口写了一大堆扩展方法。app.Run就是其中的一个,当你用app.Run注册中间件的时候是没有下一个中间件的引用的。

            public static void Run(this IAppBuilder app, Func<IOwinContext, Task> handler)
            {
                if (app == null)
                {
                    throw new ArgumentNullException("app");
                }
                if (handler == null)
                {
                    throw new ArgumentNullException("handler");
                }
    
                app.Use<UseHandlerMiddleware>(handler);
            }

    在经典的实现中,参数middleware会有两种情况,一种是delegate,一种是type,如果是type类型,则他的构造方法接受next为参数,并且里面有一个公开的Invoke方法。如果是委托,当前委托作为参数传递到next中。

     public IAppBuilder Use(object middleware, params object[] args)
            {
                _middleware.Add(ToMiddlewareFactory(middleware, args));
                return this;
            }

    下面的代码是ToMiddlewareFactory的实现,在第9行和第27行分别判断了中间件对象是委托类型还是type类型,由于本题例子是type对象,我们分析一下ToConstructorMiddlewareFactory方法。

     1 private static Tuple<Type, Delegate, object[]> ToMiddlewareFactory(object middlewareObject, object[] args)
     2         {
     3             if (middlewareObject == null)
     4             {
     5                 throw new ArgumentNullException("middlewareObject");
     6             }
     7 
     8             var middlewareDelegate = middlewareObject as Delegate;
     9             if (middlewareDelegate != null)
    10             {
    11                 return Tuple.Create(GetParameterType(middlewareDelegate), middlewareDelegate, args);
    12             }
    13 
    14             Tuple<Type, Delegate, object[]> factory = ToInstanceMiddlewareFactory(middlewareObject, args);
    15             if (factory != null)
    16             {
    17                 return factory;
    18             }
    19 
    20             factory = ToGeneratorMiddlewareFactory(middlewareObject, args);
    21             if (factory != null)
    22             {
    23                 return factory;
    24             }
    25 
    26             if (middlewareObject is Type)
    27             {
    28                 return ToConstructorMiddlewareFactory(middlewareObject, args, ref middlewareDelegate);
    29             }
    30 
    31             throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
    32                 Resources.Exception_MiddlewareNotSupported, middlewareObject.GetType().FullName));
    33         }

    在第5行获取type类型的所有构造方法,第8行获取构造方法的所有参数,在第14行有个trick,用zip方法判断参数类型是否和构造方法类型是一致的。如果是一致的则继续往下走,在第22行和第23行利用委托将构造方法创建成lambda表达式,然后生成元祖,第一个是next的类型,第二个是type的构造方法,第三个是type构造方法所需的参数。然后将元祖加入到AppBuild所维护的中间件对象。

     1  
     2         private static Tuple<Type, Delegate, object[]> ToConstructorMiddlewareFactory(object middlewareObject, object[] args, ref Delegate middlewareDelegate)
     3         {
     4             var middlewareType = middlewareObject as Type;
     5             ConstructorInfo[] constructors = middlewareType.GetConstructors();
     6             foreach (var constructor in constructors)
     7             {
     8                 ParameterInfo[] parameters = constructor.GetParameters();
     9                 Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();
    10                 if (parameterTypes.Length != args.Length + 1)
    11                 {
    12                     continue;
    13                 }
    14                 if (!parameterTypes
    15                     .Skip(1)
    16                     .Zip(args, TestArgForParameter)
    17                     .All(x => x))
    18                 {
    19                     continue;
    20                 }
    21 
    22                 ParameterExpression[] parameterExpressions = parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
    23                 NewExpression callConstructor = Expression.New(constructor, parameterExpressions);
    24                 middlewareDelegate = Expression.Lambda(callConstructor, parameterExpressions).Compile();
    25                 return Tuple.Create(parameters[0].ParameterType, middlewareDelegate, args);
    26             }
    27 
    28             throw new MissingMethodException(string.Format(CultureInfo.CurrentCulture,
    29                 Resources.Exception_NoConstructorFound, middlewareType.FullName, args.Length + 1));
    30         }

    这个时候我们已经将中间件注册到AppBuilder对象了。注册完中间件的对象我们还需要做一件事就是将这些中间件chained together,这些实现就是Build 方法中,而Build方法BuildInternal方法,这个时候会产生一个entry point供调用。

    现在我们重点看一下这个build方法。

        public object Build(Type returnType)
            {
                return BuildInternal(returnType);
            }

    Build方法调用私有的BuildInternal的方法。

    private object BuildInternal(Type signature)
            {
                object app;
                if (!_properties.TryGetValue(Constants.BuilderDefaultApp, out app))
                {
                    app = NotFound;
                }
    
                foreach (var middleware in _middleware.Reverse())
              {
                    Type neededSignature = middleware.Item1;
                    Delegate middlewareDelegate = middleware.Item2;
                    object[] middlewareArgs = middleware.Item3;
    
                    app = Convert(neededSignature, app);
                    object[] invokeParameters = new[] { app }.Concat(middlewareArgs).ToArray();
                    app = middlewareDelegate.DynamicInvoke(invokeParameters);
                    app = Convert(neededSignature, app);
                }
    
                return Convert(signature, app);
            }

    我们可以看到它是怎样将中间件chained together的,在我们之前注册的时候实际上middleware元祖会保存三个信息,第一个type就是构造函数的第一个类型,第二个委托是useHandlerMiddleware的构造方法,第三个是构造方法的参数(除了第一个),Reverse的方法会将中间件逆序,这样保证调用的顺序就是你注册的顺序,后面的是chain的逻辑,app的变量实际上就是下一个中间件构造函数的next,当得到第一个中间件的时候,里面的next会保存第二个中间件的处理逻辑,同样第二个next就是第三个...,这样chained together得到的就是第一个中间件的逻辑,所以你们在用app.Use的方法就会有一个参数next,并且需要手动调用一下。

    得到这些之后需要的就是要将中间件注册到application管道事件呢。因为asp.net的是一个大的切面框架。

     public void Initialize(HttpApplication application)
            {
                for (IntegratedPipelineBlueprintStage stage = _blueprint.FirstStage; stage != null; stage = stage.NextStage)
                {
                    var segment = new IntegratedPipelineContextStage(this, stage);
                    switch (stage.Name)
                    {
                        case Constants.StageAuthenticate:
                            application.AddOnAuthenticateRequestAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StagePostAuthenticate:
                            application.AddOnPostAuthenticateRequestAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StageAuthorize:
                            application.AddOnAuthorizeRequestAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StagePostAuthorize:
                            application.AddOnPostAuthorizeRequestAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StageResolveCache:
                            application.AddOnResolveRequestCacheAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StagePostResolveCache:
                            application.AddOnPostResolveRequestCacheAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StageMapHandler:
                            application.AddOnMapRequestHandlerAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StagePostMapHandler:
                            application.AddOnPostMapRequestHandlerAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StageAcquireState:
                            application.AddOnAcquireRequestStateAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StagePostAcquireState:
                            application.AddOnPostAcquireRequestStateAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        case Constants.StagePreHandlerExecute:
                            application.AddOnPreRequestHandlerExecuteAsync(segment.BeginEvent, segment.EndEvent);
                            break;
                        default:
                            throw new NotSupportedException(
                                string.Format(CultureInfo.InvariantCulture, Resources.Exception_UnsupportedPipelineStage, stage.Name));
                    }
                }
                // application.PreSendRequestHeaders += PreSendRequestHeaders; // Null refs for async un-buffered requests with bodies.
                application.AddOnEndRequestAsync(BeginFinalWork, EndFinalWork);
            }

    这里面有一个概念就是IntegratedPipelineBlueprintStage,这个是一个链表结构,每个对应的就是管道事件,每个stage都有entry point,这样方便我们在不同的管道事件中运行中间件,在BeginEvent里我们得到stage 的entry point,然后异步调用得到结果。entry point 就是我们上例build得到的结果。

      private async Task RunApp(AppFunc entryPoint, IDictionary<string, object> environment, TaskCompletionSource<object> tcs, StageAsyncResult result)
            {
                try
                {
                    await entryPoint(environment);
                    tcs.TrySetResult(null);
                    result.TryComplete();
                }
                catch (Exception ex)
                {
                    // Flow the exception back through the OWIN pipeline.
                    tcs.TrySetException(ex);
                    result.TryComplete();
                }
            }

    然后我们分析一下怎么在不同的管道中注册事件。在MSDN的文档描述的。

    app.UseStageMarker(PipelineStage.Authenticate)

    这个api会创建一个IntegratedPipelineBlueprintStage,上文说这是一个链表结构,之间用next属性连接。在不同的stage中会有entry point,然后在上面的例子中注册到不同的管道中去调用。下图是api的代码。

      public static IAppBuilder UseStageMarker(this IAppBuilder app, string stageName)
            {
                if (app == null)
                {
                    throw new ArgumentNullException("app");
                }
    
                object obj;
                if (app.Properties.TryGetValue(IntegratedPipelineStageMarker, out obj))
                {
                    var addMarker = (Action<IAppBuilder, string>)obj;
                    addMarker(app, stageName);
                }
                return app;
            }

    好的,到这里了,谢谢大家阅读,如果有任何不理解的欢迎交流:)

  • 相关阅读:
    安全麻烦不断 Mozilla再次推迟火狐浏览器3.1版发布 狼人:
    法国海军计算机遭病毒入侵 战机受影响停飞两天 狼人:
    金融危机下 如何看待SMB安全变化 狼人:
    卡巴斯基遭黑客SQL入侵 曝光敏感信息 狼人:
    经济危机致高科技人才变身网络黑客 狼人:
    McAfee 09安全威胁预测:低迷经济推动病毒流行 狼人:
    Windows 7用户帐户控制功能将在RC版修正 狼人:
    微软否认Windows 7存在缺陷 称UAC不会被黑客利用 狼人:
    选中网页内容safari浏览器复制粘贴
    插入容器STL学习笔记(八) 序列式容器 共性
  • 原文地址:https://www.cnblogs.com/neilhu/p/12802038.html
Copyright © 2011-2022 走看看