最近看了一下开源项目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.现在我们看一下这个类。

 using Microsoft.Owin;

 [assembly: OwinStartup(typeof(Owin.Analysis.Startup))]
namespace Owin.Analysis
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Run(context =>
{
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync("Hello World");
});
}
}
}

看起来这个代码十分的优雅,添加几个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方法。

 private static Tuple<Type, Delegate, object[]> ToMiddlewareFactory(object middlewareObject, object[] args)
{
if (middlewareObject == null)
{
throw new ArgumentNullException("middlewareObject");
} var middlewareDelegate = middlewareObject as Delegate;
if (middlewareDelegate != null)
{
return Tuple.Create(GetParameterType(middlewareDelegate), middlewareDelegate, args);
} Tuple<Type, Delegate, object[]> factory = ToInstanceMiddlewareFactory(middlewareObject, args);
if (factory != null)
{
return factory;
} factory = ToGeneratorMiddlewareFactory(middlewareObject, args);
if (factory != null)
{
return factory;
} if (middlewareObject is Type)
{
return ToConstructorMiddlewareFactory(middlewareObject, args, ref middlewareDelegate);
} throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_MiddlewareNotSupported, middlewareObject.GetType().FullName));
}

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

         private static Tuple<Type, Delegate, object[]> ToConstructorMiddlewareFactory(object middlewareObject, object[] args, ref Delegate middlewareDelegate)
{
var middlewareType = middlewareObject as Type;
ConstructorInfo[] constructors = middlewareType.GetConstructors();
foreach (var constructor in constructors)
{
ParameterInfo[] parameters = constructor.GetParameters();
Type[] parameterTypes = parameters.Select(p => p.ParameterType).ToArray();
if (parameterTypes.Length != args.Length + )
{
continue;
}
if (!parameterTypes
.Skip()
.Zip(args, TestArgForParameter)
.All(x => x))
{
continue;
} ParameterExpression[] parameterExpressions = parameters.Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
NewExpression callConstructor = Expression.New(constructor, parameterExpressions);
middlewareDelegate = Expression.Lambda(callConstructor, parameterExpressions).Compile();
return Tuple.Create(parameters[].ParameterType, middlewareDelegate, args);
} throw new MissingMethodException(string.Format(CultureInfo.CurrentCulture,
Resources.Exception_NoConstructorFound, middlewareType.FullName, args.Length + ));
}

这个时候我们已经将中间件注册到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;
}

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

Owin Katana 的底层源码分析的更多相关文章

  1. List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 继上一章继续讲解,上章内容: List-ArreyLlist集合基础增强底层源码分析:https:// ...

  2. List-ArrayList集合基础增强底层源码分析

    List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...

  3. LInkedList总结及部分底层源码分析

    LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...

  4. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

  5. JAVA ArrayList集合底层源码分析

    目录 ArrayList集合 一.ArrayList的注意事项 二. ArrayList 的底层操作机制源码分析(重点,难点.) 1.JDK8.0 2.JDK11.0 ArrayList集合 一.Ar ...

  6. 分布式缓存技术之Redis_Redis集群连接及底层源码分析

    目录 1. Jedis 单点连接 2. Jedis 基于sentinel连接 基本使用 源码分析 本次源码分析基于: jedis-3.0.1 1. Jedis 单点连接   当是单点服务时,Java ...

  7. Servlet和Tomcat底层源码分析

    Servlet 源码分析   Servlet 结构图 Servlet 和 ServletConfig 都是顶层接口,而 GenericServlet 实现了这两个顶层接口,然后HttpServlet ...

  8. 持久层Mybatis3底层源码分析,原理解析

    Mybatis-持久层的框架,功能是非常强大的,对于移动互联网的高并发 和 高性能是非常有利的,相对于Hibernate全自动的ORM框架,Mybatis简单,易于学习,sql编写在xml文件中,和代 ...

  9. Java——HashMap底层源码分析

    1.简介 HashMap 根据键的 hashCode 值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的. HashMap 最多只允许一条记录的key为 nu ...

随机推荐

  1. Web三维编程入门总结之三:3D碰撞检测初探

    自己动手写一个方法比分析他人的写的方法困难很多,由此而来的对程序的进一步理解也是分析别人的代码很难得到的. 一.先来几张效果图: 1.场景中有两个半径为1的球体,蓝色线段从球心出发指向球体的“正向” ...

  2. Gitflow分支管理策略

    Gitflow存在两个记录项目历史的分支 Master分支:存储(官方的,正式的)项目发布历史记录的分支. develop分支:充当功能的集成分支. Develop分支将包含项目的完整历史记录,而ma ...

  3. tortoise 设置beyond Compare比较工具

    1.桌面右击tortoiseSVN->setting->Diff Viewer面板,选择external,选中beyond Compare路径

  4. 实现一个简单的基于动态代理的 AOP

    实现一个简单的基于动态代理的 AOP Intro 上次看基于动态代理的 AOP 框架实现,立了一个 Flag, 自己写一个简单的 AOP 实现示例,今天过来填坑了 目前的实现是基于 Emit 来做的, ...

  5. Windows DC域控由server08r2升级至server2016测试

    测试环境 原DC: csctest.com CSCDC01 192.168.100.1 server08r2 CSCDC02 192.168.100.2 server08r2 要求: 原两台旧主机均更 ...

  6. ubuntu-18.0.4 samba安装

    (1)安装 sudo apt-get -y install samba samba-common (2)创建一个用于分享的samba目录. mkdir /home/myshare (3)给创建的这个目 ...

  7. 重磅!阿里发布《Java开发手册(泰山版)》

    最近,阿里的<Java开发手册>又更新了,这个版本历经一年的修炼,取名:<Java开发手册(泰山版)>正式出道. 正所谓无规矩不成方圆,在程序员的世界里,也存在很多规范,阿里出 ...

  8. 0day堆(2)堆的调试实验

    堆的调试实验 调试态堆管理策略和常态堆管理策略:前者只使用空表不用块表,不真实 使用调试器加载函数会触发前者 __asm int3 调试最真实的栈 未启用块表的堆区信息 堆区起始位置(假设为0x005 ...

  9. webug3.0靶场渗透基础Day_2(完)

    第八关: 管理员每天晚上十点上线 这题我没看懂什么意思,网上搜索到就是用bp生成一个poc让管理员点击,最简单的CSRF,这里就不多讲了,网上的教程很多. 第九关: 能不能从我到百度那边去? 构造下面 ...

  10. 2019-2020-1 20199329《Linux内核原理与分析》第八周作业

    <Linux内核原理与分析>第八周作业 一.本周内容概述: 理解编译链接的过程和ELF可执行文件格式 编程练习动态链接库的两种使用方式 使用gdb跟踪分析一个execve系统调用内核处理函 ...