目录
- 简介
- 生态框架
- 安装配置
- 入门指南
简介
其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。
Gin 是一个基于 Go 语言编写的 Web 框架,比近似框架 martini 拥有更好的性能,借助高性能的 httprouter,速度提升了近 40 倍。详细介绍
gin框架应用
- gorush:Go 编写的通知推送服务器。
- fnproject:容器原生,云 serverless 平台。
- photoprism:基于 Go 和 Google TensorFlow 实现的个人照片管理工具。
- krakend:拥有中间件的超高性能 API 网关。
- picfit:Go 编写的图像尺寸调整服务器。
- gotify:基于 WebSocket 进行实时消息收发的简单服务器。
- cds:企业级持续交付和 DevOps 自动化开源平台。
安装配置
- 添加环境变量
GO111MODULE on
GOPROXY https://goproxy.cn
GOROOT 是你安装go的路径
- 安装
go get -u github.com/gin-gonic/gin
- 高性能日志库zap安装
go get -u go.uber.org/zap
基础编程
- 启动默认引擎(端口8080)
- middleware应用
- context的使用
package main
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"math/rand"
"time"
)
const keyRequestId = "requestId"
func main() {
//默认服务端引擎
//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")
})
//默认8080端口
r.Run()
}
restful路由
gin的路由来自httprouter库。除“不支持路由正则表达式"之外,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)
})
}
- 冒号 :
冒号:加上一个参数名组成路由参数。可以使用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还提供了*号处理参数,*号能匹配的规则就更多。
下例中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设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。
- 中间件分为全局中间件,单个路由中间件和群组中间件。
- 对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。
- 需要注意的是中间件只对注册过的路由函数起作用。
中间件实例
- 鉴权
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"})
})
登录函数会设置一个session_id的cookie,注意这里需要指定path为/,不然gin会自动设置cookie的path为/auth,一个特别奇怪的问题。/home的逻辑很简单,使用中间件AuthMiddleWare注册之后,将会先执行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
}
}