欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Owin Katana 的底层源码分析

程序员文章站 2023-08-25 22:57:04
最近看了一下开源项目asp.net katana,感觉公开的接口非常的简洁优雅,channel 9 说是受到node.js的启发设计的,Katana是一个比较老的项目,现在已经整合到asp.net core中。 从github克隆下来的项目,这个博客专门是从代码角度去理解katana项目,所以本篇随 ......

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

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

Owin Katana 的底层源码分析

代码结构如上,简单分析一下各个文件夹的含义,这对于理解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

上面已经介绍了各个文件夹所对应的功能,相必大部分人都是一脸蒙蔽,但是不用担心,下面就来看看具体的代码,当然是从最小的例子出发。点击  你就能跳到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;
        }

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