有梦想的鱼
写代码我得再认真点,当我最终放下键盘时,我实在不想仍有太多疑惑

这篇文章只是我学习Web API框架的输出,学习方法还是输出倒逼输入比较行得通,所以不管写的好不好,坚持下去,肯定有收获。篇幅比较长,仔细思考阅读下来大约需要15分钟,涉及类图有可能在手机显示不完整,可以切换电脑版阅读。

做.NET开发有好几年时间了,从很久之前的WebFormMVC,再到目前前后端分离模式下RESTful风格的 Web API ,相信这些Web框架很多人都或多或少的用过,也算见证了NET Web端的某一阶段的发展吧,同时很多技术随着发展和迭代,以及前后端分离模式的普及和兴起,用的机会少了,难免可能觉得已经过时了,同时现在流行的Web框架太多太多,不局限于.Net,同样也很优秀,例如:

Java的Spring | Hibernate

Python的Django | Flask

Node的Express和Koa

其实最重要的不是用了多少,知道多少,而是有多少沉淀,在使用领域虽然可能存在过时了,但是从技术的角度,框架设计的思想代码风格技术点的使用把控甚至变量声明等等,都值得我们去学习。

分享方式

  1. Web API 路由注册路由处理通过阅读源代码以及边说明的的方式来阐述。
  2. Web API 管道组装和扩展虽然我们在阅读源码时会接触到它,但我仍然会用Demo的方式来说明它,因为我觉得它设计的真的很棒,忍不住要单独拎出来说一下,虽然有可能把自己讲懵,但我仍然想尝试一下。

1. Web API 路由简介

一个ASP.NET的Web应用具有一个全局的路由表,它是通过一个RouteTable类中的一个类型为RouteCollectionRoutes静态属性来表示的。

为什么这么说呢?或许这样说不太好理解,用一个简单的例子来说明,我们修改Api框架启动时,注册路由的方式,改为直接用 RouteTable.Routes来添加,这种做法跟框架提供的是一样的,最终都是将路由添加到一个地方。

在针对于路由表这一点上面不仅仅只有WebAPI是这么做的,他同样适用于WebFormMVC 同样可以直接在应用程序启动时网路由表中直接添加数据,不过此处只为了证实上述描述,在日常开发中是否可以这样用,完全取决于你自己。

1.框架提供的

public static void Register(HttpConfiguration config)
{
     // Web API 框架提供的路由
     config.Routes.MapHttpRoute(
         name: "DefaultApi",
         routeTemplate: "api/{controller}/{Action}/{id}",
         defaults: new { id = RouteParameter.Optional }
     );
}

2.通过直接路由表添加

 //在WebApiConfig.Register中修改注册路由.
 public static void Register(HttpConfiguration config)
 {
     //验证路由是否加到全局路由表
     var ro = new { id = RouteParameter.Optional };
     //获取默认WebAPI路由处理器
     IRouteHandler routeHandler = HttpControllerRouteHandler.Instance;
     RouteValueDictionary defaults = new RouteValueDictionary(ro);
     Route route = new Route("api/{controller}/{Action}/{id}", defaults, routeHandler);
     RouteTable.Routes.Add("DefaultApi", route);
 }

3.思考的问题

1.我们思考RouteCollection为什么是静态的?

