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

需求

如果讓你寫一段程序,解析http協(xié)議的請(qǐng)求報(bào)文,你會(huì)怎么寫?
在實(shí)現(xiàn)這個(gè)需求之前,我們先了解一下http協(xié)議格式。http協(xié)議有很多種規(guī)范,rfc2616、rfc7230等等,這里我們以rfc7230為例,拿一個(gè)具體的例子分析:

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 ]

可以知道,一個(gè)http請(qǐng)求分為三大部分,分別為開始行、頭部以及消息體。

start-line

start-line     = request-line / status-line

開始行又可以分為請(qǐng)求行或者狀態(tài)行,對(duì)于一個(gè)請(qǐng)求為請(qǐng)求行,對(duì)于一個(gè)返回則為狀態(tài)行。

request-line

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

請(qǐng)求行的格式為method 單個(gè)空格 請(qǐng)求的目標(biāo) 單個(gè)空格 HTTP版本 回車換行。例如

GET /hello HTTP/1.1

status-line

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

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

HTTP/1.1 200 OK

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

設(shè)計(jì)

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

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

用上面的寫法的話,最后會(huì)有一堆嵌套。稍微好一點(diǎn)的話,改成用衛(wèi)語句的寫法

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

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

  1. 對(duì)于需要了解這塊業(yè)務(wù)的人來將,閱讀成本太高;
  2. 當(dāng)后面的處理依賴當(dāng)前所處的分支時(shí),比較難處理。

狀態(tài)機(jī)

讓我們看一下jetty9是如何處理的。它引入了一個(gè)狀態(tài)機(jī)的概念。流轉(zhuǎn)圖如下


狀態(tài)機(jī)

通過狀態(tài)機(jī),jetty將對(duì)協(xié)議格式的解析轉(zhuǎn)換成了對(duì)狀態(tài)的維護(hù)。每個(gè)狀態(tài)下都只需要關(guān)注自己的業(yè)務(wù)邏輯就可以了,極大地提高了維護(hù)性,對(duì)于代碼的可閱讀性來講也提升了很多。

            // 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;
            }

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

    // 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
    }

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

改進(jìn)

其實(shí)jetty的這段邏輯,只是引入了state這個(gè)狀態(tài)變量,具體的邏輯還是比較冗長(zhǎng)的。
如果再進(jìn)一步,引入狀態(tài)模式,對(duì)每一種狀態(tài)實(shí)現(xiàn)一個(gè)狀態(tài)類,將相應(yīng)的邏輯封裝在狀態(tài)類下,就更優(yōu)雅了。

適用狀態(tài)機(jī)的場(chǎng)景

讓我們?cè)賹⑺悸窋U(kuò)展一下,除了規(guī)則解析,還有什么比較常用的場(chǎng)景適用使用狀態(tài)機(jī)呢?
后臺(tái)的操作流程其實(shí)也是比較適用的。我們最常接觸的,就是軟件安裝的流程,第一步、第二步、第三步......這種操作用狀態(tài)機(jī)實(shí)現(xiàn)也是比較容易的。通過把變化封裝在特定的狀態(tài)之中,維護(hù)成本也會(huì)變得比較低。

參考資料

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,125評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,506評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,402評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,934評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,168評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,690評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,596評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,784評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評(píng)論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,027評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評(píng)論 1 280
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,398評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,743評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標(biāo)準(zhǔn)。 注意:講述HT...
    kismetajun閱讀 27,552評(píng)論 1 45
  • 一、Jetty目錄剖析 bin:可執(zhí)行腳本文件demo- base:etc:Jetty模塊定義的XML配置文件的目...
    高廣超閱讀 4,000評(píng)論 0 32
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說閱讀 11,045評(píng)論 6 13
  • 年齡不會(huì)影響一個(gè)人的魅力,只要有自信、有毅力,你流下的每一滴汗水,都會(huì)化作對(duì)抗衰老的子彈!” 你有多自律,人生就有...
    羅劍華閱讀 457評(píng)論 2 3
  • 親愛的小壞壞, 你好呀! 今天還是很冷啊。大冬天喂奶真是太不方便了,凍得哆哆嗦嗦的真是每次把你從暖暖的被窩里抱起來...
    安安媽媽_小竹子閱讀 149評(píng)論 0 0