在講述框架之前,先來說說Go語言的內建net/http包,其實net/http已經為我們提供了基礎的路由函式組合和豐富的功能函式,如果你只是需要簡單的API編寫,net/http就完全足夠了。
下面就來編寫一個最簡單的web服務程式:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 該方法接收一個路由匹配的字串,以及一個 func(ResponseWriter, *Request) 型別的函式
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8000", nil)) // 監聽本地8000埠
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) // r.URL.Path 輸出url的路徑
}
瀏覽器訪問結果:
來進階一下,看看如何解析常見的請求引數型別,以及如何返回json格式。
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
func main() {
// 該方法接收一個路由匹配的字串,以及一個 func(ResponseWriter, *Request) 型別的函式
http.HandleFunc("/", handler)
http.HandleFunc("/get", handleGet)
http.HandleFunc("/postJson", handlePostJson)
http.HandleFunc("/postForm", handlePostForm)
http.HandleFunc("/responseJson", handleResponseJson)
log.Fatal(http.ListenAndServe(":8000", nil)) // 監聽本地8000埠
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path) // r.URL.Path 輸出url的路徑
}
// 處理GET請求查詢引數
func handleGet(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
id := query.Get("id")
fmt.Fprintf(w, "GET: id=%s\n", id)
}
// 處理application/json型別的POST請求
func handlePostJson(w http.ResponseWriter, r *http.Request) {
// 根據請求body建立一個json解析器例項
decoder := json.NewDecoder(r.Body)
// 用於存放引數key=value資料
var params map[string]string
// 解析引數 存入map
decoder.Decode(¶ms)
fmt.Fprintf(w, "POST json: username=%s, password=%s\n", params["username"], params["password"])
}
// 處理application/x-www-form-urlencoded型別的POST請求
func handlePostForm(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username := r.Form.Get("username")
password := r.Form.Get("password")
fmt.Fprintf(w, "POST form-urlencoded: username=%s, password=%s\n", username, password)
}
// 返回JSON資料格式
func handleResponseJson(w http.ResponseWriter, r *http.Request) {
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
res := Response{
200,
"success",
"admin",
}
json.NewEncoder(w).Encode(res) // 關鍵
}
/get
請求結果:/postJson
請求結果:/postForm
請求結果:/responseJson
請求結果:
到這裡,基本的使用就已經介紹的差不多了,關於更多net/http
標準庫的內容可以直接檢視標準庫文件:studygolang.com/pkgdoc
引入官方的介紹:
Gin 是一個用 Go (Golang) 編寫的 HTTP web 框架。 它是一個類似於 martini 但擁有更好效能的 API 框架,由於 httprouter
,速度提高了近 40 倍。如果你需要極好的效能,使用 Gin 吧。
特性:
- 快速:路由不使用反射,基於Radix樹,記憶體佔用少。
- 中介軟體:HTTP請求,可先經過一系列中介軟體處理,例如:Logger,Authorization,GZIP等。這個特性和 NodeJs 的
Koa
框架很像。中介軟體機制也極大地提高了框架的可擴充套件性。
- 異常處理:服務始終可用,不會當機。Gin 可以捕獲 panic,並恢復。而且有極為便利的機制處理HTTP請求過程中發生的錯誤。
- JSON:Gin可以解析並驗證請求的JSON。這個特性對
Restful API
的開發尤其有用。
- 路由分組:例如將需要授權和不需要授權的API分組,不同版本的API分組。而且分組可巢狀,且效能不受影響。
- 渲染內建:原生支援JSON,XML和HTML的渲染。
1.快速開始
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // 預設監聽本地8080埠,如果需要更改可以使用 r.Run(":9000")
}
瀏覽器訪問:
控制檯輸出:
在這段程式碼中gin做了什麼?
- 首先,我們使用了
gin.Default()
生成了一個例項,賦值給r
。 - 接下來,我們使用
r.Get("/ping", ...)
宣告瞭一個路由,告訴 Gin 什麼樣的URL 能觸發傳入的函式,這個函式返回我們想要顯示在使用者瀏覽器中的資訊。 - 最後用
r.Run()
函式來讓應用執行在本地伺服器上,預設監聽埠是 8080,可以傳入引數設定埠,例如r.Run(":9999")
即執行在9999埠。
2.路由
路由方法有 GET
, POST
, PUT
, PATCH
, DELETE
和 OPTIONS
,還有Any
,可匹配以上任意型別的請求。
r.GET("/someGet", func)
r.POST("/somePost", func)
r.PUT("/somePut", func)
r.DELETE("/someDelete", func)
r.PATCH("/somePatch", func)
r.HEAD("/someHead", func)
r.OPTIONS("/someOptions", func)
//處理所有的請求方法
r.Any("/any", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"請求型別": c.Request.Method,
})
})
路由引數
// 無引數
r.GET("/", func (c *gin.Context) {
c.String(http.StatusOK, "Hello")
})
// 路徑引數,匹配 /path/admin
r.GET("/path/:name", func(c *gin.Context) {
name := c.Param("name")//取得URL路徑中引數name的值
c.String(http.StatusOK, "Hello %s", name)
})
// 星號路由引數,匹配所有,不建議使用,如/all/*id
r.GET("/all/*id", func(c *gin.Context) {
id := c.Param("id")
c.String(http.StatusOK, "id is %s", id)
})
// 查詢引數,匹配 user?name=xxx&role=xxx,role可選
r.GET("/user", func(c *gin.Context) {
name := c.Query("name")//查詢請求URL後面的引數name的值
role := c.DefaultQuery("role", "teacher")//如果獲取不到,會賦值預設值"teacher"
c.String(http.StatusOK, "%s is a %s", name, role)
})
// form表單
r.POST("/form", func(c *gin.Context) {
username := c.PostForm("username")
password := c.DefaultPostForm("password", "000000") // 可設定預設值
c.JSON(http.StatusOK, gin.H{
"username": username,
"password": password,
})
})
// json引數
r.POST("/json", func(c *gin.Context) {
type Body struct {
Email string `json:"email"`
Username string `json:"username"`
}
var body Body
c.ShouldBind(&body)//繫結引數,將引數解析到body結構體中
c.JSON(http.StatusOK, body)
})
// 陣列引數,匹配多選業務如 array?answer=xxx&answer=xxx&answer=xxx,key一樣,value不同
r.GET("/array", func(c *gin.Context) {
array := c.QueryArray("answer")
c.JSON(http.StatusOK, array)
})
// map引數,字典引數,匹配 map?ids[a]=123&ids[b]=456&ids[c]=789
r.GET("/map", func(c *gin.Context) {
c.JSON(http.StatusOK, c.QueryMap("ids"))
})
路由分組
v1Group := r.Group("/v1")
{
v1Group.GET("/user", func(c *gin.Context) {
c.String(200, "這是v1版本/v1/user")
})
}
v2Group := r.Group("/v2")
{
v2Group.GET("/user", func(c *gin.Context) {
c.String(200, "這是v2版本/v2/user")
})
}
3.輸出渲染格式
gin可以很方便的渲染輸出資料的格式
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/someString", func(c *gin.Context) {
c.String(http.StatusOK, "string")
})
// gin.H 是 map[string]interface{} 的一種快捷方式
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.GET("/moreJSON", func(c *gin.Context) {
// 你也可以使用一個結構體
var msg struct {
Name string `json:"user"`
Message string
Number int
}
msg.Name = "Lena"
msg.Message = "hey"
msg.Number = 123
// 注意由於`json:"user"`的關係 msg.Name 在 JSON 中變成了 "user"
// 將輸出:{"user": "Lena", "Message": "hey", "Number": 123}
c.JSON(http.StatusOK, msg)
})
r.GET("/someXML", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
})
r.Run()
}
4.中介軟體
回到快速開始中,我們使用gin.Default()
生成了一個例項,檢視gin.Default()
的原始碼,可以發現Default
函式會預設繫結兩個已經準備好的中介軟體,Logger
和 Recovery
,分別幫助我們列印日誌輸出和painc處理。
從中可以看到,Gin的中介軟體是通過Use
方法設定的,它接收一個可變引數,所以我們同時可以設定多箇中介軟體。
而一個Gin的中介軟體,其實就是Gin定義的一個HandlerFunc
它在我們Gin中經常使用,比如:
// 其中func(c *gin.Context){} 就是一個HandlerFunc型別函式
r.GET("/", func(c *gin.Context) {
c.String(200, "hello")
})
我們現在來嘗試如何使用中介軟體:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// 建立一個不包含中介軟體的路由器例項
r := gin.New()
// 全域性使用中介軟體
// 使用gin自帶的 Logger 日誌中介軟體
r.Use(gin.Logger())
// 使用gin自帶的 Recovery 中介軟體從任何 panic 恢復,如果出現 panic,它會寫一個 500 錯誤。
r.Use(gin.Recovery())
// 以上程式碼相當於 r := gin.Default()
// 新增自定義的全域性中介軟體
r.Use(middleware.Cors())
// 單個路由新增中介軟體,可以新增任意多個
r.GET("/path", middleware.JWT())
// 路由組中新增中介軟體,中介軟體只在該路由組中產生作用
// user := r.Group("/user", middleware.AuthRequired())
user := r.Group("/user")
user.Use(middleware.AuthRequired())
{
user.POST("/login", Login)
}
r.Run()
}
學會使用中介軟體後,如何自定義一個自己的中介軟體呢?很簡單,比如想要自定義一個計算請求時間的requestTime
中介軟體:
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(requestTime())
r.GET("/", func(c *gin.Context) {
fmt.Println("我到這裡了 hello")
c.String(200, "hello")
})
r.Run()
}
func requestTime() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now() // 記錄開始時間
fmt.Println("這裡被我攔截住了")
fmt.Println("因為呼叫了c.Next() 我現在要走了")
c.Next() // 立即呼叫下一個HandlerFunc(會產生呼叫耗時)
fmt.Println("我又回來了")
fmt.Println(time.Since(start)) // 列印本次請求處理時間差
}
}
可以看到c.Next()
在這裡發揮了很大的作用,其中在c.Next()
之前的操作我們一般用來做驗證處理,訪問是否允許之類的。 之後的操作一般就是用來做總結處理,比如格式化輸出、響應結束時間,響應時長計算之類的。
在gin中介軟體中,除了c.Next()
,相對應的還有c.Abort()
,而c.Abort()
的作用則是阻止後續的處理函式,比如檢驗到是非法請求時,阻斷接下來的操作。
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(authority())
r.GET("/path/:name", func(c *gin.Context) {
fmt.Println("歡迎")
c.String(http.StatusOK, "Hello %s", c.Param("name"))
})
r.Run()
}
func authority() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("開始-許可權控制")
isAdmin := c.Param("name") == "admin"
if isAdmin {
c.Next()
} else {
c.Abort() //不會再執行接下去的HandlerFunc了
c.String(http.StatusOK, "對不起,您不是管理員")
}
fmt.Println("結束-許可權控制")
}
}
情況①:
情況②:
程式在呼叫了c.Abort()
之後,就只會繼續往下執行中介軟體的程式碼,不會再跳到我們定義的介面HandlerFunc去了,其實就是起到了攔截的作用。
總結一下c.Next()
作用是立即執行下一個HandlerFunc,完後會跳轉回來繼續執行c.Next()
接下去的程式碼,
而c.Abort()
則是阻斷執行下一個HandlerFunc,僅會執行接下去的程式碼,常用於許可權控制攔截操作。
有時候我們還需要跨中介軟體取值,可以使用c.Set(key, value)
設定值,使用c.Get(key)
取值
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(authority())
r.GET("/", func(c *gin.Context) {
value, ok := c.Get("key")
if ok {
fmt.Println(value)
}
c.String(http.StatusOK, "Hello")
})
r.Run()
}
func authority() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("key", "你好")
c.Next()
}
}
請求結果:
基本介紹到這裡,關於gin的文件可以參考:《Gin 框架中文文件》
我的開源專案
github.com/togettoyou/go-one-serve...
基於Gin進行快速構建RESTful API 服務的專案模板
本作品採用《CC 協議》,轉載必須註明作者和本文連結