

Gin学习笔记01 框架基础
source link: https://segmentfault.com/a/1190000041250790
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go Web
net/http库
package main import ( "fmt" "net/http" ) func sayHello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello") } func main() { http.HandleFunc("/hello", sayHello) err := http.ListenAndServe(":9090", nil) if err != nil { fmt.Println("http server failed, err:", err) } }
go run main.go
访问浏览器
http://localhost:9090/hello
http/template库
模板文件 hello.tmpl
<!DOCTYPE html> <html lang="en"> <head> <title>Hello</title> </head> <body> <p>{{ . }}</p> </body> </html>
main.go
func sayHello(w http.ResponseWriter, r *http.Request) { //解析模板 t, _ := template.ParseFiles("./hello.tmpl") //渲染模板 t.Execute(w, "World") } func main() { http.HandleFunc("/", sayHello) http.ListenAndServe(":9091", nil) }
t.Execute(w, map[string]interface{}{ "a": "aaa",//{{ .a }} "b": "bbb",//{{ .b }} })
{{/* .a */}}
{{ if lt .a 1}} a < 1 {{ else }} a >= 1 {{ end }}
t.Execute(w, map[string]interface{}{ "num": []string{"a", "b", "c"}, }) {{ range $k,$v := .num}} <p>{{ $k }} - {{ $v }}</p> {{ else }} --- {{ end }}
t, err := template.ParseFiles("./base.tmpl","./index.tmpl") name := "AAA" t.ExecuteTemplate(w, "index.tmpl", name)
base.tmpl
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>Go Templates</title> </head> <body> <div class="container-fluid"> {{block "content" . }}{{end}} </div> </body> </html>
index.tmpl
{{template "base.tmpl"}} {{define "content"}} <div>Hello world!</div> {{end}}
修改标识符
template.New("index.tmpl").Delims("{[","]}").ParseFiles("./index.tmpl")
自定义函数
t, _ := template.New("xss.tmpl").Funcs(template.FuncMap{ "safe": func(s string) template.HTML { return template.HTML(s) }, }).ParseFiles("./xss.tmpl") str := "<a>123</a>" t.Execute(w, str)
sss.tmpl
{{ . | safe }}
Gin框架
安装与使用
go env -w GO111MODULE=on go env -w GOPROXY=https://goproxy.cn,direct
引入gin库
go get -u github.com/gin-gonic/gin
编写Demo
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(":8081") // 监听并在 0.0.0.0:8081 上启动服务 }
解决:no required module provides package github.com/gin-gonic/gin: go.mod file not found in current directory or any parent directory; see 'go help modules'
go mod init xxx go get github.com/gin-gonic/gin
解决VSCODE出现红色波浪线的问题:
go mod vendor
访问浏览器
http://localhost:8081/ping
HTML渲染
Gin框架中使用
LoadHTMLGlob()
或者LoadHTMLFiles()
方法进行HTML模板渲染。
main.go
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // r.LoadHTMLGlob("template/**/*") r.LoadHTMLFiles("template/user/index.html") r.GET("/user/index", func(c *gin.Context) { c.HTML(http.StatusOK, "user/index.html", gin.H{ "title": "User Index", }) }) r.Run(":8081") // 监听并在 0.0.0.0:8081 上启动服务 }
template/user/index.html
{{define "user/index.html"}} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>{{.title}}</title> </head> <body> {{.title}} </body> </html> {{end}}
自定义函数
r.SetFuncMap(template.FuncMap{ "safe": func(str string) template.HTML { return template.HTML(str) }, })
{{.link| safe}}
加载静态文件
在解析文件(LoadHTMLGlob)前加载静态文件
r.Static("/xxx", "./statics") //以xxx开头的访问解析到./statics目录
<link rel="stylesheet" href="xxx/index.css"/>
Gin框架默认都是使用单模板,如果需要使用
block template
功能,可以通过"github.com/gin-contrib/multitemplate"
库实现
go get -u github.com/gin-contrib/multitemplate
home.tmpl和index.tmpl继承了base.tmpl
templates ├── includes │ ├── home.tmpl │ └── index.tmpl ├── layouts │ └── base.tmpl
func loadTemplates(templatesDir string) multitemplate.Renderer { r := multitemplate.NewRenderer() layouts, err := filepath.Glob(templatesDir + "/layouts/*.tmpl") if err != nil { panic(err.Error()) } includes, err := filepath.Glob(templatesDir + "/includes/*.tmpl") if err != nil { panic(err.Error()) } // 为layouts/和includes/目录生成 templates map for _, include := range includes { layoutCopy := make([]string, len(layouts)) copy(layoutCopy, layouts) files := append(layoutCopy, include) r.AddFromFiles(filepath.Base(include), files...) } return r } func main() { r := gin.Default() r.HTMLRender = loadTemplates("./templates") r.LoadHTMLGlob("templates/**/*") r.GET("/ping", func(c *gin.Context) { c.HTML(http.StatusOK, "index.tmpl", nil) }) r.Run(":8082") // 监听并在 0.0.0.0:8081 上启动服务 }
获取当前程序路径
func getCurrentPath() string { if ex, err := os.Executable(); err == nil { return filepath.Dir(ex) } return "./" }
JSON渲染
gin.H 是map[string]interface{}的缩写
func main() { r := gin.Default() // 方式一:自己拼接JSON {"message":"Hello World"} r.GET("/json", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "Hello World", }) }) //方法二:使用结构体 {"name":"test"} var msg struct { Name string `json:"name"` } msg.Name = "test" r.GET("/json2", func(c *gin.Context) { c.JSON(http.StatusOK, msg) }) r.Run(":8082") // 监听并在 0.0.0.0:8082 上启动服务 }
XML,YMAL,protobuf渲染
c.XML(http.StatusOK, msg) c.YMAL(http.StatusOK, msg) c.ProtoBuf(http.StatusOK, data)
Gin参数
query参数解析
http://localhost:8082/web?que...
func main() { r := gin.Default() r.GET("/web", func(c *gin.Context) { // query := c.Query("query") // query := c.DefaultQuery("query", "default")//设置默认值 query, ok := c.GetQuery("query") //ok表示是否获取到参数 if !ok { query = "default" } c.JSON(http.StatusOK, gin.H{ "query": query, }) }) r.Run(":8082") }
form参数解析
login.html
func main() { r := gin.Default() r.LoadHTMLFiles("login.html") r.GET("/login", func(c *gin.Context) { c.HTML(http.StatusOK, "login.html", nil) }) r.POST("/login", func(c *gin.Context) { // username := c.PostForm("username") // password := c.PostForm("password") //取到值则返回,取不到返回空 // username := c.DefaultPostForm("username", "aaa") // password := c.DefaultPostForm("password", "bbb") //默认值 username, ok := c.GetPostForm("username") if !ok { username = "xxx" } password, ok := c.GetPostForm("password") if !ok { password = "yyy" } c.JSON(http.StatusOK, gin.H{ "username": username, "password": password, }) }) r.Run(":8082") }
Json参数解析
func main() { r := gin.Default() r.POST("/json", func(c *gin.Context) { body, _ := c.GetRawData() var m gin.H //map[string] interface{} _ = json.Unmarshal(body, &m) //Unmarshal时接收体必须传递指针 c.JSON(http.StatusOK, m) }) r.Run(":8082") }
Path参数解析
访问路径:http://localhost:8082/itxiaom...
func main() { r := gin.Default() r.GET("/:name/:id", func(c *gin.Context) { name := c.Param("name") id := c.Param("id") c.JSON(http.StatusOK, gin.H{ "name": name, "id": id, }) }) r.Run(":8082") }
ShouldBind
会按照下面的顺序解析请求中的数据完成绑定:
- 如果是
GET
请求,只使用Form
绑定引擎(query
)。 - 如果是
POST
请求,首先检查content-type
是否为JSON
或XML
,然后再使用Form
(form-data
)。
type Login struct { Username string `form:"username" json:"user" binding:"required"` Password string `form:"password" json:"pwd" binding:"required"` } func main() { r := gin.Default() r.POST("/get", func(c *gin.Context) { var login Login err := c.ShouldBind(&login)//自动解析query if err == nil { c.JSON(http.StatusOK, login) } }) r.POST("/login", func(c *gin.Context) { var login Login err := c.ShouldBind(&login)//自动解析json,form if err == nil { c.JSON(http.StatusOK, login) } }) r.Run(":8082") }
<form action="/upload" method="post" enctype="multipart/form-data"> <input name="filename" type="file"/> <button type="submit">Submit</button> </form>
单个文件上传
func main() { r := gin.Default() r.LoadHTMLFiles("upload.html") r.GET("/upload", func(c *gin.Context) { c.HTML(http.StatusOK, "upload.html", nil) }) r.POST("/upload", func(c *gin.Context) { //从请求中读取文件 file, _ := c.FormFile("filename") //将文件保存到服务端 // filePath := fmt.Sprintf("./%s",file.Filename) filePath := path.Join("./", file.Filename) c.SaveUploadedFile(file, filePath) c.JSON(http.StatusOK, gin.H{ "filePath": filePath, }) }) r.Run(":8082") }
多文件上传
func main() { router := gin.Default() // 处理multipart forms提交文件时默认的内存限制是32 MiB // 可以通过下面的方式修改 // router.MaxMultipartMemory = 8 << 20 // 8 MiB router.POST("/upload", func(c *gin.Context) { // Multipart form form, _ := c.MultipartForm() files := form.File["file"] for index, file := range files { log.Println(file.Filename) dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index) // 上传文件到指定的目录 c.SaveUploadedFile(file, dst) } c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("%d files uploaded!", len(files)), }) }) router.Run() }
请求重定向
HTTP重定向
func main() { r := gin.Default() //301重定向 r.GET("/baidu", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com") }) r.Run(":8082") }
路由重定向
func main() { r := gin.Default() r.GET("/a", func(c *gin.Context) { c.Request.URL.Path = "/b" r.HandleContext(c) }) r.GET("/b", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", }) }) r.Run(":8082") }
r.GET("/index", func(c *gin.Context) {...}) r.POST("/add", func(c *gin.Context) {...}) r.PUT("/update", func(c *gin.Context) {...}) r.DELETE("/delete", func(c *gin.Context) {...}) //全部请求类型 r.Any("/all", func(c *gin.Context) {...}) //示例 func main() { r := gin.Default() r.Any("/all", func(c *gin.Context) { switch c.Request.Method { case http.MethodGet: c.JSON(http.StatusOK, gin.H{"method": "GET"}) case http.MethodPost: c.JSON(http.StatusOK, gin.H{"method": "POST"}) } }) r.Run(":8082") }
没有配置处理函数的路由:404
r.NoRoute(func(c *gin.Context) { c.HTML(http.StatusNotFound, "views/404.html", nil) })
路由组r.Group
拥有共同前缀的路由为一个路由组,路由组支持嵌套
func main() { r := gin.Default() userGroup := r.Group("/user") { userGroup.GET("/index1", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"route": "/user/index1"}) }) userGroup.GET("/index2", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"route": "/user/index2"}) }) subGroup := userGroup.Group("/sub") { subGroup.GET("/index3", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"route": "/user/sub/index3"}) }) } } r.Run(":8082") }
定义中间件
Gin的中间件必须是
gin.HandlerFunc
类型
func m1() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() // 调用该请求的剩余处理程序 // c.Abort() // 不调用该请求的剩余处理程序 cost := time.Since(start) fmt.Println("Cost:", cost) } }
注册中间件
func main() { r := gin.Default() r.Use(m1()) r.GET("/index", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", }) }) r.Run(":8082") }
func m2() gin.HandlerFunc { return func(c *gin.Context) { c.Abort() // 不调用该请求的剩余处理程序 } } func main() { r := gin.Default() r.Use(m1()) r.GET("/index", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", }) }) r.GET("/index2", m2(), func(c *gin.Context) { //以下内容不会输出 c.JSON(http.StatusOK, gin.H{ "status": "ok", }) }) r.Run(":8082") }
shopGroup := r.Group("/shop", m1()) //或shopGroup.Use(m1()) { shopGroup.GET("/index", func(c *gin.Context) {...}) ... }
r.Use(m1, m2, m3(false))
gin默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.DefaultWriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover任何panic
。如果有panic的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
gin中间件中使用goroutine
当在中间件或handler
中启动新的goroutine
时,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy()
)。(否则运行中被修改非常不安全)
go funcXX(c.Copy())
多端口运行多个服务
package main import ( "log" "net/http" "time" "github.com/gin-gonic/gin" "golang.org/x/sync/errgroup" ) var ( g errgroup.Group ) func router01() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 01", }, ) }) return e } func router02() http.Handler { e := gin.New() e.Use(gin.Recovery()) e.GET("/", func(c *gin.Context) { c.JSON( http.StatusOK, gin.H{ "code": http.StatusOK, "error": "Welcome server 02", }, ) }) return e } func main() { server01 := &http.Server{ Addr: ":8080", Handler: router01(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } server02 := &http.Server{ Addr: ":8081", Handler: router02(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } // 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务 g.Go(func() error { return server01.ListenAndServe() }) g.Go(func() error { return server02.ListenAndServe() }) if err := g.Wait(); err != nil { log.Fatal(err) } }
Gin中文文档:https://gin-gonic.com/zh-cn/d...
基于gin框架和gorm的web开发实战:https://www.bilibili.com/vide...
相关博客:https://www.liwenzhou.com/pos...
Recommend
-
60
介绍 为简化使用流程,减少开发时间,降低项目开发成本,搭建了一套基于 Gin 的web骨架。组件包括但不局限于路由、控制器、Orm、session、cookie、mysql连接、认证中间件等。 项目地址:https://g...
-
34
准备工作 确认本地环境的 $GOPATH Windows 下使用 echo %GOPATH% Linux 下使用 ...
-
29
前言 今天是我golang框架阅读系列第三篇文章,今天我们主要...
-
36
Go框架解析:gin 2019-07-06
-
9
作者:朋也 日期:2021-02-22 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) 好几年前用beego写了个pybbs-go,然后...
-
9
作者:朋也 日期:2021-02-22 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) 上古的分包模型,M模型,V视图,C控制...
-
5
作者:朋也 日期:2021-02-24 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) gin里用的验证器是 https://github.com...
-
14
作者:朋也 日期:2021-02-24 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) gin里的中间件类似于Java里的拦截器(...
-
10
前言:之前在QQ音乐实习的时候,一直用的是TEG那边写的going框架+TME在此基础上的封装,一直没能目睹Gin框架的真容,目前IEG所在组用的是Gin,正好学习一下优秀开源框架的设计思想,故记录此文 https://g...
-
2
Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。 如果您需要性能和良好的生产力,您一定会喜欢 Gin。(官...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK