使用 gorilla/mux 增強 Go HTTP 伺服器的路由能力

KevinYan發表於2020-02-05

圖片

今天這篇文章我們將會為我們之前編寫的HTTP伺服器加上覆雜路由的功能以及對路由進行分組管理。在之前的文章《深入學習用 Go 編寫HTTP伺服器》中詳細地講了使用net/http進行路由註冊、監聽網路連線、處理請求、安全關停服務的實現方法,使用起來非常方便。但是net/http有一點做的不是非常好的是,它沒有提供類似URL片段解析、路由引數繫結這樣的複雜路由功能。好在在Go社群中有一個非常流行的gorilla/mux包,它提供了對複雜路由功能的支援。在今天這篇文章中我們將探究如何用gorilla/mux包來建立具有命名引數、GET/POST處理、分組字首、限制訪問域名的路由。

安裝gorilla/mux

我們在之前寫的HTTP服務的程式碼根目錄,使用go get命令從GitHub安裝軟體包,如下所示:

go get  github.com/gorilla/mux

在《深入學習用 Go 編寫HTTP伺服器》中我們介紹過路由註冊、匹配和最後處理函式的呼叫都是由ServeMux(服務複用器)來完成的,而且我們還自己定義了複用器用以替換預設的DefaultServeMux。同樣的gorilla/mux包也是為我們提供了一個複用器。這個複用器擁有很多功能用以提升編寫Web應用的效率,而且與標準的http.ServeMux相容。

使用gorilla/mux

建立路由器

可以像下面這樣建立一個路由器

router := mux.NewRouter()

會返回一個mux.Router例項,mux.Router將傳入的請求與已註冊路由列表進行匹配,併為與URL或其他條件匹配的路由呼叫處理程式。主要特點是:

  • 可以根據URL主機,路徑,路徑字首,Header頭、查詢值,HTTP方法進行路由匹配,或是使用自定義匹配器。
  • URL主機,路徑和查詢值可以是帶有可選正規表示式的變數。
  • 路由可以被用作子路由,只有父路由匹配後才會嘗試匹配子路由。這對於定義路由組非常有用,路由組可以共享主機、路徑字首、或者其他常見的屬性。
  • 它實現了http.Handler介面,因此與標準的http.ServeMux完全相容。

註冊路由處理程式

我們將之前程式裡自定義的服務複用器替換成上面建立好mux.Router,併為其註冊路由處理器。

type helloHandler struct{}
func (*helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World")
}
func WelcomeHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome!")
}

func main() {
    router := mux.NewRouter()
    router.Handle("/", &helloHandler{})
  router.HandleFunc("/welcome", WelcomeHandler)
  ......
}

之前我們是用http.Hanlehttp.HandleFunc註冊處理程式的,這裡直接換成router.Hanlerouter.HanleFunc即可,很方便。

定義帶命名引數的路由

使用mux.Router的最大優勢是可以從請求URL中提取分段,然後作為命名引數傳入路由處理程式供使用。

接下來為我們的程式註冊一個路由處理器,讓伺服器能夠處理URL/names/Gorge/countries/NewZealand的請求:

router.HandleFunc("/names/{name}/countries/{country}", func(writer http.ResponseWriter, request *http.Request) {
            ......
})

接下來在處理函式中使用mux.Vars()函式從這些URL分段中獲取資料。該函式以http.Request為引數並返回一個URL分段名為鍵,提取的資料為值的字典。

func(writer http.ResponseWriter, request *http.Request) {
        vars := mux.Vars(request)
        name := vars["name"]
        country := vars["country"]
        fmt.Fprintf(writer, "This guy named %s, was coming from %s .", name, country)
})

讓伺服器使用我們建立的路由器

這個設定很簡單,如果沒有自定義http.Server物件,使用http.ListenAndServe(":8000", router),使用自己定義的`http.Server 物件時則是:

server := &http.Server{
    Addr:    ":8080",
    Handler: router,
}

這個和我們把自定義的服務複用器傳遞給http.Server沒有任何區別。

改造完後我們之前寫的HTTP伺服器就可以根據具體的 URL 動態地構造響應。關鍵字回覆http02可獲得完整的原始碼

其他gorilla/mux路由器的常用功能

設定路由的HTTP方法

限制路由處理器只處理指定的HTTP方法的請求:

router.HandleFunc("/books/{title}", CreateBook).Methods("POST")
router.HandleFunc("/books/{title}", ReadBook).Methods("GET")
router.HandleFunc("/books/{title}", UpdateBook).Methods("PUT")
router.HandleFunc("/books/{title}", DeleteBook).Methods("DELETE")

上面的就是一組可以響應具體HTTP方法的RESTful風格的介面的路由。

設定路由的域名

限制路由處理器只處理訪問指定域名加路由的請求:

router.HandleFunc("/books/{title}", BookHandler).Host("www.mybookstore.com")

限制HTTP 方案

將請求處理程式可響應的HTTP方案限制為http或者https

router.HandleFunc("/secure", SecureHandler).Schemes("https")
router.HandleFunc("/insecure", InsecureHandler).Schemes("http")

設定路徑字首和子路由

bookrouter := router.PathPrefix("/books").Subrouter()
bookrouter.HandleFunc("/", AllBooks)
bookrouter.HandleFunc("/{title}", GetBook)

使用gorilla/mux改進我們的HTTP伺服器

接下來我們使用gorilla/mux對我們之前寫的HTTP伺服器做一下改進,之前我們所有程式都放在了main.go中,現在我們的程式還很小,所以我們先不把專案目錄規劃的太複雜,先通過檔案做下簡單的職責劃分,新建兩個檔案router.gohandler.go分別用來存放路由註冊的邏輯和路由對應的處理器函式,兩個檔案的示例內容如下。

handler.go:

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "net/http"
)

type HelloHandler struct{}

func (*HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World")
}

func ShowVisitorInfo(writer http.ResponseWriter, request *http.Request) {
    vars := mux.Vars(request)
    name := vars["name"]
    country := vars["country"]
    fmt.Fprintf(writer, "This guy named %s, was coming from %s .", name, country)
}

router.go:

package main

import (
    "github.com/gorilla/mux"
)

func RegisterRoutes(r *mux.Router) {
    indexRouter := r.PathPrefix("/index").Subrouter()
    indexRouter.Handle("/", &HelloHandler{})

    userRouter := r.PathPrefix("/user").Subrouter()
    userRouter.HandleFunc("/names/{name}/countries/{country}", ShowVisitorInfo)
}

router.go中我們將路由分為indexuser兩組,在兩個路由組上分別定義路由。將這部分封裝在一個匯出函式RegisterRoutes供呼叫。這樣即使以後路由註冊的程式要放到單獨的目錄裡也可以供外部呼叫。

整理完後我們的main.go中就會變的很簡潔:

func main() {
   //mux := http.NewServeMux()
   //mux.Handle("/", &helloHandler{})
   muxRouter := mux.NewRouter()

   RegisterRoutes(muxRouter)

   server := &http.Server{
      Addr:    ":8080",
      Handler: muxRouter,
   }

   ......
   err := server.ListenAndServe()
   ......
}

關注公眾號回覆go-http-02獲取本文中完整的示例程式碼。

前文回顧:深入學習用 Go 編寫HTTP伺服器

喜歡我的文章,幫忙轉發點贊,如在實踐過程中遇到什麼問題可在下方給我留言。

圖片

本作品採用《CC 協議》,轉載必須註明作者和本文連結

公眾號:網管叨bi叨 | Golang、PHP、Laravel、Docker等學習經驗分享

相關文章