Go Web
net/http库
package mainimport ( "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=ongo env -w GOPROXY=https://goproxy.cn,direct
引入gin库
go get -u github.com/gin-gonic/gin
编写Demo
package mainimport "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 xxxgo get github.com/gin-gonic/gin
解决VSCODE呈现红色波浪线的问题:
go mod vendor
拜访浏览器
http://localhost:8081/ping
HTML渲染
Gin框架中应用LoadHTMLGlob()
或者LoadHTMLFiles()
办法进行HTML模板渲染。
main.go
package mainimport ( "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 mainimport ( "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...