Proxygen http2 代码分析

Proxygen 的整体架构
image

一个 HTTPSession 对应一个 tcp 连接。

HTTPSession 中包含HTTPCodec ,HTTPCodec用来在 HTTPMessage(Request/Response) 和 字节流之间做转换(就是解析/序列化)。

一个 HTTPTransaction 对应一个 HTTP2 的Stream ,也就是一次 Req/Resp
Handler 是业务逻辑处理的基类。

其中 HTTP2 部分由几个类构成:

  1. codec/HTTPCodec.h
  2. codec/HTTP2Framer.cpp
  3. codec/HTTP2Codec.cpp , HTTPParallelCodec.cpp
  4. codec/Compress/HeaderCodec.cpp,HPACKCodec.cpp
  5. session/HTTPSession.cpp
  6. session/HTTPTransacion.cpp
  7. session/HTTP2PriorityQueue.cpp

1. HTTPCodec.h

定义了 HTTPCodec 这个基类,定义了用来在 内部的 HTTP Request/Response 和 字节流之间做转换的公共接口,是HTTP1.1/SPDY/HTTP2 的公共基类。具体的HTTP2/HTTP1.1等各种协议的编解码,实现在 HTTP1.1/SPDY/HTTP2 等子类中。

1) HTTPCodec

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

2) PriorityQueue,优先级队列。

Session/HTTP2PriorityQueue.cpp 实现了这个接口类

3) HTTPCodec::Callback 类

解析的过程中,需要通知 HTTPCodec 的使用者做一些操作,因此有个 接口类 HTTPCodec::Callback,HTTPCodec 的使用者实现 Callback 的子类,传给 HTTPCodec 的子类,HTTPCodec 的子类在解析过程中解析出来的各种消息都回调这个callback。
Callback 的主要方法有:

  • onMessageBegin()
  • onHeadersComplete() // HeaderList 接收完
  • onBody() // Body 开始
  • onMessageComplete() //Message 整个接收完
  • onWindowUpdate() // 收到 WindowUpdate Frame
  • onPriority(), //收到 Priority Frame等

顾名思义,处理HTTPMessage 开始事件,收到各种 HTTP2 Frame 等等。

2. HTTP2Framer.cpp

10种Frame 的 解析/序列化 工具函数,parseXXXFrame, writeXXXFrame,最底层。供HTTP2Codec 使用。

3. HTTP2Codec

Framing Layer,即处理 HTTPMesage , HTTP2 Frame 和 输入/输出字节流之间的 解析/序列化。实现了HTTPParallelCodec(HTTP2和SPDY的公共基类,HTTPCodec 的子类),HeaderCodec两个接口类

1) 输入字节流的解析的时候,做:

i. HEADERS Frame,

做各种 Header 的变换处理。

ii. PRIORITY

4. compress/HeaderCodec.cpp

HeaderCodec 是 Header List 的编解码器。HeaderCodec解码的结果通过 HeaderCodec::StreamingCallback接口类通知给使用者(即HTTP2Codec)。HeaderCodec::StreamingCallback主要有个 onHeader(name, value) 接口(HTTP2Codec实现了)。

5. compress/HPACKCodec.cpp

HPACK保存 int --> string 的映射,通过发送 int, 代替 string 来压缩。

HPACK编码,主要需要实现动态表,静态表,Huffman encoding,Header序列化/解析。

HPACKCodec 主要有 encode(vector

)/decode(Buffer) 两个接口。重要成员变量:

  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 的映射也不用改。这是一种实现上的小技巧。

并用 一个

unordered_map<Header.name , list<index> > 

的形式存储 Header.name 到一组 vector 中的 index 的映射,这是由于 有 相同 name ,对应多个 value 的情况,而且要支持 “查找 name+value 匹配” 和 ”查找 name 匹配“ 两种查找。

6. session/HTTP2PriorityQueue.cpp

每一个 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内部的数据成员:


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。

7. session/HTTPTransaction.cpp

HTTPTransaction 表示一次 Req/Resp,HTTPTransaction 需要和 HTTPSession 交互写入 HTTPMessage 所以提出了Transport 这个概念,这样依赖关系就是单向的,没有 HTTPTransaction – HTTPSession 之间不会产生双向依赖。实际处理业务的代码,定义成一个个 Handler,需要与Handler 交互,

1) HTTPTransaction::Transport

是 HTTPTransaction 的下层为 HTTPTransaction 提供的输出HTTPMessage 服务。(下层具体指的是 HTTPSession)。

Transport 的方法有:sendHeaders()/sendBody()/pauseIngress()/resumeIngress()/ sendPriority() / 等

2) HTTPTransactionHandler

是一个个业务Handler 的基类,Handler的方法有:onHeadersComplete()/onBody()/onChunk/onTrailers/onEgressPaused()/ onEgressResumed等,就是解析出 HTTP Message的各个部分,或者流控发现无法再写了时候的通知。

i. HTTPTransaction::Handler 有好几种,业务Handler 比如直接生成 ErrorPage 的 HTTPDirectResponseHandler就是生成 404 页面的 Handler,在其内部,实现 onHeadersComplete 方法,在这个方法内生成 回包数据,然后调用 HTTPTransaction 的 sendHeaders/sendBody 等方法发回给客户端。

3) HTTPTransactionIngressSM

由于 HTTPTransaction 要调用 Handler 的好几种方法,内部要记录当前已经处理到哪一步了,不能允许在任意时机,任意来的数据都触发回调,所以搞了两个状态机,HTTPTransactionIngressSM/ HTTPTransactionEgressSM 来明确地规定 回调可以触发的顺序。

4) HTTPTransaction 的重要成员变量:

streamId, handler_ , transport_ ,egressState_ , ingressState_, recvWindow_ , sendWindow_, HTTP2PrioprityQueue::Handle queueHande_,

8. session/HTTPSession.cpp

HTTPSession 针对 HTTP2/HTTP1.1/SPDY的超集的,HTTPSession是一个 HTTPCodec::Callback(接受解析出来的各种 Frame),是 HTTPTransaction::Callback(供HTTPTransaction发送输出 HTTP Message),是 FlowControlFilter::Callback(接受输出状态打开/关闭的通知)。

1) 重要成员变量:

{
    folly::IOBufQueue readBuf_,
    folly::IOBufQueue writeBuf_,
    map<StreamID, HTTPTransaction>transactions_,
    HTTP2PriorityQueue queue,
    FlowControlFilter connFlowControl_,
}			
			

2) getNextToSend()

是做发送的,可以看到,proxygen 是按相对权重来分派带宽的。txnEgressQueue_.nextEgress() 取出当前可发送的 HTTPTransaction + 相对权重的列表,然后把可发送字节数 按照相对权重分给各个HTTPTransaction。

9. 细节

1) IOBufQueue

多处用到的基础类:IOBufQueue,Zero-Copy,引用计数。一个 Bufffer Chain,设计类似 linux kernel 的sk_buff, BSD 的 mbuf 。

https://github.com/facebook/folly/blob/master/folly/io/IOBuf.h


 * 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.

2) HTTPHeaders

HTTPHeaders 有个性能优化,用了 静态完美hash函数,把常用的 83个 Header 各自唯一地hash 成1 字节,HeaderList 的查找使用 汇编实现的memchr。

https://github.com/facebook/proxygen/blob/master/proxygen/lib/http/HTTPHeaders.h


* 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) ;)
 

posted on 2020-04-30 18:38  windydays  阅读(891)  评论(0编辑  收藏  举报

导航