【譯】如何閱讀RFC

原文: How to Read an RFC

在實驗室時,讀 RFC 就是一件令人痛苦的事情。今天搬運了一份 RFC 查閱指南,有助於大家查閱的時候保持一個良好的心情。作者是 Charles Eames ,從2000年起就活躍於 IETF,現爲 HTTP and QUIC Working Groups 的聯席主席。


甭管好壞,請求評論(RFCs)正是今天我們在互聯網上定義繁多協議的方式。一開始這些文件會被試圖解讀它們的開發者奉爲圭稚,很快又因爲難以理解而被棄之不顧。這種事情令人沮喪,更嚴重的是會導致互通性和安全上的問題。

但是(敲黑板),只要瞭解它們是如何創建和發佈的,就會更容易理解它們。以下是我從事 HTTP 和其他一些工作中總結出來的經驗。

從哪裏開始

獲取 RFC 文件的標準地方是 RFC Editor Web Site。但是,正如下面將會看到的,這個網站上缺少一部分關鍵信息,所以大多數人會選擇從 tools.ietf.org 獲取。

因爲 RFC 文件實在浩如煙海(目前數量已經超過了9000!),從中找到正確的 RFC 也變成一件困難的事。顯然你可以藉助 Web 搜索引擎來檢索,同時 RFC Editor 網站也提供了出色的站內搜索功能。

另一個選擇是 EveryRFC ,在這個網站(作者搞的)允許通過 RFC 名稱和關鍵詞一起來進行搜索,以及通過標籤進行探索。

毫無疑問,純文本形式的 RFCs 排版醜陋而難以閱讀,但是事情正在往好的方向發展。RFC Editor 正在包裝一種新的 RFC 格式,新的格式將會有更好的呈現,且允許進行定製。同時,如果想要獲取更多有用的 RFC,可以使用針對特定範圍的第三方分支。如 greebytes,維護了和 WebDAV 相關的 RFC;HTTP Working Group 維護了和 HTTP 相關的 RFC。

RFC 的類型

所有的 RFC 在頂部都有一個規範的標識,像這樣

Internet Engineering Task Force (IETF)                  R. Fielding, Ed.
Request for Comments: 7230                                         Adobe
Obsoletes: 2145, 2616                                    J. Reschke, Ed.
Updates: 2817, 2818                                           greenbytes
Category: Standards Track                                      June 2014
ISSN: 2070-1721

左上角的描述 Internet Engineering Task Force (IETF) ,意味着這是 IETF 出產的文檔,雖然很少人知道,但是確實有其他不需要 IETF 達成共識就發佈 RFC 的方式,比如 independent stream

事實上,有很多種流程方式可供發佈一個文檔。但是隻有 IETF 流程才表示整個 IETF 組織已經審查過,並就該協議標準達成了共識

舊的文檔(大約就是 RFC5705 以前的那些)在這個位置標明的是 Network Working Group,因此你需要再多做一點工作以判斷他們是否代表了 IETF 的共識。通過一開始的Status of this Memo這個段落,或者 RFC Editor 網站可以幫助你進行判斷。
sample

接下來是Request for Comments號。如果這裏顯示的是Internet-Draft,那麼它還不是一個 RFC,僅僅是一個提案,一個任何人都可以寫的提案。一個文檔當前爲一個提案並不意味着永遠不會被 IETF 採納。

Category爲這幾個描述之一:Standards TrackInformationalExperimentalBest Current Practice。這幾個的區別有些時候不那麼清晰,但只要是 IETF 出產的,就有合理的審查量。需要注意的是,InformationalExperimental並不是標準,即使是發佈的時候有 IETF 的共識。

最後,作者被列在了文檔頭部的右側。和學術文章不同,這裏並沒有列出所有對該文檔作出貢獻的人,貢獻者這部分內容通常位於底部的Acknowledgmnets。在 RFC 這裏,“作者”就是字面上的意思,就是誰寫了這個文檔。你經常會看到人名後綴Ed.,這表明他們僅僅是編輯者,因爲這個文檔早就存在了(比如修訂 RFC 這種場景)。

它是最新的嗎?

RFCs 是文檔的一系列存檔,不能更改,哪怕改的是一個單詞也不行(看看 RFC7158 和 RFC7159的區別,僅僅是改了一個年份而已)。

結果就是,你必須要知道自己手頭的是正確的文檔。文檔頭部一些元數據能夠幫到你。

  • Obsoletes列出了被本文檔完全替代的 RFCs,你應該看這個,而不是被替代的舊文檔。注意,當新版本的協議標準出爐後,並不代表舊版本的就得廢棄。比如發佈了 HTTP/2 協議並不會廢棄 HTTP/1.1 協議,因爲實現舊的協議仍然是合法和有必要的。但是,RFC7230 就替代了 RFC2616,因爲都是對 HTTP/1.1 的定義。

  • Updates列出了本文檔對其內容有實質修改的 RFCs,換句話說,如果你閱讀了這些 RFCs,那麼最好也閱讀下本文檔。

