为什么有这些要求呢?因为我懒,所以需要这个类库十分灵活简便易用,最好只要告诉它我有什么然后要什么,它就能给我弄回来。
然而12306的种种掉链子谁都知道,所以我也希望这个库足够强健,至少不会因为库本身的因素导致程序崩掉。
所以这个库的存在是十分重要的。然而幸运的是,这个库完善到现在后,基本上符合我的要求。
第一节,概念和流程概述
FSLIB.NETWORK(以下简称网络库)从设计的一开始,就借鉴了 .NET4.5+ 中提供的 HttpClient
的设计结构,并进行了抽象和流程化,为的是简便易用。
从整个流程看,网络库将整个 HTTP 请求中涉及到的核心对象分为以下几类:
类型 | 网络库内部实现 | 功能描述 |
---|---|---|
HTTP会话工厂 | HttpClient | HTTP网络库核心类,用于创建会话并维护全局设置 |
HTTP会话 | HttpContext, HttpContext<T> | HTTP单个请求的封装,每个对象完成一次具体的请求,由HTTP会话工厂创建 |
HTTP请求信息 | HttpRequestMessage | 维护了所有的请求信息,如请求头信息、正文信息、相关设置等等 |
HTTP请求数据 | HttpRequestContent 以及它的派生类 | 如果发送的请求带有正文,则正文为此类型数据。此类型的实例作为HTTP请求正文放在 HttpRequestMessage 中 |
HTTP响应信息 | HttpResponseMessage | 维护了所有的响应信息,如响应头信息、响应数据等等 |
HTTP响应数据 | HttpResponseContent 以及它的派生类 | 包含了服务器返回的最终结果 |
HTTP请求工厂 | IHttpHandler, HttpHandler | 用于创建需要的各个细节对象,如 HttpWebRequest。此类被HttpClient对象使用 |
HTTP设置 | HttpSettings | 维护了全局的设置 |
HTTP请求是流程化的,其基本运行流程如下图所示。
具体描述如下。
- 客户端要求发送请求,创建客户端
HttpClient
对象实例,并设置相关设置 - 调用
HttpClient
对象的Create
方法(或相关的简便写法),传递必须的参数(方法类型、URL、引用页、地址、头信息以及一部分常用选项设置等)以及期望的响应类型(如String
、byte[]
等),HttpContext
对象将会调用HttpHandler
依据相关的信息进行初始化并创建需要的HttpContext
。在此过程中,HttpClient
会将传递的参数进行包装。此时,HttpContext
尚未发送 - 根据需要,对
HttpContext
进行设置(可选) - 调用
HttpContext
对象的Send
方法(同步)或SendAsync
(异步)方法发送请求 HttpContext
对象调用HttpHandler
新建HttpWebRequest
对象,并进行初始化HttpContext
开始请求,连接服务器并等待请求流- 写入请求头。如果不出错,则判断是否有请求数据,如果没有,则跳到第 9 步骤
- 向流写入请求数据
- 等待响应头。获得响应头后,判断是否正确,并更新Cookies。如果此时发生
ProtocolException
(常见于?40x/50x?错误),则会进行二次处理以便于获得响应内容 - 校验响应头是否正常。如果正常,则对响应流进行包装(包含但不局限于镜像、解压缩等)
- 将响应流交由
HttpResponseMessage
进行处理,它负责将流数据读取并处理完成 - 校验响应数据是否正确,并记录错误
- 请求完成
在整个流程中,只有第一步到第四步是每次都需要完成的操作,其它的操作一般来说,并不需要调用方过多关心。
第二节,发送数据处理模型
对于HTTP应用来说,最常见的莫过于发送和处理响应数据。本网络库为了简化数据发送流程,对发送数据内容进行了抽象化,将所有的要发送的数据统一为HttpRequestContent并由其完成发送的写入过程。
在调用HttpClient
创建HttpContext
的过程中,HttpClient
会依据一定的规则将传入的数据进行包装以便于发送。最终请求发送的数据格式由两个参数决定:ContentType和实际的数据 data。其中,ContentType 决定了由哪个数据承载类对data进行处理并发送。
HttpClient
将原始数据(参数传入的数据)转换为HttpRequestContent
的流程,可以用下图表示。
在大部分情况下,HttpClient内建的自动处理机制已经可以处理绝大部分数据类型。其默认的处理机制如下表格所示(按列表顺序处理,data为传入的数据,ContentType为指定的类型)。
情况 | 返回类型 | 备注 |
---|---|---|
data 类型可转换为 HttpRequestContent |
data 直接强制类型转换为 HttpRequestContent 后返回 |
|
data 类型为 string |
包装为 RequestStringContent |
原样发送 |
data 类型为 Stream |
包装为 RequestCopyStreamContent |
直接复制流并直接发送 |
data 类型为 字节数组 byte[] |
包装为 RequestByteBufferContent |
直接写入数组 |
data 为字典类型(IDictionary<string, string> )且 ContentType 不为 ContenType.Json |
包装为 RequestFormDataContent |
生成表单数据 |
ContentType 为 ContentType.Json |
包装为 RequestJsonContent |
使用 Json.Net 库对数据进行序列化 |
ContentType 为 ContentType.Xml |
包装为 RequestXmlContent |
使用XML序列化对数据进行序列化 |
(默认) | 包装为 RequestObjectContent |
内部使用反射机制对data进行处理 |
值得一提的是 RequestObjectContent
,它继承自 RequestFormData
,提供了一个快速方便的方式将各种可能的数据类型格式化为表单,并支持文件上传。这个类型内部使用反射机制,对传入的数据类型进行二次绑定。
在RequestObjectContent
中,其将 data
作为顶级对象进行绑定,存在一个递归过程,处理逻辑如下:
在 RequestObjectContent
处理完成后,由底层的 RequestFormContent
负责将实际的数据写入请求流中。以上绑定的时机位于HttpContext
的发送过程中。
第三节,接收数据处理模型
在创建HttpContext
的时候,就已经要求指定返回结果的类型,在Create
方法中,HttpClient
对象会依据期望的数据类型结果来创建HttpContext
的泛型类型。在这之后,通过HttpContext
的Result
属性即可快速拿到相关结果。
HttpContext
会依据实际返回的结果动态调整响应类型。此时,如果继续使用Result属性取结果,将有可能导致抛出异常(类型不匹配)。因此,当请求不成功(IsValid()
返回false
)时,应该使用 HttpContext
的 ResponseContent
获得响应结果,并需要根据情况判断其类型。网络库内置了如下类型结果的支持:
byte[]
:直接获得原始的响应字节数组Stream
:获得原始的响应流并进行复制。此时,响应结果为ResponseCopyStreamContent
,它的Stream
属性表示了镜像流。此镜像流可以通过Create
时的targetStream
参数传入,或使用自动生成的MemoryStream
。所有的服务器响应将会以同步或异步的模式写入这个流中,并在响应关闭后可以继续使用EventHandler<ResponseStreamContent.RequireProcessStreamEventArgs>
:一个特殊的类型,以事件模式处理响应。此时,响应结果为ResponseStreamContent
,它的RequireProcessStream
事件表示已经读取到数据并等待处理。此事件可以通过Create
时的streamInvoker
参数传入,或在发送之前手动绑定XmlDocument
:返回一个XML文档结果Image
:返回一个图像结果- 其它的非
object
类型:返回ResponseObjectContent
,此类型自动判断响应,并尝试通过JSON或XML反序列化将响应反序列化为对象 - 其它情况:返回
ResponseBinaryContent
,此类型和byte[]
类型基本一致
通过额外的HtmlAgility扩展包可提供对 HtmlDocument
的返回支持。
图挂了
好漂亮的流程图,想问一下这是用啥画的~
wordpress里的一个插件,是个在线画图保存SVG的工具。Chrome也有类似的应用。
等这个手册等的真不容易。。。。。
大赞啊,这个图得耗费你不少心思吧