因为静态对象会一直存在内存中,直到程序池下一次回收之前.`

2.我们在用WebAPI开发应用接口的时候,前端Url请求是怎么通过路由到达我们对应的控制器和Action的?

其实是根据路由来控制的,至于怎么控制,怎么实现,后面会慢慢介绍.

3.路由控制如何路由呢?

1.先注册路由,再将路由和处理器绑定。

2.然后用户请求根据请求的Url 匹配对应的处理器,再由处理器进行路由模板规则解析。

3.根据解析到的规则反射找到Contrller和Action。

2. Web API 路由注册

路由注册这一部分在MVC5之后就有2部分了,新增了一个特性路由,此次不做分享,后续有机会再补上..
我们看下简单的路由注册图,当然其中还有很多东西,只是画的比较简陋,

上面的所表示的流程中,我们作为使用框架的开发人员,关注的只是其中一小部分,从代码角度能看到,就只有如下很简单的几句代码,并且连这个代码都是框架生成的,从另一个角度也说明框架的封装比较完整和强大,让开发人员只关注自己的业务就行了

注册路由流程部分

1.注册方式

1.在Web Api程序启动时,首先调用GlobalConfiguration.Configure(WebApiConfig.Register)方法,这个方法接收的参数是一个Action<HttpConfiguration> 作为参数,看到Action我们的第一反应就是作为回调执行, 框架给我们预留了扩展空间,用户扩展的内容在内部执行

2.说白了GlobalConfiguration.Configure()方法的参数,需要一个委托,而委托的本质就是一个无返回值,包含HttpConfiguration类型的实例作为参数的方法,我们转到框架定义的WebApiConfig.Register()方法,毫无疑问它符合要求,所以这个方法就是在框架中被GlobalConfiguration.Configure() 执行的回调,而这个回调的作用就是注册路由

注册路由作为框架GlobalConfiguration.Configure的回调,与上图中相等

2.GlobalConfiguration

我们发现更多的信息在GlobalConfiguration类中,继续一步步解读,打开源码找到GlobalConfiguration这个类来看

1.它是个静态类包含一个重要的方法Configure(Action configurationCallback)

2.它包含3个重要的静态属性ConfigurationDefaultHandlerDefaultServer

3.三个属性被调用时就已经初始化了,但被Lazy类型包裹,说明是被延迟执行,具体延迟执行的时机就是调用它的Value属性时。

4.目前在路由注册阶段只介绍Configuration,剩下2个在路由解析部分在分享.

1.我们首先看GlobalConfiguration.Configure(Action configurationCallback) 的内部做了2件事,执行我们扩展的回调,也就是注册路由的业务,然后检查初始化HttpConfiguration

执行回调需要的参数,正是第一个属性Configuration 通过该类型的Routes属性的MapHttpRoute扩展方法来往路由表中添加数据。可以知道我们只要搞清楚了HttpConfiguration类型的Configuration的来源,就能搞清楚很多事情。

我们找到HttpConfiguration类型的Configuration是怎么初始化的,先展开它,查看代码看到他是由内部的CreateConfiguration()方法创建的

3.注册流程步骤

1.在CreateConfiguration()HttpConfiguration被构造时我们经过查看上下文发现

1.传入一个HostedHttpRouteCollection对象,并在内部赋值给HttpRouteCollection类型的_routes属性

2.HostedHttpRouteCollection在构造时,传入了我们的全局路由表RouteTable.Routes

2.我们找到映射路由的扩展方法MapHttpRoute可以知道我们调用它的Route属性是一个HostedHttpRouteCollection也就是说,在MapHttpRouteCreateRoute()是由HostedHttpRouteCollection来调用的

3.继续转到HostedHttpRouteCollection类中的CreateRoute()内部看到它返回一个HostedHttpRoute而它就是实现IHttpRoute的实例。

4.继续深入在HostedHttpRoute构造时,内部有一个Route类型的OriginalRoute属性,它被赋值为继承自(Route:RouteBase)HttpWebRoute类型。

5.在HttpWebRoute初始构造时传入了几个极为重要的参数路由模板IRouteHandler类型HttpControllerRouteHandler以及一个IHttpRoute类型的HostedHttpRoute,然后程序返回,注意此处的IRouteHandler可以理解为是路由的处理器.最终在HostedHttpRouteCollection对象上调用routes.Add(name, route)添加 路由模板名字处理程序.

4.总结

现在我们对Web Api中的路由注册部分已经做了一个简单的介绍,并且一步一步理解了源码实现的逻辑,其实说到本质,路由注册只做了一件事,就是将路由模板规则和路由处理器提前绑定,客户端按照对应的规则请求来匹配对应的路由处理器做最终的处理,而目前框架内置的路由处理器就是一个HttpControllerRouteHandler,后面部分进入路由处理,如果在阅读过程中有任何疑问欢迎随时和我讨论, 强烈建议在阅读本篇分享建立了粗略的知识点之后,有时间的话自己先下载Web Api源码进行阅读,这样可以帮助更好的理解。


3. Web API 路由处理

上一节我们分享了webAPI中路由注册的过程,知道了WebAPI中添加路由映射的方式,最终映射在路由表的是,路由模板和路由处理器组成一个Route对象,然后将它添加进去,接下来这一节就介绍WebAPI程序如何对路由请求进行处理.

在此我们需要把目光转移到ASP.NET管道模型中去,因为请求处理的核心部件是由ASP.NET管道中的一个叫
UrlRoutingModule 的伙计在负责进行着核心的工作.

是的,它是一个 HttpModule 因为它继承自 IHttpModule,并且实现扩展了 HttpApplication 的PostResolveRequestCache事件,而WebAPI后面的一切都是围绕着这个扩展事件展开,同样MVC & WebForm也是如此.

如果您不知道我标记高亮的类和接口是什么,您可能需要去仔细阅读 ASP.NET 管道模型简析这一篇分享.

我们打开UrlRoutingModule类找到PostResolveRequestCache事件订阅,路由处理这一部分全部都是围绕着以下代码展开.

image.png

处理流程步骤

简单的描述一下:

  1. 当请求到达时根据上下文找到路由数据RouteData.

  2. 在RouteData中找到路由处理器IRouteHandler,而这个IRouteHandler,正是我们上一节路由注册中步骤的第6步操作中提到的HttpControllerRouteHandler.

  3. 将RouteData和IRouteHandler一起组装成请求上下文RequestContext.

  4. 路由处理器中的GetHttpHandler根据传入的请求上下文,找到一个"请求处理器"IHttpHandler.

  5. 其实最终由请求处理器是一个叫HttpControllerHandler的接管后续的操作,由这里开始正式进入了我们的WebAPI处理管道.

到目前为止,假设我认为您已经清楚了上面的几个概念和描述的基本步骤,接下来我们进入到方法的内部,去看一下究竟是如何一步步做到的。

获取路由对象

1.进入到RouteCollection的GetRouteData方法,首先迭代器中获取继承自RouteBase对象的实例.

image.png

2.因为我们在注册路由时已经给定(路由注册流程第5步),毫无疑问类型为RouteBase的current获取到的是一个HttpWebRoute对象,即返回RouteData数据,最终调用的是HttpWebRoute中的重写方法GetRouteData,用类图表示

classDiagram RouteBase <|-- Route Route <|-- HttpWebRoute RouteBase: +GetRouteData()() class Route{ +GetRouteData() } class HttpWebRoute{ +GetRouteData() }

3.由于HttpWebRoute中在注册路由时,它的HttpRoute属性在构造时指定为HostedHttpRoute类型,那么 GetRouteData方法将会调用标记处,Route中的GetRouteData()方法

image.png

4.方法中做法很简单,将当前Route类和注册时传入类型为IRouteHandler的HttpControllerRouteHandler处理器组装成一个RouteData对象返回.

image.png

获取HttpHandler

1.接下来就是从处理器中获取请求处理器HttpHandler,我们在上面不止一次说过,路由处理器是一个IRouteHandler类型的HttpControllerRouteHandler实例,那么它是如何返回"请求处理器"的呢?

image.png

2.我们看到获取请求处理器的代码很简单,在GetHttpHandler中直接创建了一个对象返回,而此刻的HttpControllerHandler的创建,就是标志着我们的http请求将正式被处理,而它的本质仅仅只是一个HttpHandler.

HttpControllerHandler执行

1.进入HttpControllerHandler内部,我们看到它在构造时接收2个参数,一个是routeData,一个是handler参数,方法执行时将routeData转为HostedHttpRouteData对象,再将handler转化为HttpMessageInvoker对象,然后赋值给内部私有变量_server.

image.png

2.看到这个类型为HttpMessageHandler的参数是不是有点眼熟,在路由注册的第3步,我们在GlobalConfiguration类中看到过它.

classDiagram class GlobalConfiguration{ +Configuration:HttpConfiguration +DefaultHandler:HttpMessageHandler +DefaultServer:HttpServer }

3.现在可能还不知道它的作用,继续往下看,一定会慢慢清晰的,我们知道HttpHandler会被框架默认执行ProcessRequest方法,此处是返回一个Task的异步方法ProcessRequestAsync,在它内部调用了ProcessRequestAsyncCore

image.png

4.注意上面标记的 await _server.SendAsync(request, cancellationToken)这行代码,在这里它接收一个请求上下文参数,然后正式开始它的执行.

5.在第1步中看到这个_server是一个GlobalConfiguration.DefaultServer转化后的实例,现在我们需要回到GlobalConfiguration去正式看一下DefaultServer了,因为这一切都与它密不可分.

private static Lazy<HttpServer> _defaultServer = CreateDefaultServer();
public static HttpServer DefaultServer
{
    get { return _defaultServer.Value; }
}

private static Lazy<HttpServer> CreateDefaultServer()
{
    return new Lazy<HttpServer>(() => new 
    HttpServer(_configuration.Value, _defaultHandler.Value));
}

6.看了DefaultServer的由来,它是一个继承自DelegatingHandler类型为 HttpServer 的属性,它其实就是我们WebAPI管道的头部,而 HttpServer 中的SendAsync方法就是我们所有请求处理的第一站,此时同样标志着Http请求正式被WebAPI管道接管并开始处理.

4. Web API 管道组装

我们在学习Web API 管道组装之前,需要先认识一些基本的类概念和它们之间的关联,先看下面类图大概扫一眼混个眼熟,因为api管道就是下面几个组成.

classDiagram HttpMessageHandler <|-- DelegatingHandler HttpMessageHandler <|-- HttpRoutingDispatcher DelegatingHandler <|-- HttpServer HttpRoutingDispatcher <|-- HttpControllerDispatcher HttpMessageHandler : # SendAsync() class DelegatingHandler{ - innerHandler:HttpMessageHandler + innerHandler:HttpMessageHandler # SendAsync() } class HttpRoutingDispatcher{ # SendAsync() } class HttpServer{ Initialize() SendAsync() } class HttpControllerDispatcher{ # SendAsync() }

当然可能还需要一些设计模式的功底,因为这会使你更加容易理解它的设计思想,在一开始介绍中我说过我担心把自己讲懵,因为我在学习它的时候花了相当长一段时间去看、去理解直到现在我只能说尝试着去分享.

除了上面类图中的我们还需要认识2个类型:

  1. HttpRequestMessage 代表http消息在API请求传递的实例.

  2. HttpResponseMessage 代表http消息在API中被处理后返回传递的实例.

HttpMessageHandler

要认识API管道就必须先了解 HttpMessageHandlerDelegatingHandler 这2个抽象类,我们先介绍HttpMessageHandler,在msdn中官方文档的定义是 "HTTP 消息处理程序的基类",个人觉得可以仅仅把它当做一个约束吧,跟接口一样,只不过比接口和类的关系更加紧密.

image.png

1.我们在在类中标记了,它有一个在命名空间内受保护的异步抽象方法,接收HttpRequestMessage,返回一个Task <HttpResponseMessage>,在这我们相当于定义了要被api处理消息和返回消息的约束,后面所有派生的类必须遵从这个约定.

DelegatingHandler

DelegatingHandler在msdn文档中的定义是将 HTTP 响应消息的处理委托给另一处理程序(称为“内部处理程序”)的 HTTP 处理程序的类型

从字面上理解就是自己不对消息做任何处理,把处理的动作交给别人,那它是如何将处理动作交给别人的呢?

image.png

我们先看一下里面的代码有如下3个特点:

  1. 首先它继承自HttpMessageHandler,实现了SendAsync方法.

  2. 它有一个类型是HttpMessageHandler的公共InnerHandler属性.

  3. 我们看SendAsync方法内部只是调用了InnerHandler属性的SendAsync方法.

上面我们说过HttpMessageHandler是一个"消息处理基类",既然接收基类,那InnerHandler属性可以代表任何继承自HttpMessageHandler的实例.

即说明此处的this.InnerHandler.SendAsync()代表的是HttpMessageHandler或者DelegatingHandler的子类实例中的SendAsync

到这里我只是想尽可能的引导说明它是如何将此处的消息处理委托给别人的,而这正是DelegatingHandler的核心作用,至于它究竟是如何实现委托的呢?

在此处实现思想可以理解为 装饰器模式 其核心做法为 一个对象包含指向另一个对象的引用,并将部分工作委派给引用对象,继承中的对象则继承了父类的行为,它们自己能够完成这些工作.

HttpServer

1.在HttpControllerHandler执行部分中,我们接触过HttpServer并且了解到它继承自DelegatingHandler,知道它的SendAsync方法是被作为HttpControllerHandler(httphandler)执行的入口.

image.png

2.可以看到HttpServer构造时内部默认创建了一个HttpRoutingDispatcher类并把它赋值给内部类型为HttpMessageHandler的只读属性_dispatcher
目前我们只需要记住这2步操作就好了,对于HttpRoutingDispatcher内部的一些事情我们下一节会介绍它.

3.在DelegatingHandler中我们知道了它是如何将消息处理委派给别人,如下图我简单绘制了整个API消息管道的类关系.

image.png

4.现在我们要思考的是框架如何将这些步骤串联起来,使它有条不紊的执行的呢?现在将目光转到HttpServer中的SendAsync方法,在这里面能找到我们需要的答案.

image.png

5.他有一个EnsureInitialized方法,注释告诉我们了它是 请求初始化服务的,如何初始化呢?我们转到里面

image.png

6.在这里利用HttpClientFactory.CreatePipeline()方法传入了一个HttpRoutingDispatcher的实例和一个Collection<DelegatingHandler>集合,并返回一个HttpMessageHandler给InnerHandler.

这里需要解释并且记录其实就是利用工厂创建一个消息处理实例,然后将父类DelegatingHandler的InnerHandle属性设置为HttpRoutingDispatcher的实例.

7.继续回到SendAsync方法,我们看到它调用了base.SendAsync()也就是调用父类的SendAsync方法,内部竟然是调用innerHandler,也就是说它本身没有处理消息,而是把处理的动作委派给HttpRoutingDispatcher来实现.

image.png

HttpRoutingDispatcher 和 HttpControllerDispatcher

上一节我们知道在HttpServer中开始执行消息处理,是由其父类DelegatingHandler将处理动作委派给HttpRoutingDispatcher类的实例方法SendAsync来实现最终调用的,我们现在来看看在HttpRoutingDispatcher中究竟有什么.

5. Web API 管道扩展

如何扩展webAPI管道

1.首先继承自DelegatingHandler

2.重写父类方法

image.png

3.在HttpConfiguration类的MessageHandlers注册

image.png

按照上面3步操作就可以完成我们自定义对API管道的扩展.

我们下一部分将解释它是如何将我们自定义的管道添加到内置管道的.

注册原理

1.转到管道工厂方法内部,我们看到首先将HttpRoutingDispatcher赋值到局部变量pipeline

image.png

2.标记"扩展集合" 参数存储的是自定义管道的集合,内部在注册时首先将集合反转,也就是原来 1 2 3顺序的自定义管道变为3 2 1的顺序.

3.然后开始迭代,将HttpRoutingDispatcher的引用传递给顺序为3的自定义管道.

4.将引用赋值完成后,在将顺序为3的自定义管道赋值给局部变量pipleline,最后返回,以此类推形成了一个委托链,在调用时呈链式调用依次执行.

posted on 2021-09-03 21:43  yuyuyui  阅读(483)  评论(0)    收藏  举报