4

Gin学习笔记01 框架基础

 3 years ago
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.
neoserver,ios ssh client

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会按照下面的顺序解析请求中的数据完成绑定:

  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 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
    • studygolang.com 6 years ago
    • Cache

    web框架Gin使用

    介绍 为简化使用流程,减少开发时间,降低项目开发成本,搭建了一套基于 Gin 的web骨架。组件包括但不局限于路由、控制器、Orm、session、cookie、mysql连接、认证中间件等。 项目地址:https://g...

  • 34
    • studygolang.com 6 years ago
    • Cache

    Gin框架介绍

    准备工作 确认本地环境的 $GOPATH Windows 下使用 echo %GOPATH% Linux 下使用 ...

  • 29
    • www.tuicool.com 5 years ago
    • Cache

    Go框架解析-gin

    前言 今天是我golang框架阅读系列第三篇文章,今天我们主要...

  • 36
    • tigerb.cn 5 years ago
    • Cache

    Go框架解析-gin - TIGERB

    Go框架解析:gin 2019-07-06

  • 9
    • tomoya92.github.io 4 years ago
    • Cache

    Gin学习笔记 - HelloWorld

    作者:朋也 日期:2021-02-22 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) 好几年前用beego写了个pybbs-go,然后...

  • 9
    • tomoya92.github.io 4 years ago
    • Cache

    Gin学习笔记 - MVC & CRUD

    作者:朋也 日期:2021-02-22 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) 上古的分包模型,M模型,V视图,C控制...

  • 5
    • tomoya92.github.io 4 years ago
    • Cache

    Gin学习笔记 - Validator

    作者:朋也 日期:2021-02-24 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) gin里用的验证器是 https://github.com...

  • 14
    • tomoya92.github.io 4 years ago
    • Cache

    Gin学习笔记 - Middleware

    作者:朋也 日期:2021-02-24 版权声明:自由转载-非商用-非衍生-保持署名( 创意共享3.0许可证 ) gin里的中间件类似于Java里的拦截器(...

  • 10
    • yangshuai-uestc.github.io 3 years ago
    • Cache

    Gin框架学习

    前言:之前在QQ音乐实习的时候,一直用的是TEG那边写的going框架+TME在此基础上的封装,一直没能目睹Gin框架的真容,目前IEG所在组用的是Gin,正好学习一下优秀开源框架的设计思想,故记录此文 https://g...

  • 2
    • loli.fj.cn 2 years ago
    • Cache

    【笔记】Gin学习笔记

    Gin 是一个用 Go (Golang) 编写的 Web 框架。 它具有类似 martini 的 API,性能要好得多,多亏了 httprouter,速度提高了 40 倍。 如果您需要性能和良好的生产力,您一定会喜欢 Gin。(官...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK