为什么有这些要求呢?因为我懒,所以需要这个类库十分灵活简便易用,最好只要告诉它我有什么然后要什么,它就能给我弄回来。
然而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也有类似的应用。
等这个手册等的真不容易。。。。。
大赞啊,这个图得耗费你不少心思吧