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会依照上面的程序解析申请中的数据实现绑定:

  1. 如果是 GET 申请,只应用 Form 绑定引擎(query)。
  2. 如果是 POST 申请,首先查看 content-type 是否为 JSONXML,而后再应用 Formform-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()默认应用了LoggerRecovery中间件,其中:

  • 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...