目錄
- 簡介
- 生態(tài)框架
- 安裝配置
- 入門指南
簡介
其實對于golang而言,web框架的依賴要遠(yuǎn)比Python,Java之類的要小。自身的net/http足夠簡單,性能也非常不錯??蚣芨袷且恍┏S煤瘮?shù)或者工具的集合。借助框架開發(fā),不僅可以省去很多常用的封裝帶來的時間,也有助于團(tuán)隊的編碼風(fēng)格和形成規(guī)范。
Gin 是一個基于 Go 語言編寫的 Web 框架,比近似框架 martini 擁有更好的性能,借助高性能的 httprouter,速度提升了近 40 倍。詳細(xì)介紹
gin框架應(yīng)用
- gorush:Go 編寫的通知推送服務(wù)器。
- fnproject:容器原生,云 serverless 平臺。
- photoprism:基于 Go 和 Google TensorFlow 實現(xiàn)的個人照片管理工具。
- krakend:擁有中間件的超高性能 API 網(wǎng)關(guān)。
- picfit:Go 編寫的圖像尺寸調(diào)整服務(wù)器。
- gotify:基于 WebSocket 進(jìn)行實時消息收發(fā)的簡單服務(wù)器。
- cds:企業(yè)級持續(xù)交付和 DevOps 自動化開源平臺。
安裝配置
- 添加環(huán)境變量
GO111MODULE on
GOPROXY https://goproxy.cn
GOROOT 是你安裝go的路徑
- 安裝
go get -u github.com/gin-gonic/gin
- 高性能日志庫zap安裝
go get -u go.uber.org/zap
基礎(chǔ)編程
- 啟動默認(rèn)引擎(端口8080)
- middleware應(yīng)用
- context的使用
package main
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"math/rand"
"time"
)
const keyRequestId = "requestId"
func main() {
//默認(rèn)服務(wù)端引擎
//http://localhost:8080/ping
//自動帶有日志模塊
r := gin.Default()
log,err := zap.NewProduction()
if err != nil{
panic(err)
}
//使用middleware,接入zap日志模塊
r.Use(func(c *gin.Context) {
//path,response code,log latency,
start := time.Now()
c.Next()
log.Info("incoming request",
zap.String("path",c.Request.URL.Path),
zap.Int("status",c.Writer.Status()),
zap.Duration("elapsed", time.Now().Sub(start)),
)
},
//另外一個use
//入口,增加requestId
func(c *gin.Context) {
c.Set(keyRequestId,rand.Int())
c.Next()
})
r.GET("/ping", func(c *gin.Context) {
h := gin.H{"message":"pong",}
if rid,exist := c.Get(keyRequestId); exist{
h[keyRequestId]=rid
}
c.JSON(200,h)
})
r.GET("/hello", func(c *gin.Context) {
c.String(200,"hello")
})
//默認(rèn)8080端口
r.Run()
}
restful路由
gin的路由來自httprouter庫。除“不支持路由正則表達(dá)式"之外,httprouter具有的功能,gin也具有:
func main(){
router := gin.Default()
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
}
- 冒號 :
冒號:加上一個參數(shù)名組成路由參數(shù)??梢允褂?code>c.Params的方法讀取其值。這個值必須是字串string。諸如/user/rsj217,和user/hello都可以匹配,而/user/和/user/rsj217/不會被匹配。
下例中name代表":"的變量
func main(){
router := gin.Default()
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
}
- 星號:*
除了:,gin還提供了號處理參數(shù),號能匹配的規(guī)則就更多。
下例中action代表"*" 的變量
func main(){
router := gin.Default()
router.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
message := name + " is " + action
c.String(http.StatusOK, message)
})
}
$ curl http://127.0.0.1:8000/user/carmen/
carmen is /%
--
$ curl http://127.0.0.1:8000/user/carmen/中國
carmen is /中國%
文件上傳
- 上傳單個文件
func main(){
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
name := c.PostForm("name")
fmt.Println(name)
file, header, err := c.Request.FormFile("upload")
if err != nil {
c.String(http.StatusBadRequest, "Bad request")
return
}
filename := header.Filename
fmt.Println(file, err, filename)
out, err := os.Create(filename)
if err != nil {
log.Fatal(err)
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
log.Fatal(err)
}
c.String(http.StatusCreated, "upload successful")
})
router.Run(":8000")
}
- 上傳多個文件
router.POST("/multi/upload", func(c *gin.Context) {
err := c.Request.ParseMultipartForm(200000)
if err != nil {
log.Fatal(err)
}
formdata := c.Request.MultipartForm
files := formdata.File["upload"]
for i, _ := range files { /
file, err := files[i].Open()
defer file.Close()
if err != nil {
log.Fatal(err)
}
out, err := os.Create(files[i].Filename)
defer out.Close()
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(out, file)
if err != nil {
log.Fatal(err)
}
c.String(http.StatusCreated, "upload successful")
}
})
表單上傳與gin的render模板
- 表單如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
</head>
<body>
<h3>Single Upload</h3>
<form action="/upload", method="post" enctype="multipart/form-data">
<input type="text" value="hello gin" />
<input type="file" name="upload" />
<input type="submit" value="upload" />
</form>
<h3>Multi Upload</h3>
<form action="/multi/upload", method="post" enctype="multipart/form-data">
<input type="text" value="hello gin" />
<input type="file" name="upload" />
<input type="file" name="upload" />
<input type="submit" value="upload" />
</form>
</body>
</html>
- 后端代碼
type User struct {
Username string `form:"username" json:"username" binding:"required"`
Passwd string `form:"passwd" json:"passwd" bdinding:"required"`
Age int `form:"age" json:"age"`
}
func main(){
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
var user User
var err error
contentType := c.Request.Header.Get("Content-Type")
switch contentType {
case "application/json":
err = c.BindJSON(&user)
case "application/x-www-form-urlencoded":
err = c.BindWith(&user, binding.Form)
}
if err != nil {
fmt.Println(err)
log.Fatal(err)
}
c.JSON(http.StatusOK, gin.H{
"user": user.Username,
"passwd": user.Passwd,
"age": user.Age,
})
})
}
middleware中間件
- golang的net/http設(shè)計的一大特點就是特別容易構(gòu)建中間件。gin也提供了類似的中間件。
- 中間件分為全局中間件,單個路由中間件和群組中間件。
- 對于分組路由,嵌套使用中間件,可以限定中間件的作用范圍。
- 需要注意的是中間件只對注冊過的路由函數(shù)起作用。
中間件實例
- 鑒權(quán)
router.GET("/auth/signin", func(c *gin.Context) {
cookie := &http.Cookie{
Name: "session_id",
Value: "123",
Path: "/",
HttpOnly: true,
}
http.SetCookie(c.Writer, cookie)
c.String(http.StatusOK, "Login successful")
})
router.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": "home"})
})
登錄函數(shù)會設(shè)置一個session_id的cookie,注意這里需要指定path為/,不然gin會自動設(shè)置cookie的path為/auth,一個特別奇怪的問題。/home的邏輯很簡單,使用中間件AuthMiddleWare注冊之后,將會先執(zhí)行AuthMiddleWare的邏輯,然后才到/home的邏輯。
func AuthMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
if cookie, err := c.Request.Cookie("session_id"); err == nil {
value := cookie.Value
fmt.Println(value)
if value == "123" {
c.Next()
return
}
}
c.JSON(http.StatusUnauthorized, gin.H{
"error": "Unauthorized",
})
c.Abort()
return
}
}