自從參加了 Flomesh 的 workshop媚朦,了解了可編程網(wǎng)關(guān) Pipy氧敢。對這個“小東西”充滿了好奇,前后寫了兩篇文章询张,看了部分源碼解開了其部分面紗孙乖。但始終未見其全貌,沒有觸及其核心設(shè)計瑞侮。
不是有句話的圆,“好奇害死貓”。其實應(yīng)該還有后半句半火,“滿足了就沒事”(見維基百科)越妈。
所有就有了今天的這一篇,對前兩篇感興趣的可以跳轉(zhuǎn)翻看钮糖。
言歸正傳梅掠。
事件模型
上篇寫了 Pipy 基于事件的信息流轉(zhuǎn)酌住,其實還未深入觸及其核心的事件模型。既然是事件模型阎抒,先看事件酪我。
src/event.hpp:41
中定義了 Pipy 的四種事件:
Data
MessageStart
MessageEnd
SessionEnd
翻看源碼可知(必須吐槽文檔太少)這幾種事件其實是有順序的:MessageStart
-> Data
-> MessageEnd
-> SessionEnd
。
這種面向事件模型且叁,必然有生產(chǎn)者和消費者都哭。又是翻看源碼可知,生產(chǎn)者和消費者都是 pipy::Filter
逞带。我們在上篇文章中講過:每個 Pipeline
都有一個過濾器鏈欺矫,類似單向鏈表的數(shù)據(jù)結(jié)構(gòu)。
那是不是按照上面說的展氓,事件是從一個 Filter
流向下一個 Filter
穆趴?也對,也不對遇汞。
矛盾未妹?
先看 Filter
如何向下傳遞事件,src/session.cpp:55
處空入,Filter
持有 output
變量络它,類似為 Event::Receiver
(參數(shù)為 Event
的 std::function
的別名,作為外行的筆者并不懂 c++执庐,但不妨礙了解程序設(shè)計)酪耕。通過 Receiver
調(diào)用下一個 Filter
的 #process
方法。
這里的 Receiver
就可以理解為事件發(fā)送的窗口轨淌,而 #process(Context *ctx, Event *inp)
就是事件的接收窗口迂烁。
這就是前面為什么說 “事件是從一個 Filter
流向下一個 Filter
” 是正確的。
為什么不對递鹉?首先盟步,一個 Filter
會產(chǎn)生多個事件,比如 decodeHttpRequest
可能會產(chǎn)生 MessageStart
躏结、Data
和 MessageEnd
事件却盘,并且每產(chǎn)生一個事件都會通過Receiver
向下傳遞,不會等 #process
流程結(jié)束才傳遞事件媳拴;再就是下一個 Filter
可能并不會對某個事件感興趣(下一個 Filter
的 #process
方法不做任何處理就返回了)黄橘。
可能看下圖會更容易理解(圖中 no output
表明事件不會向下傳遞):
最簡單的示例
在 test/001-echo/pipy.js
提供了的示例:
pipy()
.listen(6080)
.decodeHttpRequest()
.encodeHttpResponse()
發(fā)起請求
$ curl -X POST localhost:6080 -d '{}'
{}
HTTP 消息體
#request
POST / HTTP/1.1
Content-Type: application/javascript
User-Agent: PostmanRuntime/7.28.1
Accept: */*
Postman-Token: fc84b575-7fea-487b-a55d-f6085bc62cf7
Host: localhost:6080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 2
{}
#response
HTTP/1.1 200 OK
postman-token: fc84b575-7fea-487b-a55d-f6085bc62cf7
accept-encoding: gzip, deflate, br
host: localhost:6080
accept: */*
user-agent: PostmanRuntime/7.28.1
content-type: application/javascript
Connection: keep-alive
Content-Length: 2
{}
這里我們以過濾器 decodeHttpRequest
為例,官方的說明是 Deframes an HTTP request message
屈溉。前面提到它會產(chǎn)生 3 個事件塞关,都是在 deframe
的過程中發(fā)出的。
Session
調(diào)用第一個 Filter
時子巾,傳入的事件類型是 event::Data
帆赢。decodeHttpRequest
關(guān)注該事件小压,并按照 HTTP 協(xié)議開始解析。
在上圖可以看到解析的不同階段椰于,會發(fā)出不同的事件怠益。調(diào)用 Receiver
傳輸事件,調(diào)用 encodeHttpResponse
的 #process()
方法瘾婿。
這里又會好奇蜻牢,假如上面的示例中去掉兩個過濾器中的任何一個,或者都去掉偏陪,能不能正常工作孩饼?
答案是都不能!響應(yīng)狀態(tài)碼都是 502 Bad Gateway
(curl/httpie)竹挡。
分析
這里需要結(jié)合本文的第一張圖 event-handling-flow。
去掉兩個過濾器
假如兩個都去掉了立膛,HTTP Request 請求消息會被直接回傳給客戶端揪罕,協(xié)議錯誤。
去掉 decodeHttpRequest
前面提到 Session
傳給第一個 Filter
的事件是 event::Data
宝泵。而 encodeHttpResponse
針對該事件只會將其保存到 buffer 中好啰。
然后整個鏈路在此結(jié)束,沒有回傳任何數(shù)據(jù)儿奶】蛲客戶端會等待響應(yīng),超時退出(curl)闯捎。
去掉 encodeHttpResponse
先說結(jié)果椰弊,與前面一樣超時退出。
為什么會這樣瓤鼻,明明 decodeHttpRequest
產(chǎn)生了 3 個時間秉版,Session
里的 Receiver
也有收到,也確實寫回了請求 body 里的 {}
茬祷。
encodeHttpResponse
過濾器有寫回響應(yīng)頭清焕,缺少了這些信息,響應(yīng)就不并不是合法的 HTTP 協(xié)議祭犯,只是普通的 TCP 協(xié)議秸妥。
總結(jié)
Pipy 基于事件模型的設(shè)計,提供了強大的靈活性沃粗。允許我們在“規(guī)則”中使用不同過濾器針對不同的事件粥惧,對請求和響應(yīng)的信息進行處理。
“規(guī)則” 就是業(yè)務(wù)邏輯的核心陪每,而 Pipy 就是這邏輯的執(zhí)行引擎影晓。
最后镰吵,“好奇心是成長的驅(qū)動力,永遠保持好奇心挂签“碳溃”