不幸的是,ASCII 格式的文本文檔(比如在 RFC Editor 上的)沒有告訴你,自己的內容被哪些文檔更新,或者被哪些文檔所廢棄。這使得絕大多數人選擇使用 tools.ietf.org 上的 RFC 分支,該分支恰好對這部分內容有很好的展示

[Docs] [txt|pdf] [draft-ietf-http...] [Tracker] [Diff1] [Diff2] [Errata]

Obsoleted by: 7230, 7231, 7232, 7233, 7234, 7235          DRAFT STANDARD
Updated by: 2817, 5785, 6266, 6585                          Errata Exist

頁面上每一個號碼都是一個鏈接,你可以方便地找到最新的那份。

但即便最新的文檔通常也還存在問題。在頁面的頂部,右側可以看到一個警告信息Errata Exist,你可以到最頂部的Errata去訪問這些信息。
Errata

Errata是對文檔的修正和說明,只是並不足以形成一個新的 RFC。有些時候,它們對於協議的實現有重要的影響(比如,標準中的一個bug導致了重大的誤解),所以值得讀一下。

例如 RFC7230 的 errata。在讀 errata 的時候,也要留心它們的狀態,許多 errata 並沒有被接受,因爲它們只是對標準的誤讀。

理解上下文

一個比你預想的更普遍的現象是:開發者閱讀了 RFC 的描述,根據他們看到的着手實現,最終完成的實物卻和 RFC 作者想表達的意思相悖。

這是因爲在閱讀者會截取文檔部分來閱讀的情況下,想要找到一種保證不會出現誤解的寫作方式是極端困難的(比如對某些神聖經典的誤讀)。

結果就是,讀 RFC 不僅要閱讀直接相關的部分,還要閱讀間接相關的一小部分,不管是在同一份標準文檔,或在其他文檔中。緊要關頭,如果你無法通讀文檔,那麼閱讀所有可能相關的部分也足夠了。

例如,規定 HTTP 頭部由 CRLF 分隔,但是如果你跳過了這部分,將看到接收者將單個的 LF 識別爲行終止符,並忽略所有前面的 CR。對不?

同樣重要,需要提請大家注意的是許多協議在 IANA registries上建立了條目,以管理它們的擴展點。這些擴展點,而非標準文檔,纔是事實的源泉。例如 HTTP 方法的典範列表就在這裏,而不在任何的 HTTP 標準文檔中。

解讀要求

幾乎所有的 RFCs 在頂部都有類似下面這樣的一段文字:

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in
BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all
capitals, as shown here.

RFC2119的這些關鍵詞幫助定義了互通性,但有時也會讓開發者糊塗。像這樣的描述在標準中是常見的:

The Foo message MUST NOT contain a Bar header.

這個要求放置在協議單元(Foo message)前面。如果你正在發送一個 Foo message,顯然不能包含 Bar header;如果包含了 Bar header,那就是一個不符合規範的消息。

但是這條要求對於消息的接收者來說就不那麼清晰了,如果你收到一個帶有 Bar header 的消息,咋整?

部分人會直接拒絕該消息,即使標準並沒有規定要這麼做。其他人仍會處理該消息,但是會剔除 Bar header,或者忽略它,即使標準明確規定所有的消息頭都需要被處理。

這些差異,在無意中導致了互通性問題。正確的做法是遵循正常的頭部處理過程,除非標準中要求不要那麼做。

這是因爲通常意義上,標準只是公開地定義了一些行爲,換句話說,法無禁止即可爲。因此,過度解讀協議標準會在無意中造成破壞,因爲你選擇這樣做的同時還有其他人選擇那樣做。

理想情況下,標準應該從處理消息方的行爲的角度來定義,就像這樣:

Senders of the Foo message MUST NOT include a Bar header. Recipients
of a Foo message that includes a Bar header MUST ignore the Bar header,
but MUST NOT remove it.

除了這些,最好在標準中找找有關錯誤處理的通用建議(比如 HTTP 的 Conformance and Error Handling 部分)

同樣地,請記住這些要求的目標。大部分的標準文檔有一套高度發展的術語,用於區分標準文檔中的不同角色。

比如 HTTP 有代理,這是一箇中介,有客戶端有服務器端(但不是 User-Agent 或原始服務器)。需要注意所有這些角色的要求。

同樣地,依賴於標準所針對的場景,在 HTTP 的一些要求中,區分“生成”消息和僅僅“發送”消息兩個行爲。留意這種類型的術語能夠節省你瞎猜的功夫。

SHOULD

是的,SHOULD 有自己的段落。這個扭扭捏捏的術語傳染了很多 RFC,除非下功夫去根除。RFC2119 是這樣描述它的:

