1

Proxygen http2 代码分析

 2 years ago
source link: https://byronhe.com/post/Proxygen-http2-code-analysis/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Proxygen http2 代码分析

Proxygen 的整体架构

一个 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。

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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK