Gin框架介绍
介绍
Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。
快速入门
安装gin
go get -u github.com/gin-gonic/gin
引入gin
import "github.com/gin-gonic/gin"
开始
package mainimport "github.com/gin-gonic/gin"func main() {router := gin.Default()router.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})router.Run() // 监听并在 0.0.0.0:8080 上启动服务
}
路由
-
普通路由
router.GET("/", func) router.POST("/login", func) router.Any("/login", func)
-
路由分组
// 简单的路由组: v1{v1 := router.Group("/v1")v1.POST("/login", loginEndpoint)v1.POST("/submit", submitEndpoint)v1.POST("/read", readEndpoint)}// 简单的路由组: v2{v2 := router.Group("/v2")v2.POST("/login", loginEndpoint)v2.POST("/submit", submitEndpoint)v2.POST("/read", readEndpoint)}
-
RESTFUL
router.GET("/user", QueryFunc) //查询 router.Post("/user", AddFunc) // 新增 router.Delete("/user", DeleteFunc) // 删除 router.PUT("/user", UpdateFunc) // 更新(客户端提供完整数据) router.PATCH("/user", PatchUpdateFunc) // 更新(客户端提供需要修改的数据)
-
重定向
// 重定向到外部 router.GET("/test", func(c *gin.Context) {c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") })// 重定向到内部 router.POST("/test", func(c *gin.Context) {c.Redirect(http.StatusFound, "/foo") })router.GET("/test", func(c *gin.Context) {c.Request.URL.Path = "/test2"router.HandleContext(c) }) router.GET("/test2", func(c *gin.Context) {c.JSON(200, gin.H{"hello": "world"}) })
-
静态文件
func main() {router := gin.Default()router.Static("/assets", "./assets") // 文件目录router.StaticFS("/more_static", http.Dir("my_file_system"))router.StaticFile("/favicon.ico", "./resources/favicon.ico") // 单独的文件// 监听并在 0.0.0.0:8080 上启动服务router.Run(":8080") }
输出
-
XML/JSON/TOML/YAML/ProtoBuf
c.JSON(http.StatusOK, struct/gin.H) c.XML(http.StatusOK, struct/gin.H) c.YAML(http.StatusOK, struct/gin.H) c.ProtoBuf(http.StatusOK, struct/gin.H) ... 可以到定义文件中去看更多的输出方法
-
HTML
func main() {router := gin.Default()router.LoadHTMLGlob("templates/*")//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")router.GET("/index", func(c *gin.Context) {c.HTML(http.StatusOK, "index.tmpl", gin.H{"title": "Main website",})})router.Run(":8080") }templates/index.tmpl <html><h1>{{ .title }}</h1> </html>
func main() {router := gin.Default()router.LoadHTMLGlob("templates/**/*")router.GET("/posts/index", func(c *gin.Context) {c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{"title": "Posts",})})router.GET("/users/index", func(c *gin.Context) {c.HTML(http.StatusOK, "users/index.tmpl", gin.H{"title": "Users",})})router.Run(":8080") }templates/posts/index.tmpl {{ define "posts/index.tmpl" }} <html><h1>{{ .title }} </h1> <p>Using posts/index.tmpl</p> </html> {{ end }}templates/users/index.tmpl {{ define "users/index.tmpl" }} <html><h1>{{ .title }} </h1> <p>Using users/index.tmpl</p> </html> {{ end }}
自定义模板渲染器 ...
参数
-
参数绑定
ShouldBind: 自动识别参数,并绑定对应字段到结构体中ShouldBindJSON tag:json ShouldBindXML tag:xml ShouldBindQuery tag:form ShouldBindYAML tag:yaml ShouldBindTOML tag:toml ShouldBindHeader tag:header 无法自动识别 ShouldBindUri tag:uri 无法自动识别type formA struct {Foo string `json:"foo" xml:"foo" binding:"required" form:"foo"` }
type formA struct {Foo string `json:"foo" xml:"foo" binding:"required"` }type formB struct {Bar string `json:"bar" xml:"bar" binding:"required"` }func SomeHandler(c *gin.Context) {objA := formA{}objB := formB{}// c.ShouldBind 使用了 c.Request.Body,不可重用。if errA := c.ShouldBind(&objA); errA == nil {c.String(http.StatusOK, `the body should be formA`)// 因为现在 c.Request.Body 是 EOF,所以这里会报错。} else if errB := c.ShouldBind(&objB); errB == nil {c.String(http.StatusOK, `the body should be formB`)} else {...} }
要想多次绑定,可以使用
c.ShouldBindBodyWith
func SomeHandler(c *gin.Context) {objA := formA{}objB := formB{}// 读取 c.Request.Body 并将结果存入上下文。if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {c.String(http.StatusOK, `the body should be formA`)// 这时, 复用存储在上下文中的 body。} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {c.String(http.StatusOK, `the body should be formB JSON`)// 可以接受其他格式} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {c.String(http.StatusOK, `the body should be formB XML`)} else {...} }
c.MustBindWith(&obj, binding.JSON) 如果发生绑定错误,则请求终止,并设置错误码为400。ShouldBind()返回错误给开发者,不会导致请求终止。
-
非绑定获取
r.GET("/:u/:p", func) c.Param("u") // url: /11/22r.GET("/", func) c.Query("u") // url: /?u=11 c.DefaultQuery("u", "222")r.GET("/", func) c.PostForm("u") // 获取表单中字段的值 c.DefaultPostForm("u", "222")
中间件
-
调用栈
func mw1() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("mw1 before")c.Next()fmt.Println("mw1 after")} }func mw2() gin.HandlerFunc {return func(c *gin.Context) {fmt.Println("mw2 before")c.Next()fmt.Println("mw2 after")} }func Middleware() {r := gin.Default()r.GET("/", mw1(), mw2(), func(c *gin.Context) {fmt.Println("self")c.String(http.StatusOK, "self")})err := r.Run(":8080")if err != nil {panic(err)} }输出: mw1 before mw2 before self mw2 after mw1 after
-
用户认证
// 模拟一些私人数据 var secrets = gin.H{"foo": gin.H{"email": "foo@bar.com", "phone": "123433"},"austin": gin.H{"email": "austin@example.com", "phone": "666"},"lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, }func main() {router := gin.Default()// 路由组使用 gin.BasicAuth() 中间件// gin.Accounts 是 map[string]string 的一种快捷方式authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{"foo": "bar","austin": "1234","lena": "hello2","manu": "4321",}))// /admin/secrets 端点// 触发 "localhost:8080/admin/secretsauthorized.GET("/secrets", func(c *gin.Context) {// 获取用户,它是由 BasicAuth 中间件设置的user := c.MustGet(gin.AuthUserKey).(string)if secret, ok := secrets[user]; ok {c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})} else {c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})}})// 监听并在 0.0.0.0:8080 上启动服务router.Run(":8080") }使用中间件
-
使用中间件
func main() {// 新建一个没有任何默认中间件的路由r := gin.New()// 全局中间件// Logger 中间件将日志写入 gin.DefaultWriter,即使你将 GIN_MODE 设置为 release。// By default gin.DefaultWriter = os.Stdoutrouter.Use(gin.Logger())// Recovery 中间件会 recover 任何 panic。如果有 panic 的话,会写入 500。router.Use(gin.Recovery())// 你可以为每个路由添加任意数量的中间件。router.GET("/benchmark", MyBenchLogger(), benchEndpoint)// 认证路由组// authorized := router.Group("/", AuthRequired())// 和使用以下两行代码的效果完全一样:authorized := router.Group("/")// 路由组中间件! 在此例中,我们在 "authorized" 路由组中使用自定义创建的// AuthRequired() 中间件authorized.Use(AuthRequired()){authorized.POST("/login", loginEndpoint)authorized.POST("/submit", submitEndpoint)authorized.POST("/read", readEndpoint)// 嵌套路由组testing := authorized.Group("testing")testing.GET("/analytics", analyticsEndpoint)}// 监听并在 0.0.0.0:8080 上启动服务router.Run(":8080") }
模型验证
-
官方验证器
Gin使用 go-playground/validator/v10 进行验证。 查看标签用法的全部文档.
type LoginInfo struct {Username string `json:"username" form:"username" binding:"required"`Password string `json:"password" form:"password" binding:"number"`Email string `json:"email" form:"email" binding:"email"` }func Validator() {r := gin.Default()r.GET("/", func(c *gin.Context) {login := LoginInfo{}err := c.ShouldBind(&login)if err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}c.JSON(http.StatusOK, login)})err := r.Run(":8080")if err != nil {panic(err)} }
-
自定义验证器
import ("net/http""reflect""time""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""github.com/go-playground/validator/v10" )// Booking 包含绑定和验证的数据。 type Booking struct {CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"` }var bookableDate validator.Func = func(fl validator.FieldLevel) bool {date, ok := fl.Field().Interface().(time.Time)if ok {today := time.Now()if today.After(date) {return false}}return true }func main() {route := gin.Default()if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("bookabledate", bookableDate)}route.GET("/bookable", getBookable)route.Run(":8085") }func getBookable(c *gin.Context) {var b Bookingif err := c.ShouldBindWith(&b, binding.Query); err == nil {c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})} }
HTTPS
-
自有证书
Openssl 生成证书,如果只是提供API服务,可以用没有经过认证的证书,如果是网站,则需要认证证书
router.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
-
开源免费认证证书(Let's Encrypt)
package mainimport ("log""github.com/gin-gonic/autotls""github.com/gin-gonic/gin""golang.org/x/crypto/acme/autocert" )func main() {router := gin.Default()// Ping handlerrouter.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})m := autocert.Manager{Prompt: autocert.AcceptTOS,HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),Cache: autocert.DirCache("/var/www/.cache"),}log.Fatal(autotls.RunWithManager(r, &m)) }