SHOULD  This word, or the adjective "RECOMMENDED", mean that there
        may exist valid reasons in particular circumstances to ignore a
        particular item, but the full implications must be understood and
        carefully weighed before choosing a different course.

實際上,作者通常用 SHOULD 和 SHOULD NOT 表達:“我們樂意你這樣做,但是我們不能總是要求你這樣做”。

例如,在 HTTP 方法的概覽部分,我們看到這樣的描述:

When a request method is received that is unrecognized or not
implemented by an origin server, the origin server SHOULD respond
with the 501 (Not Implemented) status code. When a request method
is received that is known by an origin server but not allowed for
the target resource, the origin server SHOULD respond with the 405
(Method Not Allowed) status code.

這些 SHOULDs 不是 MUSTs,因爲服務器可以合理地決定採用另一種行爲。如果服務器認爲一個請求來自於一個攻擊者,就可以直接斷開連接,或者被請求的資源要求 HTTP 認證,就可以在返回405之前返回一個401錯誤(沒有經過認證)。

SHOULD 並不意味着服務器就可以忽略所有的要求,就因爲 SHOULD 看起來無需被尊重。

有些時候,我們看到 SHOULD 是這樣的形式:

A sender that generates a message containing a payload body SHOULD
generate a Content-Type header field in that message unless the
intended media type of the enclosed representation is unknown to
the sender.

注意“unless”這個詞,它規定了 SHOULD 允許的特殊情形。有爭議的是,這裏應該被定義爲 MUST,因爲 unless 條款仍然會生效,但這種寫法風格在標準中有些普遍。

閱讀樣例

另一個普遍的陷阱是草草瀏覽標準,找到其中的樣例,然後照着實現。

不幸的是,樣例是最少得到作者關注的部分,因爲只要協議有一點變動就需要更新這些樣例。

結果就是,樣例是整個標準文檔中最不靠譜的部分。誠然,作者應該在發佈之前再次徹底地檢查這些樣例,但是總會有錯誤成爲漏網之魚。

而且,一個完美的案例也許並不是爲了闡釋你正在查找的標準中的那部分內容。它們會出於簡潔的原因被截斷,也可能需要額外的一步解碼纔可讀。

就算要花去更多的時間,更好的做法也應該是去讀真正的文本內容,畢竟樣例不能完全代表標準文檔。

On ABNF

Augmented BNF 通常被用於定義協議單元。如:

FooHeader = 1#foo
foo       = 1*9DIGIT [ ";" "bar" ]

一旦你習慣了,會發現 ABNF 提供了一個易於理解的框架去描述協議的一些元素是什麼樣的。

但是,ABNF 是“負有野心的”。它建立了一種理想的消息格式,並要求你生成的消息必須要匹配它。它並沒有規定如果接收到不匹配它格式的消息應該怎麼處理。實際上,許多規範都沒有說明 ABNF 和處理要求之間的關係。

如果你嘗試嚴格遵守它們的 ABNF 格式,大多數協議會發生嚴重的問題,小部分正常。在上面的例子中,空格不允許出現在冒號的周圍,但是你完全可以賭有些人會這樣做,然後一些實現也同樣會接受它。

所以,確認你讀完了 ABNF 附近的附加要求和上下文,並意識到如果沒有直接要求,就需要調整解析,使得接收的範圍比 ABNF 要求的更大。

一些規範開始認識到 ABNF 的雄心壯志,明確定義了包含錯誤處理的解析算法。當定義明確了,就務必嚴格準守,以保障互操作性。

安全考量

RFC3552 開始,RFC 的模板中就包括了Security Considerations部分。

結果就是,鮮少有 RFC 發佈的時候會沒有一個關於安全的關鍵章節。審查過程不允許一個草案裏寫“本協議沒有安全考量部分”。

所以,不管你是在實現或者部署一個協議,花點精力去閱讀和理解安全考量部分是值得的。否則,很有可能栽到坑裏。

遵循規範的參考(如果有的話)是一個好主意。如果沒有,嘗試查找一些術語,以增進對當前正在討論問題的理解。

更多

如果一個 RFC 不能回答你的問題,或者你不能確認它內容裏的描述,最好的辦法就是找到最相關的 Working Group,在他們的郵件列表中提問。如果沒有活躍的工作組能夠解決問題中涉及的主題,試試合適的區域中的郵件列表。

提交一個 errata 通常不是你第一步應該採取的措施,先去找個人談談吧。

許多工作組現在採用 Github 來管理他們的規範,如果你對某個活躍的規範存疑,那麼到 Github 上去提交問題。如果這個規範已經是一個 RFC,通常最好的做法是使用郵件列表,除非你找到一個相反的方向。

我確認還有很多關於如何閱讀 RFC 的內容可以寫,一些我已經寫在這兒的內容也存在爭議,但這就是我的思考。希望能幫到你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章