Josh Hsieh
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- title: Go 1.22 對路由的改善 date: 2024-04-01 00:31:15 categories: - Software Engineering tags: - Go - Golang - Translation --- [![hackmd-github-sync-badge](https://hackmd.io/MhHB3mlFRR6yhYgzwqu8NQ/badge)](https://hackmd.io/MhHB3mlFRR6yhYgzwqu8NQ) > 譯自 [Routing Enhancements for Go 1.22](https://go.dev/blog/routing-enhancements) > [name=Roy Thomas Fielding, on behalf of the Go team] 13 February 2024 Go 1.22 為 `net/http` 套件的路由器上帶來了兩項改進: 方法匹配 (method matching) 以及萬用字元 (wildcards)。這些特性能讓你用模式 (patterns) 來描述常見的路由規則,而不必再用 Go 程式碼手動撰寫路由邏輯。雖然它們易於使用及解釋,但當一個請求匹配多個模式時,找到正確的規則去選擇最終的 (winning) 模式是一個挑戰。 這些改變是我們持續努力讓 Go 成為建立生產環境系統 (production systems) 的一種良好語言的一部分。我們研究了很多第三方的網站框架,抽取出我們認為最常被使用的功能,並將它們整合進 `net/http`。然後我們在 [GitHub discussion](https://github.com/golang/go/discussions/60227) 和 [proposal issue](https://go.dev/issue/61410) 中與社群協作,驗證我們的選擇並改進我們的設計。把這些功能加入標準函式庫 (standard library) 許多專案就可以少一個額外依賴 (dependency)。但對於目前的使用者或有進階路由需求的程式來說,第三方網站框架仍是一個不錯的選擇。 # 改善 新的路由特性幾乎只影響到了要傳進 `net/http.ServeMux` 的兩個方法 `Handle` 跟 `HandleFunc` 的模式字串 (pattern string),以及對應的 頂層函式 `http.Handle` 跟 `http.HandleFunc`。唯一的 API 變更,是在 `net/http.Request` 上新增了兩個用來處理萬用字元匹配的方法。 我們將透過一個假想的部落格伺服器來說明這些變更,其中每篇文章都有一個整數 ID。像 `GET /posts/234` 這樣的請求會找出 ID 為 234 的文章。在 Go 1.22 版本之前,處理這些請求的程式碼會以以下這樣的一行開始: ```go http.HandleFunc("/posts/", handlePost) ``` 後方的 `/` 將所有以 `/posts/` 開頭的請求都路由到 `handlePost` 函式,該函式必須檢查 HTTP 方法是否為 `GET`,提取出識別碼,然後找出文章。由於方法檢查並不是滿足這個請求的硬性必要條件,很容易就被忘記。結果就是像 DELETE `/posts/234` 這樣的請求也會讀到文章,這通常不是我們預期的行為。 在 Go 1.22 中,現有的程式碼會繼續運作,或者你可以改為寫成這樣: ```go http.HandleFunc("GET /posts/{id}", handlePost2) ``` 這個模式會匹配 HTTP GET 請求,且其路徑以 `/posts/` 開頭並剛好有兩段。(作為一個特殊案例,`GET` 也會匹配到 `HEAD`;其他所有的方法都需要完全匹配)。`handlePost2` 函數不再需要檢查方法,而且可以透過 `net/http.Request` 中的新 `PathValue` 方法提取識別碼字串: ```go idString := req.PathValue("id") ``` `handlePost2` 的其餘部分的行為會像 `handlePost` 一樣,將字串識別碼轉換為整數並取得文章。 如果沒有註冊其他能匹配模式,像 `DELETE /posts/234` 就會失敗。根據 [HTTP semantics](https://httpwg.org/specs/rfc9110.html#status.405),一個 `net/http` 伺服器會回應 `405 Method Not Allowed` 的錯誤,並在 `Allow` 這個標頭 (header) 中列出允許的方法。 萬用字元可以匹配整個段落,像上方例子中的 {id},如果它以 `...` 結尾,它可以匹配到路徑所有剩餘的段落,就像模式 `/files/{pathname...}` 。 最後一點語法。正如我們上面展示的,以 `/` 結尾的模式,如 `/posts/`,會匹配所有以此字串開頭的路徑。要匹配只有 `/` 結尾的路徑,你可以寫成 `/posts/{$}`。這將匹配 `/posts/` 但不匹配 `/posts` 或 `/posts/234`。 還有最後一點 API: `net/http.Request` 有一個 `SetPathValue` 方法,這樣標準函式庫外的路由器可以透過 `Request.PathValue` 使他們自己的路徑解析結果是可使用的。 # 優先級 每個 HTTP 路由器都必須處理重疊 (overlap) 的模式,例如 `/posts/{id}` 和 `/posts/latest`。這兩個模式都能匹配路徑 `posts/latest`,但只能有一個可以處理這個請求。哪一個模式會優先被選中呢? 一些路由器不允許重疊;其他的會使用最後註冊的模式。Go 一直都允許重疊,並都會選擇較長的模式無論註冊順序。保持順序獨立性對我們來說很重要 (且對於向後兼容是必須的),但我們需要一個比"最長就贏"更好的規則。此規則會選擇 `/posts/latest` 而不是 `/posts/{id}`,但會優先選擇 `/posts/{identifier}` 而不選擇這兩者。這看起來不太對:萬用字元的名稱不應該有影響。感覺 `/posts/latest` 應該總是在這場競爭中勝出,因為它只匹配到一個路徑,而不是多個。 我們探索一個好的優先級規則的過程,讓我們考慮到模式的許多特性。例如,我們考慮過優先選擇具有最長實際 (非萬用字元) 前綴的模式。這樣會選擇 `/posts/latest` 而不是 `/posts/{id}`。但這樣無法區分 `/users/{u}/posts/latest` 和 `/users/{u}/posts/{id}`,而前者似乎要具有優先權。我們最終選擇一條基於模式意義而不是外觀的規則。每個有效的模式都匹配一組請求。例如,`/posts/latest` 匹配路徑為 `/posts/latest` 的請求,而 `/posts/{id}` 匹配第一段為 `posts` 的任何有兩個段落的請求。如果一個模式匹配的請求是另一個模式匹配請求的嚴格子集,我們認為這個模式比另一個模式更精確。模式 `/posts/latest` 比 `/posts/{id}` 更精確,因為後者匹配的請求包含前者的匹配還有更多。 優先權規則很簡單: 最精確的模式勝出。這個規則符合我們的直覺,認為 `posts/latest` 應該被優先於 `posts/{id}`,而 `/users/{u}/posts/latest` 應該被優先於 `/users/{u}/posts/{id}`。這對於方法也是有道理的。例如,`GET /posts/{id}` 有高於 `/posts/{id}` 的優先權,因為前者僅匹配 `GET` 和 `HEAD` 請求,而後者則匹配任何方法的請求。 "最精確獲勝"的規則是將原始"最長獲勝"的規則概括 (generalize),用於原始模式的路徑部分,即沒有萬用字元或 `{$}` 的部分。這樣的模式只有在一個是另一個的前綴時才會重疊,而較長的模式則是更精確的。 那如果兩個模式重疊但都沒有更精確呢?例如,`/posts/{id}` 和 `/{resource}/latest` 都能匹配 `/posts/latest`。沒有明顯的答案指出哪個具優先權,所以我們認為這些模式彼此衝突。無論以何種順序註冊它們都會引發 `panic`。 優先級規則對於方法和路徑的工作方式完全如上所述,但我們不得不為了保持兼容性對主機 (hosts) 做出一個例外: 如果兩個模式在其他情況下會衝突,而其中一個有主機而另一個沒有,那麼帶有主機的模式將擁有優先權。 電腦科學的學生可能會回想起關於正規表達式 (regular expressions) 和正規語言 (regular languages) 的精彩理論。每個正規表達式都選定一個正規語言,即由該表達式匹配的字串集合。有些問題談論語言而非表達式的話,提出和回答會更容易。我們的優先權規則就是受到這個理論的啟發。實際上,每個路由模式都對應一個正規表達式,而匹配的請求集合扮演著正規語言的角色。 通過語言而非表達式來定義優先權,使其易於陳述和理解。但基於可能有無限集合的規則有一個缺點:如何有效實現它並不明確。我們可以通過檢查逐個段落來確定兩個模式是否衝突。粗略來說,如果一個模式在另一個模式有萬用字元的位置上有一個字面 (literal) 段落,則它更具體;但如果字面與萬用字元在兩個方向上都對齊,則這些模式衝突。 當新模式註冊到 `ServeMux` 時,它會檢查與先前註冊的模式是否存在衝突。但檢查每一對模式將會耗費二次方的時間。我們使用一個索引來跳過與新模式無法衝突的模式;在實踐中,這效果相當好。無論如何,這種檢查是在模式註冊時發生的,通常是在服務器啟動時。在 Go 1.22 中匹配進來的請求花費的時間與之前的版本相比並沒有太大變化。 # 兼容性 我們盡力保持新功能與舊版本的 Go 相容。新的模式語法是舊語法的超集,而新的優先級規則也是對舊規則的概括。但有一些邊緣案例 (edge cases)。例如,之前版本的 Go 接受帶有大括號的模式並將其作為字面處理,但 Go 1.22 使用大括號表示萬用字元。透過把 GODEBUG 設定成 `httpmux=go121` 就能恢復舊的行為。 更多關於這些路由改善功能的細節,可以查看 `net/http.ServeMux` 的[文件](https://go.dev/pkg/net/http#ServeMux)。

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully