Go Web 開發不得不說的請求路由

1. 請求路由的概念

請求路由的定義: 請求路由是指將客戶端的請求與服務器上對應的處理程序匹配和映射的過程。它決定了不同的 URL 或 API 請求被映射到哪些處理函數。

請求路由的作用: 請求路由實現了請求與處理函數之間的解耦,使代碼更加模塊化。同時,它也使得 URL 和 API 更符合 RESTful 設計規範。請求路由還可以實現諸如負載均衡、緩存、日誌記錄、身份驗證等功能。

2. net/http 包提供的請求路由功能

ServeMux 路由請求: net/http 包中的 ServeMux 類型實現了 HTTP 請求路由。它包含一個映射表,將註冊的模式映射到對應的處理程序。

請求來臨時, ServeMux 會在這個表中查找與請求 URL 匹配的模式,如果找到匹配的,就會調用註冊的處理程序。

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/foo", fooHandler)
    mux.HandleFunc("/bar", barHandler)
    http.ListenAndServe(":8080", mux)
}
func fooHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello Foo!") 
}
func barHandler(w http.ResponseWriter, r *http.Request) {
   fmt.Fprint(w, "Hello Bar!")  
}

上面使用 ServeMux 將 /foo 映射到 fooHandler 處理程序, /bar 映射到 barHandler。請求來臨時就會調用對應的處理程序。

Handlers and HandlerFunc 路由處理: Go 語言通過 http.Handler 接口和 HandlerFunc 類型來定義請求處理程序。所有處理請求的處理程序都必須滿足 http.Handler 接口。

HandlerFunc 類型則是實現了 http.Handler 接口的一個適配器,可以很方便的把普通函數 (符合特定簽名) 轉換成 http.Handler。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request) 
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
   f(w, r)
}

在上面的 ServeMux 的示例中, 用 HandleFunc() 函數使用了 HandlerFunc 類型的處理程序。

路由匹配規則: ServeMux 的路由匹配採用最長匹配原則。也就是說匹配度越高的路由擁有優先權。如果添加了這兩個路由映射關係:

mux.HandleFunc("/app/", appHandler)
mux.HandleFunc("/app/info", infoHandler)

那麼對於 /app/info 請求來說, 會匹配 infoHandler 而不是 appHandler。

因爲更長更具體的模式 /app/info 擁有優先匹配權。這就使得 ServeMux 實現了基本的請求路由功能。

3. 第三方路由框架

Go 語言生態中有很多優秀的請求路由框架。基於 net/http 包的路由功能, 這些框架提供了更多高級功能, 使用更加靈活方便。這裏介紹兩個流行的第三方路由框架。

Gorilla mux 的用法及特點: Gorilla mux 是 Go 語言中最流行的請求路由框架之一。它在 net/http 基礎上, 提供了強大的路由匹配與派發功能。特點包括:

  • 功能強大的路由匹配: 支持變量、通配符、正則表達式等多種方式的路由模式。

  • 優雅的 URL 構建 API: 可以通過代碼構建複雜的 URL, 而不需要自己拼接。

  • 中間件支持: 支持任意攔截器和中間件。實現諸如日誌、身份驗證等功能。

  • 完全兼容 net/http。

下面是一個 Gorilla mux 的基本用法示例:

import (
    "github.com/gorilla/mux"
)
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/products/{key}", ProductHandler)
    r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
    // 變量路由
    r.HandleFunc("/user/{userId}/{userName}", UserHandler)
    // 正則表達式路由
    r.HandleFunc("/articles/{category:[a-z]+}/", ArticlesCategoryRegHandler)
    http.ListenAndServe(":8080", r)
}

用以上代碼可以看出,Gorilla mux 通過聲明方式定義路由, 使得代碼結構更加清晰。各種佔位符、正則表達式也使得 URL 規則更加靈活。

Chi 的用法及特點: Chi 是另一個比較流行的 Go 語言路由框架。它採用中間件的模式, 提供了很多額外的路由功能。

  • 支持鏈式路由定義方式, 語法更清晰。

  • 內置強大的中間件生態。

  • 支持通配、正則等路由方式。

  • 性能高效。

下面是一個使用 Chi 構建服務的示例:

