Gin学习
最简单的实例
1 | package main |
获取路由中的参数
1 | package main |
访问:/u/qiyou/abc
,返回的是{"name":"qiyou","pass":"/abc"}
,注意abc这里有一个斜杠
获取GET参数
1 | package main |
DefaultQuery
: 如果请求的参数没有包含key,那么默认为第二个参数。
获取POST参数
1 | package main |
DefaultPostForm
: 如果请求的参数没有包含key,那么默认为第二个参数。
同时获取GET和POST参数
注意:GET路由不支持POST请求,如果发送POST那么将会放回404,POST路由也是如此
1 | package main |
上传文件
1 | package main |
测试:curl -F "files=@abc.txt" http://192.168.43.212:7777/upload
多文件上传1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/upload", func(c *gin.Context) {
all,_:=c.MultipartForm()
files:=all.File["files[]"]
for _,file:=range files{
if err:=c.SaveUploadedFile(file,"./abc.txt");err!=nil{
c.String(http.StatusInternalServerError,err.Error())
}else{
c.String(http.StatusOK,"Success,"+file.Filename)
}
}
})
r.Run("0.0.0.0:7777")
}
测试:curl -F "files[]=@main.go" -F "files[]=@abc.txt" http://192.168.43.212:7777/upload
路由分组
1 | package main |
自定义中间件
使用1
r := gin.New()
代替1
r := gin.Default() // 默认启动方式,包含 Logger、Recovery 中间件
简单的实例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
//添加中间件
r.Use(gin.Recovery())
r.Use(gin.Logger())
r.GET("/test/",func(c *gin.Context){
c.String(200,"test")
})
test:=r.Group("/www/")
//给路由组添加中间件
test.Use(gin.Recovery())
test.Use(gin.Logger())
{
test.GET("/abc/",func(c *gin.Context){
c.String(200,"abc")
})
}
r.Run(":7777")
}
日志文件
1 | package main |
自定义日志格式,输出类似于apache日志格式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package main
import (
"fmt"
"github.com/gin-gonic/gin"
"time"
)
func main() {
router := gin.New()
// LoggerWithFormatter 中间件会将日志写入 gin.DefaultWriter
// By default gin.DefaultWriter = os.Stdout
router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
// 你的自定义格式
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
}))
router.Use(gin.Recovery())
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":7777")
}
模型绑定
要注意几个点是:
- tag必须要有,以kv的形式存在,例如:
json:"Fieldame"
,而且tag里面的key-value
冒号之间不能用空格 - form表示的是GET和POST,json表示的是json,xml表示的是xml
binding:"required"
表示的是一定要有值,
1 | package main |
如果只想绑定GET参数不绑定POST参数,可以使用ShouldBindQuery
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package main
import (
"github.com/gin-gonic/gin"
)
type Login struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.New()
var test Login
router.POST("/login",func(c *gin.Context){
if err:=c.ShouldBindQuery(&test);err!=nil{
c.JSON(400,gin.H{"message":"login failed"})
}else{
c.JSON(200,gin.H{
"user":test.User,
"pass":test.Password,
})
}
})
router.Run(":7777")
}
接收POST或者GET1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30package main
import (
"github.com/gin-gonic/gin"
)
type Login struct {
User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" xml:"password" binding:"required"`
}
func main() {
router := gin.New()
var test Login
router.POST("/login",func(c *gin.Context){
// 如果是Get,那么接收不到请求中的Post的数据
// 如果是Post, 首先判断 `content-type` 的类型 `JSON` or `XML`, 然后使用对应的绑定器获取数据.
//https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
if err:=c.ShouldBind(&test);err!=nil{
c.JSON(400,gin.H{"message":"login failed"})
}else{
c.JSON(200,gin.H{
"user":test.User,
"pass":test.Password,
})
}
})
router.Run(":7777")
}
绑定uri
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package main
import (
"github.com/gin-gonic/gin"
)
type Login struct {
Path string `uri:"path" binding:"required"`
User string `uri:"user" binding:"required"`
}
func main() {
router := gin.New()
var test Login
router.POST("/:user/:path",func(c *gin.Context){
if err:=c.ShouldBindUri(&test);err!=nil{
c.JSON(400,gin.H{"message":"login failed"})
}else{
c.JSON(200,gin.H{
"user":test.User,
"pass":test.Path,
})
}
})
router.Run(":7777")
}
只绑定POST1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
type Login struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.New()
var test Login
router.POST("/login",func(c *gin.Context){
if err:=c.ShouldBindWith(&test,binding.Form);err!=nil{
c.JSON(400,gin.H{"message":"login failed"})
}else{
c.JSON(200,gin.H{
"user":test.Username,
"pass":test.Password,
})
}
})
router.Run(":7777")
}
XML和JSON、YAML格式渲染1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31package main
import (
"github.com/gin-gonic/gin"
)
type Login struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.Default()
var test =Login{
Username: "abc",
Password: "abc",
}
router.POST("/json",func(c *gin.Context){
c.JSON(200,test)
})
router.POST("/xml",func(c *gin.Context){
c.XML(200,gin.H{"status":200,"msg":"suceess xml"}) //或者使用结构体也可以
})
router.POST("yaml",func(c *gin.Context){
c.YAML(200,gin.H{"status":200,"msg":"suceess yaml"}) //或者使用结构体也可以
})
router.Run(":7777")
}
JSONP1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
param:=map[string]interface{}{
"name":"qiyou",
}
router.GET("/JSONP",func(c *gin.Context){
c.JSONP(200,param)
})
router.Run(":7777")
}
访问/JSONP?callback=call
,将会输出call({"name":"qiyou"});
AsciiJSON
将使特殊字符编码
其实默认的c.JSON
也会将特殊的HTML字符替换为对应的unicode字符1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package main
import (
"github.com/gin-gonic/gin"
)
type Login struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.Default()
param:=map[string]interface{}{
"name":"<b>test</b>",
}
router.GET("/JSON",func(c *gin.Context){
c.AsciiJSON(200,param)
})
router.Run(":7777")
}
PureJSON
不将特殊字符编码
PS: 该功能在go1.6及以下版本无法实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package main
import (
"github.com/gin-gonic/gin"
)
type Login struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.Default()
param:=map[string]interface{}{
"name":"<b>test</b>",
}
router.GET("/JSON",func(c *gin.Context){
c.PureJSON(200,param)
})
router.Run(":7777")
}
静态文件
设置静态文件路径
PS: 这些目录下资源是可以随时更新,而不用重新启动程序1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
//设置单个文件
router.StaticFile("/1.png","./static/1.png")
//设置一个文件夹
router.StaticFS("/other",http.Dir("./Static"))
router.Run(":7777")
}
返回第三方获取的数据1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
router.GET("/test", func(c *gin.Context) {
response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
reader := response.Body
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")
extraHeaders := map[string]string{
"Content-Disposition": `attachment; filename="gopher.png"`,
}
c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
})
router.Run(":7777")
}
模板渲染
LoadHTMLGlob()
: 支持统配符LoadHTMLFiles()
: 支持多个文件
PS: LoadHTMLGlob和LoadHTMLFiles只能使用其中一个,否则后者会覆盖掉前者r.LoadHTMLGlob("template/*")
//此处加载html路径只能指定一次,如果指定多次只有最后一次有效r.LoadHTMLGlob("view/*")
//此处重新设置了html的路径,所以template中的html就无法起作用。r.LoadHTMLGlob("template/**/*")
: 匹配template
子文件夹里面的文件,但是不匹配template
下的文件,比如只匹配template/test/index.html
,但是不匹配template/index.html
1 | package main |
index.html1
2
3
4
5
6
7
8
9
10<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{.title}}</title>
</head>
<body>
{{.name}}
</body>
</html>
路径不同相同文件名的模板渲染,使用`{{defind "name"}}`定义以下模板名称1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("./templates/**/*")
router.GET(("/user"),func(c *gin.Context){
c.HTML(200,"users/index.html",gin.H{ //这里填写的是模板名称
"title":"users",
"name":"zhangsan",
})
})
router.GET(("/posts"),func(c *gin.Context){
c.HTML(200,"posts/index.html",gin.H{ //这里填写的是模板名称
"title":"posts",
"name":"lisi",
})
})
router.Run(":7777")
}
posts/index.html
内容
1 | {{define "posts/index.html"}} //定义模板名称,模板名称可以随便定义,但是建议还是和以目录的形式会好一点 |
自定义模板函数
PS: 默认模板渲染的时候会自动html编码,router.SetFuncMap
必须在router.LoadHTMLGlob
前面,否则会宕机1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26package main
import (
"github.com/gin-gonic/gin"
"html/template"
"strings"
)
func main() {
router := gin.Default()
router.SetFuncMap(template.FuncMap{
"htmlspecialchars":HTMLspecialchars,
})
router.LoadHTMLGlob("./templates/index.html")
router.GET("/test", func(c *gin.Context) {
c.HTML(200,"index.html", gin.H{
"title":"test",
"name":"<abc>",
})
})
router.Run(":7777")
}
func HTMLspecialchars(s string)string{
return strings.Replace(s,"<","<",-1)
}
重定向
1 | package main |
验证中间件
1 | package main |
路由组使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
auth:=router.Group("/admin",gin.BasicAuth(gin.Accounts{
"admin":"admin",
}))
auth.GET("/login",func(c *gin.Context){
c.JSON(200,gin.H{
"success":c.MustGet(gin.AuthUserKey).(string),
})
})
router.Run(":7777")
}
自定义中间件
1 | package main |
自定义http配置
1 | package main |
启动多个服务
1 | package main |
自定义路由日志格式
我们可以使用
gin.DebugPrintRouteFunc
来定义格式
默认的路由是这样子的1
[GIN-debug] GET / --> main.main.func1 (3 handlers)
1 | package main |
设置Cookie
1 | package main |