Proxygen 的整体架构
一个 HTTPSession 对应一个 tcp 连接。
HTTPSession 中包含HTTPCodec ,HTTPCodec用来在 HTTPMessage(Request/Response) 和 字节流之间做转换(就是解析/序列化)。
一个 HTTPTransaction 对应一个 HTTP2 的Stream ,也就是一次 Req/Resp Handler 是业务逻辑处理的基类。
其中 HTTP2 部分由几个类构成:
定义了 HTTPCodec 这个基类,定义了用来在 内部的 HTTP Request/Response 和 字节流之间做转换的公共接口,是HTTP1.1/SPDY/HTTP2 的公共基类。具体的HTTP2/HTTP1.1等各种协议的编解码,实现在 HTTP1.1/SPDY/HTTP2 等子类中。
HTTPCodec 是 HTTP1.1/ HTTP2 接口的超集,即HTTP1.1也被当成 HTTP2 实现
i. 首先所有的 子类都要支持 StreamID,HTTP1.1 由于有KeepAlive,看成有多个Stream。 ii. onIngress(),传入输入数据,驱动解析。 iii. generateHeader()/ generateBody() /generateChunkHeader() /generateGoaway()/ generatePingRequest()/ generatePriority() 等,是 http1.1/http2 的超集。HTTP1xCodec.cpp 是没有实现HTTP2 的这些Frame 的generateXXX函数。HTTP1.1
Session/HTTP2PriorityQueue.cpp 实现了这个接口类
解析的过程中,需要通知 HTTPCodec 的使用者做一些操作,因此有个 接口类 HTTPCodec::Callback,HTTPCodec 的使用者实现 Callback 的子类,传给 HTTPCodec 的子类,HTTPCodec 的子类在解析过程中解析出来的各种消息都回调这个callback。 Callback 的主要方法有:
顾名思义,处理HTTPMessage 开始事件,收到各种 HTTP2 Frame 等等。
10种Frame 的 解析/序列化 工具函数,parseXXXFrame, writeXXXFrame,最底层。供HTTP2Codec 使用。
Framing Layer,即处理 HTTPMesage , HTTP2 Frame 和 输入/输出字节流之间的 解析/序列化。实现了HTTPParallelCodec(HTTP2和SPDY的公共基类,HTTPCodec 的子类),HeaderCodec两个接口类
做各种 Header 的变换处理。
HeaderCodec 是 Header List 的编解码器。HeaderCodec解码的结果通过 HeaderCodec::StreamingCallback接口类通知给使用者(即HTTP2Codec)。HeaderCodec::StreamingCallback主要有个 onHeader(name, value) 接口(HTTP2Codec实现了)。
HPACK保存 int –> string 的映射,通过发送 int, 代替 string 来压缩。
HPACK编码,主要需要实现动态表,静态表,Huffman encoding,Header序列化/解析。
HPACKCodec 主要有 encode(vector)/decode(Buffer) 两个接口。重要成员变量:
1 2 | HPACKEncoder encoder_; HPACKDecoder decoder_; |
---|
实际的 动态表/静态表 逻辑,是一个HeaderTable,主要数据结构就是vector + ,
HPACKDecoder 里面的动态表对应的是 HPACKContext,先 peek 一个字节,判断是否用了HPACKDecodeBuffer来解码
Integer比特编解码,literal编解码,都实现在 HPACKDecodeBuffer / HPACKEncodeBuffer里面。
Huffman encoding 实际使用的是 256位的trie,不是2位的,为了优化性能。
在 HeaderTable.cpp 中,可以看到,实际的 vector 是当成一个 环形队列来用的, 实际是 把 RFC 中规定的 [ 1 — n ] 的区间,逆向映射到 这个环形队列中,即 index 1 是,对应到 环形队列的 尾,index n 是对应到环形队列的头。 这样,按照 RFC,增加 Header 进来的时候,应该是加在 1 前面,并把所有的已有 Header 往后挪1个位置,实现上就可以不用做 “挪动”,而且已有的 name–>index 的映射也不用改。这是一种实现上的小技巧。
并用 一个
1 | unordered_map<Header.name , list<index> > |
---|
的形式存储 Header.name 到一组 vector 中的 index 的映射,这是由于 有 相同 name ,对应多个 value 的情况,而且要支持 “查找 name+value 匹配” 和 ”查找 name 匹配“ 两种查找。
每一个 Stream都 依赖一个 Stream 或者 Stream 0。 一个 Stream A 依赖 Stream B ,那么 A 就是 B 的子节点。形成依赖树。 Stream 0 是树的根节点。
HTTP2PriorityQueue实现了PriorityQueue接口类,实现了 addPriorityNode(StreamID id, StreamID parent)
接口
主要的逻辑实现在 Node这个内部类里面。
PriorityQueue 的数据成员,主要是一个 unorder_map<streamID, Node>
Node内部的数据成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Node { HTTP2PriorityQueue& queue_; //属于哪个 PriorityQueue Node *parent_{nullptr}; //树中的父节点 HTTPCodec::StreamID id_{0}; // uint16_t weight_{16}; //权重 HTTPTransaction *txn_{nullptr}; bool isPermanent_{false}; bool enqueued_{false}; //enqueued 表示在 有输出数据的队列中 uint64_t totalEnqueuedWeight_{0}; uint64_t totalChildWeight_{0}; std::list<std::unique_ptr<Node>> children_; // 子节点 std::list<std::unique_ptr<Node>>::iterator self_; folly::IntrusiveListHook enqueuedHook_; //一个侵入式链表 folly::IntrusiveList<Node, &Node::enqueuedHook_> enqueuedChildren_; // } |
---|
有输出数据等待发送 (pending Egress) 的 HTTPTransaction ,会加入
signalPendingEgress() , 通知 HTTP2PriorityQueue 某一个 Stream 产生了输出数据。 内部实现是把 这 Stream加入
clearPendingEgress(), 通知一个 Stream 发完了 输出数据。
addOrUpdatePriorityNode(),
nextEgress() , 获取有输出数据等待发送的 HTTPTransaction 的列表,列表的每个元素是 pair< HTTPTransaction * ,double weight > ,按照 weight 从大到小排列,列表中的所有weight 加起来等于1。
HTTPTransaction 表示一次 Req/Resp,HTTPTransaction 需要和 HTTPSession 交互写入 HTTPMessage 所以提出了Transport 这个概念,这样依赖关系就是单向的,没有 HTTPTransaction – HTTPSession 之间不会产生双向依赖。实际处理业务的代码,定义成一个个 Handler,需要与Handler 交互,
是 HTTPTransaction 的下层为 HTTPTransaction 提供的输出HTTPMessage 服务。(下层具体指的是 HTTPSession)。
Transport 的方法有:sendHeaders()/sendBody()/pauseIngress()/resumeIngress()/ sendPriority() / 等
是一个个业务Handler 的基类,Handler的方法有:onHeadersComplete()/onBody()/onChunk/onTrailers/onEgressPaused()/ onEgressResumed等,就是解析出 HTTP Message的各个部分,或者流控发现无法再写了时候的通知。
i. HTTPTransaction::Handler 有好几种,业务Handler 比如直接生成 ErrorPage 的 HTTPDirectResponseHandler就是生成 404 页面的 Handler,在其内部,实现 onHeadersComplete 方法,在这个方法内生成 回包数据,然后调用 HTTPTransaction 的 sendHeaders/sendBody 等方法发回给客户端。
由于 HTTPTransaction 要调用 Handler 的好几种方法,内部要记录当前已经处理到哪一步了,不能允许在任意时机,任意来的数据都触发回调,所以搞了两个状态机,HTTPTransactionIngressSM/ HTTPTransactionEgressSM 来明确地规定 回调可以触发的顺序。
streamId, handler_ , transport_ ,egressState_ , ingressState_, recvWindow_ , sendWindow_, HTTP2PrioprityQueue::Handle queueHande_,
HTTPSession 针对 HTTP2/HTTP1.1/SPDY的超集的,HTTPSession是一个 HTTPCodec::Callback(接受解析出来的各种 Frame),是 HTTPTransaction::Callback(供HTTPTransaction发送输出 HTTP Message),是 FlowControlFilter::Callback(接受输出状态打开/关闭的通知)。
1 2 3 4 5 6 7 8 | { folly::IOBufQueue readBuf_, folly::IOBufQueue writeBuf_, map<StreamID, HTTPTransaction>transactions_, HTTP2PriorityQueue queue, FlowControlFilter connFlowControl_, } |
---|
是做发送的,可以看到,proxygen 是按相对权重来分派带宽的。txnEgressQueue_.nextEgress() 取出当前可发送的 HTTPTransaction + 相对权重的列表,然后把可发送字节数 按照相对权重分给各个HTTPTransaction。
多处用到的基础类:IOBufQueue,Zero-Copy,引用计数。一个 Bufffer Chain,设计类似 linux kernel 的sk_buff, BSD 的 mbuf 。
https://github.com/facebook/folly/blob/master/folly/io/IOBuf.h
1 2 3 4 5 6 7 8 | * An IOBuf is a pointer to a buffer of data. * * IOBuf objects are intended to be used primarily for networking code, and are * modelled somewhat after FreeBSD's mbuf data structure, and Linux's sk_buff structure. * IOBuf objects facilitate zero-copy network programming, by allowing multiple * IOBuf objects to point to the same underlying buffer of data, using a * reference count to track when the buffer is no longer needed and can be freed. |
---|
HTTPHeaders 有个性能优化,用了 静态完美hash函数,把常用的 83个 Header 各自唯一地hash 成1 字节,HeaderList 的查找使用 汇编实现的memchr。
https://github.com/facebook/proxygen/blob/master/proxygen/lib/http/HTTPHeaders.h
1 2 3 4 5 6 7 8 | * Headers are stored as Name/Value pairs, in the order they are received on * the wire. We hash the names of all common HTTP headers (using a static * perfect hash function generated using gperf from HTTPCommonHeaders.gperf) * into 1-byte hashes (we call them "codes") and only store these. We search * them using memchr, which has an x86_64 assembly implementation with * complexity O(n/16) ;) |
---|