淺析Golang中Gin框架存在的必要性
1. 簡介
在Go語言中,net/http
包提供了一個強大且靈活的標準HTTP庫,可以用來構建Web應用程序和處理HTTP請求。這個包是Go語言標準庫的一部分,因此所有的Go程序都可以直接使用它。既然已經(jīng)有 net/http
這樣強大和靈活的標準庫,為什么還出現(xiàn)了像 Gin
這樣的,方便我們構建Web應用程序的第三方庫?
其實在于net/http
的定位,其提供了基本的HTTP功能,但它的設計目標是簡單和通用性,而不是提供高級特性和便利的開發(fā)體驗。在處理HTTP請求和構建Web應用時,可能會遇到一系列的問題,這也造就了Gin
這樣的第三方庫的出現(xiàn)。
下文我們將對一系列場景的介紹,通過比對 net/http
和 Gin
二者在這些場景下的不同實現(xiàn),進而說明Gin
框架存在的必要性。
2. 復雜路由場景處理
在實際的Web應用程序開發(fā)中,使用同一個路由前綴的場景非常普遍,這里舉兩個比較常見的例子。
比如在設計API時,可能會隨著時間的推移對API進行更新和改進。為了保持向后兼容性,并允許多個API版本共存,通常會使用類似 /v1
、/v2
這樣的路由前綴來區(qū)分不同版本的API。
還有另外一個場景,一個大型Web應用程序經(jīng)常是由多個模塊組成,每個模塊負責不同的功能。為了更好地組織代碼和區(qū)分不同模塊的路由,經(jīng)常都是使用模塊名作為路由前綴。
在這兩個場景中,大概率都會使用同一個路由前綴。如果使用net/http
來框架web應用,實現(xiàn)大概如下:
package main import ( "fmt" "net/http" ) func handleUsersV1(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "User list in v1") } func handlePostsV1(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Post list in v1") } func main() { http.HandleFunc("/v1/users", handleUsersV1) http.HandleFunc("/v1/posts", handlePostsV1) http.ListenAndServe(":8080", nil) }
在上面的示例中,我們手動使用 http.HandleFunc
來定義不同的路由處理函數(shù)。
代碼示例看起來沒有太大問題,但是是因為只有兩個路由組,如果隨著路由數(shù)量增加,處理函數(shù)的數(shù)量也會增加,代碼會變得越來越復雜和冗長。而且每一個路由規(guī)則都需要手動設置路由前綴,如例子中的 v1
前綴,如果前綴是 /v1/v2/...
這樣子設置起來,會導致代碼架構不清晰,同時操作繁雜,容易出錯。
但是相比之下,Gin
框架實現(xiàn)了路由分組的功能,下面來看Gin
框架來對該功能的實現(xiàn):
package main import ( "fmt" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // 創(chuàng)建一個路由組 v1 := router.Group("/v1") { v1.GET("/users", func(c *gin.Context) { c.String(200, "User list in v1") }) v1.GET("/posts", func(c *gin.Context) { c.String(200, "Post list in v1") }) } router.Run(":8080") }
在上面的例子中,通過router.Group
創(chuàng)建了一個v1
路由前綴的路由組,我們設置路由規(guī)則時,不需要再設置路由前綴,框架會自動幫我們組裝好。
同時,相同路由前綴的規(guī)則,也在同一個代碼塊里進行維護。 相比于 net/http
代碼庫,Gin
使得代碼結構更清晰、更易于管理。
3. 中間件處理
在web應用請求處理過程中,除了執(zhí)行具體的業(yè)務邏輯之外,往往需要在這之前執(zhí)行一些通用的邏輯,比如鑒權操作,錯誤處理或者是日志打印功能,這些邏輯我們統(tǒng)稱為中間件處理邏輯,而且往往是必不可少的。
首先對于錯誤處理,在應用程序的執(zhí)行過程中,可能會發(fā)生一些內部錯誤,如數(shù)據(jù)庫連接失敗、文件讀取錯誤等。合理的錯誤處理可以避免這些錯誤導致整個應用崩潰,而是通過適當?shù)腻e誤響應告知客戶端。
對于鑒權操作,在許多web處理場景中,經(jīng)常都是用戶認證之后,才能訪問某些受限資源或執(zhí)行某些操作。同時鑒權操作還可以限制用戶的權限,避免用戶有未經(jīng)授權的訪問,這有助于提高程序的安全性。
因此,一個完整的HTTP請求處理邏輯,是極有可能需要這些中間件處理邏輯的。而且理論上框架或者類庫應該有對中間件邏輯的支持。下面先來看看 net/http
能怎么去實現(xiàn):
package main import ( "fmt" "log" "net/http" ) // 錯誤處理中間件 func errorHandler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) log.Printf("Panic: %v", err) } }() next.ServeHTTP(w, r) }) } // 認證鑒權中間件 func authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 模擬身份驗證 if r.Header.Get("Authorization") != "secret" { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) } // 處理業(yè)務邏輯 func helloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World!") } // 另外 func anotherHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Another endpoint") } func main() { // 創(chuàng)建路由處理器 router := http.NewServeMux() // 應用中間件, 注冊處理器 handler := errorHandler(authMiddleware(http.HandlerFunc(helloHandler))) router.Handle("/", handler) // 應用中間件, 注冊另外一個請求的處理器 another := errorHandler(authMiddleware(http.HandlerFunc(anotherHandler))) router.Handle("/another", another) // 啟動服務器 http.ListenAndServe(":8080", router) }
在上述示例中,我們在net/http
中通過errorHandler
和 authMiddleware
兩個中間件實現(xiàn)了錯誤處理和鑒權功能。 接下來我們查看示例代碼的第49行,可以發(fā)現(xiàn)代碼通過裝飾者模式,給原本的處理器增加了錯誤處理和鑒權操作功能。
這段代碼的實現(xiàn)的優(yōu)點,是通過裝飾者模式,對多個處理函數(shù)進行組合,形成處理器鏈,實現(xiàn)了錯誤處理和認證鑒權功能。而不需要在每個處理函數(shù)handler
中去加上這部分邏輯,這使得代碼具備更高的可讀性和可維護性。
但是這里也存在著一個很明顯的缺點,這個功能并不是框架給我們提供的,而是我們自己實現(xiàn)的。我們每新增一個處理函數(shù)handler
, 都需要對這個handler
進行裝飾,為其增加錯誤處理和鑒權操作,這在增加我們負擔的同時,也容易出錯。同時需求也是不斷變化的,有可能部分請求只需要錯誤處理了,一部分請求只需要鑒權操作,一部分請求既需要錯誤處理也需要鑒權操作,基于這個代碼結構,其會變得越來越難維護。
相比之下,Gin
框架提供了一種更靈活的方式來啟用和禁用中間件邏輯,能針對某個路由組進行設置,而不需要對每個路由規(guī)則單獨設置,下面展示下示例代碼:
package main import ( "github.com/gin-gonic/gin" ) func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // 模擬身份驗證 if c.GetHeader("Authorization") != "secret" { c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) return } c.Next() } } func main() { router := gin.Default() // 全局添加 Logger 和 Recovery 中間件 // 創(chuàng)建一個路由組,該組中的所有路由都會應用 authMiddleware 中間件 authenticated := router.Group("/") authenticated.Use(authMiddleware()) { authenticated.GET("/hello", func(c *gin.Context) { c.String(200, "Hello, World!") }) authenticated.GET("/private", func(c *gin.Context) { c.String(200, "Private data") }) } // 不在路由組中,因此沒有應用 authMiddleware 中間件 router.GET("/welcome", func(c *gin.Context) { c.String(200, "Welcome!") }) router.Run(":8080") }
在上述示例中,我們通過router.Group("/")
創(chuàng)建了一個名為 authenticated
的路由組,然后使用 Use
方法,給該路由組啟用 authMiddleware
中間件。在這路由組下所有的路由規(guī)則,都會自動執(zhí)行authMiddleware
實現(xiàn)的鑒權操作。
相對于net/http
的優(yōu)點,首先是不需要對每個handler
進行裝飾,增加中間件邏輯,用戶只需要專注于業(yè)務邏輯的開發(fā)即可,減輕了負擔。
其次可維護性更高了,如果業(yè)務需要不再需要進行鑒權操作,gin
只需要刪除掉Use
方法的調用,而net/http
則需要對所有handler
的裝飾操作進行處理,刪除掉裝飾者節(jié)點中的鑒權操作節(jié)點,工作量相對于gin
來說非常大,同時也容易出錯。
最后,gin
在處理不同部分的請求需要使用不同中間件的場景下,更為靈活,實現(xiàn)起來也更為簡單。比如 一部分請求需要鑒權操作,一部分請求需要錯誤里處理,還有一部分既需要錯誤處理,也需要鑒權操作。這種場景下,只需要通過gin
創(chuàng)建三個路由組router
, 然后不同的路由組分別調用 Use
方法啟用不同的中間件,即可實現(xiàn)需求了,這相對于net/http
更為靈活和可維護。
這也是為什么有net/http
的前提下,還出現(xiàn)了gin
框架的重要原因之一。
4. 數(shù)據(jù)綁定
在處理HTTP請求時,比較常見的功能,是將請求中的數(shù)據(jù)自動綁定到結構體當中。下面以一個表單數(shù)據(jù)為例,如果使用net/http
,如何將數(shù)據(jù)綁定到結構體當中:
package main import ( "fmt" "log" "net/http" ) type User struct { Name string `json:"name"` Email string `json:"email"` } func handleFormSubmit(w http.ResponseWriter, r *http.Request) { var user User // 將表單數(shù)據(jù)綁定到 User 結構體 user.Name = r.FormValue("name") user.Email = r.FormValue("email") // 處理用戶數(shù)據(jù) fmt.Fprintf(w, "用戶已創(chuàng)建:%s (%s)", user.Name, user.Email) } func main() { http.HandleFunc("/createUser", handleFormSubmit) http.ListenAndServe(":8080", nil) }
我們需要調用FormValue
方法,一個一個得從表單中讀取出數(shù)據(jù),然后設置到結構體當中。而且在字段比較多的情況下,我們很有可能漏掉其中的某些字段,導致后續(xù)處理邏輯出現(xiàn)問題。而且每個字段都需要我們手動讀取設置,也很影響我們的開發(fā)效率。
下面我們來看看Gin
是如何讀取表單數(shù)據(jù),將其設置到結構體當中的:
package main import ( "fmt" "github.com/gin-gonic/gin" ) type User struct { Name string `json:"name"` Email string `json:"email"` } func handleFormSubmit(c *gin.Context) { var user User // 將表單數(shù)據(jù)綁定到 User 結構體 err := c.ShouldBind(&user) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "無效的表單數(shù)據(jù)"}) return } // 處理用戶數(shù)據(jù) c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("用戶已創(chuàng)建:%s (%s)", user.Name, user.Email)}) } func main() { router := gin.Default() router.POST("/createUser", handleFormSubmit) router.Run(":8080") }
看上面示例代碼的第17行,可以看到直接調用ShouldBind
函數(shù),便可以自動將表單的數(shù)據(jù)自動映射到結構體當中,不再需要一個一個字段讀取,然后再單獨設置到結構體當中。
相比于使用net/http
, gin
框架在數(shù)據(jù)綁定方面更為方便,同時也不容易出錯。gin
提供了各種 api
, 能夠將各種類型的數(shù)據(jù)映射到結構體當中,用戶只需要調用對應的 api
即可。而net/http
則未提供相對應的操作,需要用戶讀取數(shù)據(jù),然后手動設置到結構體當中。
5. 總結
在Go語言中, net/http
提供了基本的HTTP功能,但它的設計目標是簡單和通用性,而不是提供高級特性和便利的開發(fā)體驗。在處理HTTP請求和構建Web應用時,處理復雜的路由規(guī)則時,會顯得力不從心;同時對于一些公共操作,比如日志記錄,錯誤處理等,很難做到可插拔設計;想要將請求數(shù)據(jù)綁定到結構體中,net/http
也沒有提供一些簡易的操作,都是需要用戶手動去實現(xiàn)的。
這就是為什么出現(xiàn)了像 Gin
這樣的第三方庫,其是一個構建在 net/http
之上,旨在簡化和加速Web應用程序的開發(fā)。
總的來說,Gin
可以幫助開發(fā)者更高效地構建Web應用程序,提供了更好的開發(fā)體驗和更豐富的功能。當然,選擇使用 net/http
還是 Gin 取決于項目的規(guī)模、需求和個人喜好。對于簡單的小型項目,net/http
可能已經(jīng)足夠,但對于復雜的應用程序,Gin 可能會更適合。
到此這篇關于淺析Golang中Gin框架存在的必要性的文章就介紹到這了,更多相關Golang Gin內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
golang使用 gomodule 在公共測試環(huán)境管理go的依賴的實例詳解
這篇文章主要介紹了golang使用 gomodule 在公共測試環(huán)境管理go的依賴,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11