Skip to content

HttpRouter

倉庫地址: julienschmidt/httprouter: A high performance HTTP request router that scales well (github.com)

Gin 的路由組件采用的是HttpRouter,這同樣也是一個輕量,高性能的路由組件,整個組件只有三個.go文件,代碼十分的簡潔,其主要有以下特點。

一對一匹配:一個請求只能匹配到零個或一個路由,且有利於 SEO 優化。

路徑自動校正:隨意選擇喜歡的 URL 風格,就算多了或者少了一個斜槓,也會自動重定向。如果有大小寫錯誤的話,查找時也會忽略大小寫進行正確的重定向。

路由參數自動解析:只要給路徑段一個名稱,路由器就會把動態值傳遞給你。由於路由器的設計,路徑參數解析的佔用非常低廉。

零垃圾:在路由分配與調度的過程中,不會產生任何內存垃圾。

RefstfulAPI 支持:路由器的設計鼓勵合理的分層的 Restful API。

錯誤處理:可以設置一個錯誤處理器來處理請求中的異常,路由器會將其捕獲並記錄,然後重定向到錯誤頁面。

基本用法

就像是springboot一樣,一個函數綁定一個 URL 且對應一個處理器。

go
package main

import (
   "fmt"
   "github.com/julienschmidt/httprouter"
   "log"
   "net/http"
)

func Hello(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
   fmt.Fprintf(w, "<h1>Hello World!")
}

func main() {
   router := httprouter.New()
   router.GET("/hello", Hello)
   log.Fatal(http.ListenAndServe(":8080", router))
}

隨後用瀏覽器或者任何的接口測試工具輸入127.0.0.1:8080,即可看到正確的內容,我們可以看到HttpRouter僅僅只是做了路由,實際上依舊是采用的net/http默認組件,gin也是如此,只不過封裝的相對而言要更深一點。

命名參數

go
package main

import (
   "fmt"
   "github.com/julienschmidt/httprouter"
   "log"
   "net/http"
)

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
   fmt.Fprintf(w, "<h1>Hello World!")
}

func main() {
   router := httprouter.New()
   router.GET("/hello:name", Hello)
   log.Fatal(http.ListenAndServe(":8080", router))
}

這次的路由後面加了一個:namename就是一個命名參數,可以通過httprouter.Params來訪問參數切片,可以通過索引或者ByName(name)來獲取參數。同樣的,你可以把http.handlerhttp.handlerFunc當作httprouter.handler來使用,路由本身實現了其接口,例如下方的例子。

go
func Hello(w http.ResponseWriter, r *http.Request) {
    params := httprouter.ParamsFromContext(r.Context())
    //params := r.Context().Value(httprouter.ParamsKey)也可以

    fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
}

當方法綁定的路由是/user/:user,下面的幾種 URL 的匹配情況

 /user/gordon              match
 /user/you                 match
 /user/gordon/profile      no match
 /user/                    no match

你不能將/user/new/user/:user注冊到同一個請求方法上,每一個請求方法應當是相互獨立的。

捕獲全部參數

第二種類型是捕獲全部參數,顧名思義,他們匹配一切,因此必須位於Pattern的尾部。

go
Pattern: /src/*filepath

 /src/                     match
 /src/somefile.go          match
 /src/subdir/somefile.go   match

HttpRouter的工作原理是構建大量的前綴樹,感興趣的可以了解:httprouter package - github.com/julienschmidt/httprouter - Go Packages

OPTIONS & CORS

有些人可能會希望修改對於 OPTIONS 的自動響應並設置一些響應頭來適配 CORS 的預檢請求,這些需求可以通過使用Router.GlobalOPTIONShandler 來實現。

go
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("Access-Control-Request-Method") != "" {
        // 設置CORS響應頭
        header := w.Header()
        header.Set("Access-Control-Allow-Methods", r.Header.Get("Allow"))
        header.Set("Access-Control-Allow-Origin", "*")
    }

    // 適配狀態碼204
    w.WriteHeader(http.StatusNoContent)
})

NOT FOUND 處理器

TIP

可能需要關閉Router.HandleMethodNotAllowed,來避免一些問題。

go
router.NotFound = http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
  //你的邏輯
})

基本校驗

go
package main

import (
  "fmt"
  "log"
  "net/http"

  "github.com/julienschmidt/httprouter"
)

func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
  return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    //獲取基本的身份憑據
    user, password, hasAuth := r.BasicAuth()

    if hasAuth && user == requiredUser && password == requiredPassword {
      // 將請求委派給給予的處理器
      h(w, r, ps)
    } else {
      // 否則請求認證
      w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
      http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
    }
  }
}

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  fmt.Fprint(w, "Not protected!\n")
}

func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
  fmt.Fprint(w, "Protected!\n")
}

func main() {
  user := "gordon"
  pass := "secret!"

  router := httprouter.New()
  router.GET("/", Index)
  router.GET("/protected/", BasicAuth(Protected, user, pass))

  log.Fatal(http.ListenAndServe(":8080", router))
}

Golang學習網由www.golangdev.cn整理維護