Jetty源碼閱讀——用狀態(tài)隔離變化

需求

如果讓你寫一段程序,解析http協(xié)議的請求報文倍奢,你會怎么寫彪见?
在實現(xiàn)這個需求之前,我們先了解一下http協(xié)議格式娱挨。http協(xié)議有很多種規(guī)范,rfc2616捕犬、rfc7230等等跷坝,這里我們以rfc7230為例,拿一個具體的例子分析:

GET /hello HTTP/1.1
Host: localhost
Transfer-Encoding: chunked

7\r\n
Mozilla\r\n 
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n 
\r\n
   All HTTP/1.1 messages consist of a start-line followed by a sequence
   of octets in a format similar to the Internet Message Format
   [RFC5322]: zero or more header fields (collectively referred to as
   the "headers" or the "header section"), an empty line indicating the
   end of the header section, and an optional message body.
     HTTP-message   = start-line
                      *( header-field CRLF )
                      CRLF
                      [ message-body ]

可以知道碉碉,一個http請求分為三大部分柴钻,分別為開始行、頭部以及消息體垢粮。

start-line

start-line     = request-line / status-line

開始行又可以分為請求行或者狀態(tài)行贴届,對于一個請求為請求行,對于一個返回則為狀態(tài)行蜡吧。

request-line

request-line   = method SP request-target SP HTTP-version CRLF

請求行的格式為method 單個空格 請求的目標 單個空格 HTTP版本 回車換行毫蚓。例如

GET /hello HTTP/1.1

status-line

status-line = HTTP-version SP status-code SP reason-phrase CRLF

狀態(tài)行的格式為HTTP版本 單個空格 狀態(tài)碼 單個空格 原因短語。例如

HTTP/1.1 200 OK

后面的規(guī)則類似昔善,大家可以對照協(xié)議文檔看一下元潘。

設計

了解了http協(xié)議的規(guī)范以后,再想怎么設計程序君仆,大家可能一陣頭大翩概。對于請求和響應的消息體,要分成兩種邏輯處理返咱。如果只看請求的分支钥庇,直接按照規(guī)范解析,那么我們的代碼基本就是

if (validMethods.contains(str)) {
    ...
    if (str.equals(" ")) {
    }
}

用上面的寫法的話咖摹,最后會有一堆嵌套评姨。稍微好一點的話,改成用衛(wèi)語句的寫法

if (!validMethods.contains(str)) {
    throw new Exception();
}
...
if (!str.equals(" ")) {
    throw new Exception();
}
...

這種寫法雖然避免了大堆的嵌套楞艾,書寫更叫流暢参咙,但是不夠優(yōu)雅。至少有以下兩點問題

  1. 對于需要了解這塊業(yè)務的人來將硫眯,閱讀成本太高蕴侧;
  2. 當后面的處理依賴當前所處的分支時,比較難處理两入。

狀態(tài)機

讓我們看一下jetty9是如何處理的净宵。它引入了一個狀態(tài)機的概念。流轉圖如下


狀態(tài)機

通過狀態(tài)機,jetty將對協(xié)議格式的解析轉換成了對狀態(tài)的維護择葡。每個狀態(tài)下都只需要關注自己的業(yè)務邏輯就可以了紧武,極大地提高了維護性,對于代碼的可閱讀性來講也提升了很多敏储。

            // Start a request/response
            if (_state==State.START)
            {
                _version=null;
                _method=null;
                _methodString=null;
                _endOfContent=EndOfContent.UNKNOWN_CONTENT;
                _header=null;
                if (quickStart(buffer))
                    return true;
            }

            // Request/response line
            if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
            {
                if (parseLine(buffer))
                    return true;
            }

            // parse headers
            if (_state== State.HEADER)
            {
                if (parseFields(buffer))
                    return true;
            }

            // parse content
            if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.TRAILER.ordinal())
            {
                // Handle HEAD response
                if (_responseStatus>0 && _headResponse)
                {
                    setState(State.END);
                    return handleContentMessage();
                }
                else
                {
                    if (parseContent(buffer))
                        return true;
                }
            }

            // parse headers
            if (_state==State.TRAILER)
            {
                if (parseFields(buffer))
                    return true;
            }

細心的同學還會發(fā)現(xiàn)阻星,jetty還使用了枚舉的順序來做校驗。枚舉類定義如下:

    // States
    public enum State
    {
        START,
        METHOD,
        RESPONSE_VERSION,
        SPACE1,
        STATUS,
        URI,
        SPACE2,
        REQUEST_VERSION,
        REASON,
        PROXY,
        HEADER,
        CONTENT,
        EOF_CONTENT,
        CHUNKED_CONTENT,
        CHUNK_SIZE,
        CHUNK_PARAMS,
        CHUNK,
        TRAILER,
        END,
        CLOSE,  // The associated stream/endpoint should be closed
        CLOSED  // The associated stream/endpoint is at EOF
    }

這一點也和協(xié)議規(guī)則的特點有關已添,協(xié)議的格式從上到下基本是固定的妥箕。

改進

其實jetty的這段邏輯,只是引入了state這個狀態(tài)變量更舞,具體的邏輯還是比較冗長的畦幢。
如果再進一步,引入狀態(tài)模式缆蝉,對每一種狀態(tài)實現(xiàn)一個狀態(tài)類宇葱,將相應的邏輯封裝在狀態(tài)類下,就更優(yōu)雅了刊头。

適用狀態(tài)機的場景

讓我們再將思路擴展一下黍瞧,除了規(guī)則解析,還有什么比較常用的場景適用使用狀態(tài)機呢芽偏?
后臺的操作流程其實也是比較適用的雷逆。我們最常接觸的,就是軟件安裝的流程污尉,第一步膀哲、第二步、第三步......這種操作用狀態(tài)機實現(xiàn)也是比較容易的被碗。通過把變化封裝在特定的狀態(tài)之中某宪,維護成本也會變得比較低。

參考資料

Jetty9源碼剖析 - Connection組件 - HttpParser
rfc7230

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末锐朴,一起剝皮案震驚了整個濱河市兴喂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌焚志,老刑警劉巖衣迷,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異酱酬,居然都是意外死亡壶谒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門膳沽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汗菜,“玉大人让禀,你說我怎么就攤上這事≡山纾” “怎么了巡揍?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長菌瘪。 經(jīng)常有香客問我腮敌,道長,這世上最難降的妖魔是什么俏扩? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任缀皱,我火速辦了婚禮,結果婚禮上动猬,老公的妹妹穿的比我還像新娘。我一直安慰自己表箭,他們只是感情好赁咙,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著免钻,像睡著了一般彼水。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上极舔,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天凤覆,我揣著相機與錄音,去河邊找鬼拆魏。 笑死盯桦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的渤刃。 我是一名探鬼主播拥峦,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卖子!你這毒婦竟也來了略号?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤洋闽,失蹤者是張志新(化名)和其女友劉穎玄柠,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诫舅,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡羽利,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了骚勘。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铐伴。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡撮奏,死狀恐怖,靈堂內的尸體忽然破棺而出当宴,到底是詐尸還是另有隱情畜吊,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布户矢,位于F島的核電站玲献,受9級特大地震影響,放射性物質發(fā)生泄漏梯浪。R本人自食惡果不足惜捌年,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挂洛。 院中可真熱鬧礼预,春花似錦、人聲如沸虏劲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柒巫。三九已至励堡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堡掏,已是汗流浹背应结。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泉唁,地道東北人鹅龄。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像亭畜,于是被迫代替她去往敵國和親砾层。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內容