Go 語言版本 1-22 的路由增強功能

Go 1.22 對 net/http包中的路由器進行了兩項增強:方式匹配和通配符。這些特性允許你將常見的路由表示爲模式,而非 Go 代碼。儘管這些功能簡單易解釋和使用,但想選擇成功模式的正確規則(當多個模式匹配一個請求時)依然是個挑戰。我們作出這些改變是爲了持續讓 Go 成爲構建生產系統的優秀語言。我們研究了許多第三方 web 框架,提取出我們認爲最常用的特性,並將它們集成進 net/http。然後我們通過在 GitHub 上與社區進行討論和問題建議,驗證了我們的選擇並改進了我們的設計。將這些特性添加到標準庫意味着許多項目少了一個依賴性。但對於當前用戶或帶有高級路由需求的程序來說,第三方 Web 框架仍然是一個很好的選擇。

新增功能

新的路由功能幾乎完全影響到了 net/http.ServeMux方法 HandleHandleFunc傳遞的模式字符串,以及頂層函數 http.Handlehttp.HandleFunc。唯一的 API 變化是在 net/http.Request上添加了兩個新方法,用於處理通配符匹配。我們將通過一個假設的博客服務器來說明這些變化,其中每篇帖子都有一個整數標識符。請求如 GET /posts/234取回 ID 爲 234 的帖子。在 Go 1.22 之前,處理這些請求的代碼將從這樣的一行開始:

http.Handle("/posts/", handlePost)

尾部的斜槓將所有以 /posts/開始的請求路由到 handlePost函數,該函數將需要檢查 HTTP 方法是否爲 GET,提取標識符,並取回帖子。由於方法檢查並非嚴格必要以滿足請求,忽略它將是一個自然的錯誤。這將意味着像 DELETE/posts/234這樣的請求將取回帖子,這至少是令人驚訝的。而在 Go 1.22 中,現有代碼將繼續工作,或者你可以改寫成這樣:

http.Handle("GET /posts/{id}", handlePost2)

這個模式匹配路徑開始以 /posts/的請求並且有兩個段的 GET 請求將匹配到這個模式(特別是,GET 也可以匹配 HEAD; 其他所有的方法都僅僅匹配一次)。handlePost2這個函數不再需要檢查方法,並且可以通過在 Request 上使用新的 PathValue方法來編寫提取標識符字符串:

idString := req.PathValue("id")

剩下的 handlePost2將會行爲類似 handlePost,將字符串標識符轉換爲整數並獲取帖子。如 DELETE /posts/234 這樣的請求在沒有其他匹配模式被註冊的情況下將失敗。根據 HTTP 語法,一個 net/http服務器將會回覆這樣的請求一個 405 Method Not Allowed 錯誤並在 Allow 頭中列出可用的方法。一個通配符可以匹配一個全部的段,就像上面的例子中的 {id},或者,如果它以 ...結束,它可以匹配路徑中所有剩餘的段,就像這個模式:/files/{pathname...}。還有最後一點語法。正如我們上面展示的,以斜槓結尾的模式,像 /posts/,匹配所有以該字符串開始的路徑。要只匹配結尾有斜槓的路徑,你可以寫成 /posts/{$}。這將匹配到 /posts/但不會匹配到 /posts/posts/{234}。還有最後一點 API:net/http.Request有一個 SetPathValue方法,以便標準庫之外的路由器可以通過 Request.PathValue 使他們自己路徑解析的結果可用。

優先級

每一個 HTTP 路由器必須處理重疊的模式,就像 /posts/{id}/posts/latest。這兩種模式都匹配 “posts/latest” 路徑,但最多隻能有一個處理請求。哪一個模式優先呢?一些路由器不允許重疊;其它的使用最後註冊的模式。Go 一直允許重疊,並且有選擇更長的模式的優先級規則,不論註冊順序。保證順序不依賴很重要並且必須向後兼容,但我們需要一個比 “最長的贏” 更好的規則。這個規則會選擇 /post/latest而不是 /post/{id},但卻會選擇 /posts/{id}/{category}而不是 /posts/{id}。當我們選擇新的規則時,有幾點我們想要:我們想要一個可以預測的規則,這樣當你看到一系列模式註冊的時候,你可以知道哪一個模式會匹配給定路徑。如果這個規則對於模式註冊的順序沒有依賴性,那麼它就是 “順序不依賴” 的,用戶將更容易理解和處理。因爲所以這個匹配的路徑在處理程序內部都能被看到,一個好的規則需要對於所有請求一視同仁,其他規則則可能給攻擊者提供利用程序隱藏 bug 的機會。最後,我們希望新規則能夠儘可能的保留現有的行爲,以支持向後兼容性。目前, net/http的優先級規則被定義爲,“更長的元素會匹配”。換句話說,我們會比較模式中的元素總數量,包括各種元素類型,即使他們不在同級別。例如,模式 /a/b/c/a/b之間的比較,將會有 /a/b/c勝出,因爲它有 3 個元素,而 /a/b只有 2 個。在新的規則下, 我們依然遵循舊規定,長優先匹配,但引入了明顯的層級關係。在進行比較的時候,我們會先看模式元素類型的數量是否有不同, 然後纔看元素的總數量。所以 /a/b/c仍然會優先匹配,因爲 /a/b只有兩層, " 這很直觀。然而,當涉及到帶有通配符的模式時,這種層級關係就顯得非常重要了。如 /a/{p}/c/a/b, /a/b優先匹配,儘管總元素數量較少, 因爲它包含更多的文字元素。帶有方法和通配符的新模式讓我們更有信心地使用新匹配規則,新規則會盡量匹配文本元素,而適當地降低了通配符元素優先級。特定的元素(文本和方法)在具體的元素(通配符)之前,文本元素優先於方法元素。在比較完所有這些情況後,我們最後才進行長度比較。這種新的優先級規則在實踐中很容易理解和預測。

總結

這些新特性讓 Go 的內置 HTTP 庫更強大,能更清晰地表達日常 web 編程任務。這些特性是 net/http包長期沉澱的結果,經過了多次反覆考量,包括研究 web 框架的 API,同社區反覆交流,以及我們認爲對於大部分 Go web 編程來說是有意義的特性。如果你對這些特性有任何的反饋,歡迎在我們的 issue tracker 上提出。我們期待你在 Go1.22 版本中試用這些新功能!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/chdh-jy4wHOdni-HLo3Tfg