import (
  "github.com/go-chi/chi"
  "github.com/go-chi/chi/middleware"
)
func main() {
  r := chi.NewRouter()
  // 中間件,日誌、錯誤處理等
  r.Use(middleware.Logger) 
  r.Use(middleware.Recoverer)
  r.Get("/", HomePageHandler)
  r.Route("/users", func(r chi.Router) {
    r.Get("/", UserListHandler) // 匹配 /users
    r.Get("/{userId}", UserGetHandler) // 匹配 /users/{userId}  
  })
  http.ListenAndServe(":3000", r)
  // chi 用 chain 方式處理請求,中間件模式在其中發揮得淋漓盡致。
}

各框架對比: Gorilla mux 和 chi 都是在 net/http 標準路由功能上的擴展, 它們有以下主要區別:

  • Gorilla mux 使用聲明式語法定義路由, chi 採用中間件鏈式處理。

  • Gorilla mux 功能更豐富, chi 性能更高。

  • Gorilla mux 社區更加活躍。

所以, 若要求功能強大, Gorilla mux 是一個不錯的選擇; 如果要求性能和鏈式處理, chi 更勝一籌。

4. 定義路由的最佳實踐

良好的路由設計有助於構建健壯、易用、易擴展的系統。將介紹請求路由的兩個最佳實踐。

RESTful API 設計: REST 架構風格提倡使用統一的接口風格設計 API, 使其更簡潔、靈活、易理解。最常見的設計就是通過 HTTP 動詞區分對資源的不同操作類型。如下表所示:

eTYrtr

在 Go 語言中實現這樣的 API 也非常簡單。以一個用戶管理 API 爲例:

func main() {
  r := mux.NewRouter()
  r.HandleFunc("/users", getUsers).Methods("GET")
  r.HandleFunc("/users/{userId}", createUser).Methods("POST")
  r.HandleFunc("/users/{userId}", updateUser).Methods("PUT")
  r.HandleFunc("/users/{userId}", deleteUser).Methods("DELETE")
  log.Fatal(http.ListenAndServe(":8000", r))
}

用 Go 語言的請求路由功能, 輕鬆構建符合 REST 風格。

優雅的代碼結構: Go 語言中比較推薦的項目佈局方式是按功能將代碼分組到內部包中。這種方式也適用於組織路由代碼。下面是一個按功能分組的目錄結構示例:

├── main.go
├── route.go
├── router/
|   ├── blog.go
|   └── user.go
├── handlers/
|   ├── blog.go 
|   └── user.go
└── middlewares/
    ├── auth.go
    └── logging.go

在這樣的佈局下, 可以在 route.go 中構建路由器; router 包中按功能定義路由規則; handlers 包中實現請求處理; middlewares 包實現中間件。這樣可以使各個功能組件解耦, 也便於代碼維護。

main.go 中可以如下初始化:

func main() {
    router := NewRouter()
    // 加載user路由
    user.Load(router)
    // 加載blog路由
    blog.Load(router)
    // 使用日誌、認證中間件
    router.Use(middlewares.Logging, middlewares.Auth)
    http.ListenAndServe(":8000", router)
}

5. 性能優化手段

對 Go 語言 Web 服務來說, 請求路由性能是關鍵的性能優化點。主要可以從兩個方面進行優化:

優化請求匹配: 請求路由中的模式匹配是性能的關鍵點。可以從編寫高效正則, 最小化回溯次數等方面進行優化。一些框架也使用了並行匹配、預排序、cache 等機制提高性能。

併發連接處理: Go 語言得天獨厚的併發支持也應該在請求路由中得以利用。常見的做法是爲不同路由分組使用不同的 ServeMux 實例。並使用 Goroutine 併發處理它們

func main() {
   apiRouter := mux.NewRouter()
   webRouter := mux.NewRouter() 
   // 爲兩個路由器分別處理請求
   go http.ListenAndServe(":8080", apiRouter)
   http.ListenAndServe(":8000", webRouter)
}

6. 總結

Go 語言實現 Web 請求路由的特點

  • 語言原生支持路由功能, 使用簡單。

  • 支持高度定製化的路由實現。

  • 第三方路由框架功能強大。

  • 高併發處理能力。

Go 方式 vs 傳統方式

  • Go 語言通過 interface 實現靈活機制。類似 node.js 中間件模式。

  • 性能接近 Nginx, 併發能力強。

  • 更容易實現微服務架構。

Go 語言利用自身優勢, 提供了簡單卻強大的 Web 請求路由實現。

同時豐富的設計模式使得 Go 路由功能可擴展性強, 容易實現高性能服務。

未來隨着框架的成熟, Go 語言在後端服務端會有越來越廣闊的應用前景。

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