Gin Web基础

安装

Go版本在1.13+,从 Go 1.13 开始,Go Module模式将成为默认模式

# go version              
go version go1.16 darwin/amd64
# go env GO111MODULE       
on

gin_test_project # go mod init
gin_test_project # go get -u github.com/gin-gonic/gin

请求路由 - 多种请求

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/get", func(c *gin.Context) {
		c.String(200, "get")
	})
	r.POST("/post", func(c *gin.Context) {
		c.String(200, "post")
	})
	// 另一种方式
	r.Handle("DELETE", "/delete", func(c *gin.Context) {
		c.String(200, "delete")
	})
	// 一个请求路由对应多个请求方法
	r.Any("/any", func(c *gin.Context) {
		c.String(200, "any")
	})
	r.Run()
}

测试
router_type % curl -X GET "http://127.0.0.1:8080/get"
get
router_type % curl -X POST "http://127.0.0.1:8080/post"
post
router_type % curl -X DELETE "http://127.0.0.1:8080/delete"
delete
router_type % curl -X PUT "http://127.0.0.1:8080/any"
any 
router_type % curl -X CONNECT "http://127.0.0.1:8080/any"
any

请求路由 - 静态文件夹

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	// 静态文件夹绑定
	r.Static("/assets", "./assets")
	// 另一种写法
	r.StaticFS("/static", http.Dir("static"))
	// 设置单个文件资源
	r.StaticFile("/favicon.ico", "./favicon.ico")
	r.Run()
}
命令行启动
router_static % go build -o router_static && ./router_static
测试
router_static % curl "http://127.0.0.1:8080/assets/a.html"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    assets content
</body>
</html>

router_static % curl "http://127.0.0.1:8080/static/b.html"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    static content
</body>
</html>
目录结构

在这里插入图片描述
在这里插入图片描述

请求路由 - 参数作为url

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/:name/:id", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"name": c.Param("name"),
			"id":   c.Param("id"),
		})
	})
	r.Run()
}


测试
router_url % curl -X GET "http://127.0.0.1:8080/zhangsan/10"
{"id":"10","name":"zhangsan"}

请求路由 - 泛绑定

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/user/*action", func(c *gin.Context) {
		c.String(200, "hello world")
	})
	r.Run()
}
测试
router_generic % curl -X GET "http://127.0.0.1:8080/user/xxxx"
hello world
router_generic % curl -X GET "http://127.0.0.1:8080/user/yyyy"
hello world                                                                                                                                    

获取请求参数 - 获取GET参数

package main

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		firstName := c.Query("first_name")
		lastName := c.DefaultQuery("last_name", "wang")
		c.String(http.StatusOK, "%s, %s", firstName, lastName)
	})
	r.Run(":8080")
}
测试
param_get % curl -X GET "http://127.0.0.1:8080/test?first_name=zhang"
zhang, wang
param_get % curl -X GET "http://127.0.0.1:8080/test?first_name=zhang&last_name=san"
zhang, san

获取请求参数 - 获取body内容

package main

import (
	"bytes"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"net/http"
)

func main() {
	r := gin.Default()
	r.POST("/test", func(c *gin.Context) {
		bodyBytes, err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.String(http.StatusBadRequest, err.Error())
			c.Abort()
		}
		//c.String(http.StatusOK, string(bodyBytes))

		//firstName := c.PostForm("first_name") // ioutil.ReadAll读完之后PostForm、DefaultPostForm获取不到数据
		//lastName := c.DefaultPostForm("last_name", "wang")
		//c.String(http.StatusOK, "%s, %s, %s", firstName, lastName, string(bodyBytes))

		c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // ioutil.ReadAll读完之后需要将bodyBytes回存到c.Request.Body, PostForm、DefaultPostForm就可以获取到数据
		firstName := c.PostForm("first_name")
		lastName := c.DefaultPostForm("last_name", "wang")
		c.String(http.StatusOK, "%s, %s, %s", firstName, lastName, string(bodyBytes))
	})
	r.Run()
}
测试
param_body % curl -X POST 'http://127.0.0.1:8080/test' -d 'first_name=zhang&last_name=san'
zhang, san, first_name=zhang&last_name=san

获取请求参数 - 获取bind参数

package main

import (
	"github.com/gin-gonic/gin"
	"time"
)

type Student struct {
	Name     string    `form:"name"`
	Address  string    `form:"address"`
	Birthday time.Time `form:"birthday" time_format:"2006-01-02"`
}

func main() {
	r := gin.Default()
	r.GET("/stu", student)
	r.POST("/stu", student)
	r.Run()
}

func student(c *gin.Context) {
	var stu Student
	// 根据请求content-type来做不同的binding操作
	if err := c.ShouldBind(&stu); err == nil {
		c.String(200, "%v", stu)
	} else {
		c.String(200, "student bind error: %v", err)
	}
}
测试
param_struct % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&address=hangzhou&birthday=2022-03-20"
{zhang hangzhou 2022-03-20 00:00:00 +0800 CST}
param_struct % curl -X POST "http://127.0.0.1:8080/stu" -d "name=zhang&address=hangzhou&birthday=2022-03-20"
{zhang hangzhou 2022-03-20 00:00:00 +0800 CST}
param_struct % curl -H "Content-Type:application/json" -X POST "http://127.0.0.1:8080/stu" -d '{"name": "zhang", "address": "hangzhou"}'                          
{zhang hangzhou 0001-01-01 00:00:00 +0000 UTC}

验证请求参数 - 结构体验证

package main

import "github.com/gin-gonic/gin"

type Student struct {
	Age     int    `form:"age" binding:"required,gt=18"` // 同时满足用,有一个满足用|
	Name    string `form:"name" binding:"required"`
	Address string `form:"address" binding:"required"`
}

func main() {
	r := gin.Default()
	r.GET("/stu", func(c *gin.Context) {
		var stu Student
		if err := c.ShouldBind(&stu); err != nil {
			c.String(500, "%v", err)
			c.Abort()
			return
		}
		c.String(200, "%v", stu)
	})
	r.Run()
}
测试
valid_binding % curl -X GET "http://127.0.0.1:8080/stu?age=11&name=zhang&address=hangzhou"
Key: 'Student.Age' Error:Field validation for 'Age' failed on the 'gt' tag
valid_binding % curl -X GET "http://127.0.0.1:8080/stu?age=19&name=zhang&address=hangzhou"
{19 zhang hangzhou}
valid_binding % curl -X GET "http://127.0.0.1:8080/stu?age=19&name=zhang"                 
Key: 'Student.Address' Error:Field validation for 'Address' failed on the 'required' tag

验证请求参数 - 自定义验证规则

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10" // gin.v1.7.7 use validator.v10
	"time"
)

type Book struct {
	CheckIn  time.Time `form:"check_in" binding:"required,bookdate" time_format:"2006-01-02"` // 在参数 binding 上使用自定义的校验方法函数注册时候的名称
	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}

var bookDate validator.Func = func(fl validator.FieldLevel) bool {
	if date, ok := fl.Field().Interface().(time.Time); ok {
		today := time.Now()
		if date.Unix() > today.Unix() {
			return true
		}
	}
	return false
}

func main() {
	r := gin.Default()
	// 将我们自定义的校验方法注册到 validator中
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		// 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
		_ = v.RegisterValidation("bookdate", bookDate)
	}
	r.GET("/book", func(c *gin.Context) {
		var b Book
		if err := c.ShouldBind(&b); err != nil {
			c.JSON(500, gin.H{"error": err.Error()})
			c.Abort()
			return
		}
		c.JSON(200, gin.H{"message": "ok", "book": b})
	})
	r.Run()
}
测试
valid_custom % curl -X GET "http://127.0.0.1:8080/book?check_in=2022-03-21&check_out=2022-03-23"
{"book":{"CheckIn":"2022-03-21T00:00:00+08:00","CheckOut":"2022-03-23T00:00:00+08:00"},"message":"ok"}
valid_custom % curl -X GET "http://127.0.0.1:8080/book?check_in=2022-03-20&check_out=2022-03-23"
{"error":"Key: 'Book.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookdate' tag"}
valid_custom % curl -X GET "http://127.0.0.1:8080/book?check_in=2022-03-23&check_out=2022-03-22"
{"error":"Key: 'Book.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}

验证请求参数 - 多语言翻译验证

package main

import (
	"github.com/gin-gonic/gin"
	en2 "github.com/go-playground/locales/en"
	zh2 "github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"                                 // 支持多语言化
	en_translations "github.com/go-playground/validator/v10/translations/en" // 基于语言做特殊的错误显示,英文语言包
	zh_translations "github.com/go-playground/validator/v10/translations/zh" // 基于语言做特殊的错误显示,中文语言包
)

type Student struct {
	Age     int    `form:"age" validate:"required,gt=18"` // 验证tag为validate
	Name    string `form:"name" validate:"required"`
	Address string `form:"address" validate:"required"`
}

var (
	Uni      *ut.UniversalTranslator
	Validate *validator.Validate
)

// 验证信息多语言化
func main() {
	Validate = validator.New() // 创建验证器

	zh := zh2.New()
	en := en2.New()

	Uni = ut.New(zh, en) // 创建翻译器,设置支持的语言

	r := gin.Default()
	r.GET("/stu", func(c *gin.Context) {
		locale := c.DefaultQuery("locale", "zh")
		// 根据请求语言参数获取相应的翻译器
		trans, _ := Uni.GetTranslator(locale)
		switch locale {
		case "zh":
			_ = zh_translations.RegisterDefaultTranslations(Validate, trans) // 中文语言翻译器注册到验证器
		case "en":
			_ = en_translations.RegisterDefaultTranslations(Validate, trans) // 英文语言翻译器注册到验证器
		default:
			_ = zh_translations.RegisterDefaultTranslations(Validate, trans)
		}
		var stu Student
		if err := c.ShouldBind(&stu); err != nil {
			c.String(500, "%v", err)
			c.Abort()
			return
		}
		// 验证结构体
		if err := Validate.Struct(stu); err != nil {
			errs := err.(validator.ValidationErrors) // 把错误转化为错误集合
			sliceErrs := []string{}
			// 遍历错误,翻译成相应的语言
			for _, e := range errs {
				sliceErrs = append(sliceErrs, e.Translate(trans))
			}
			c.String(500, "%v", sliceErrs)
			c.Abort()
			return
		}
		c.String(200, "%v", stu)
	})
	r.Run()
}
测试
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu"
[Age为必填字段 Name为必填字段 Address为必填字段]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang"
[Age为必填字段 Address为必填字段]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&age=17"                 
[Age必须大于18 Address为必填字段]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&age=17&address=hangzhou&locale=en"
[Age must be greater than 18]
valid_v10 % curl -X GET "http://127.0.0.1:8080/stu?name=zhang&age=19&address=hangzhou"
{19 zhang hangzhou}

中间件 - 使用

package main

import (
	"github.com/gin-gonic/gin"
	"io"
	"os"
)

func main() {
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f)
	gin.DefaultErrorWriter = io.MultiWriter(f)
	r := gin.New()
	r.Use(gin.Logger(), gin.Recovery())
	r.GET("/test", func(c *gin.Context) {
		name := c.DefaultQuery("name", "zhang")
		panic("test painc")
		c.String(200, "%s", name)
	})
	r.Run()
}
测试
middleware_gin % curl -X GET "http://127.0.0.1:8080/test"
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /test                     --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080


[31m2022/03/22 20:38:46 [Recovery] 2022/03/22 - 20:38:46 panic recovered:
GET /test HTTP/1.1
Host: 127.0.0.1:8080
Accept: */*
User-Agent: curl/7.64.1


test painc
/Users/zhanghao/go/src/scripts/gin_test_project/middleware_gin/main.go:17 (0x153eaf0)
	main.func1: panic("test painc")
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x153cff9)
	(*Context).Next: c.handlers[c.index](c)
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/recovery.go:99 (0x153cfe0)
	CustomRecoveryWithWriter.func1: c.Next()
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x153c0d3)
	(*Context).Next: c.handlers[c.index](c)
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/logger.go:241 (0x153c092)
	LoggerWithConfig.func1: c.Next()
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:168 (0x1531faf)
	(*Context).Next: c.handlers[c.index](c)
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:555 (0x1531f95)
	(*Engine).handleHTTPRequest: c.Next()
/Users/zhanghao/go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/gin.go:511 (0x1531a4a)
	(*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/go/src/net/http/server.go:2887 (0x12a6a02)
	serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/go/src/net/http/server.go:1952 (0x12a1e2c)
	(*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/go/src/runtime/asm_amd64.s:1371 (0x106d660)
	goexit: BYTE	$0x90	// NOP
[0m
[GIN] 2022/03/22 - 20:38:46 | 500 |    5.549236ms |       127.0.0.1 | GET      "/test"

中间件 - 自定义中间件

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.Use(IPAuthMiddleware())
	r.GET("/test", func(c *gin.Context) {
		c.String(200, "test gin middleware")
	})
	r.Run()
}

func IPAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		ipList := []string{
			"127.0.0.2",
		}
		flag := false
		clientIP := c.ClientIP()
		for _, host := range ipList {
			if clientIP == host {
				flag = true
				break
			}
		}
		if !flag {
			c.String(401, "%s not in ipList", clientIP)
			c.Abort()
		}
	}
}
测试
middleware_whitelist % curl -X GET "http://127.0.0.1:8080/test"
127.0.0.1 not in ipList

优雅关停服务器

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		time.Sleep(time.Second * 10)
		c.String(200, "test shutdown")
	})
	srv := &http.Server{
		Addr:    ":8080",
		Handler: r,
	}
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	exit := make(chan os.Signal)
	signal.Notify(exit, syscall.SIGINT, syscall.SIGTERM) // 捕获Ctrl + C , kill -15
	<-exit
	log.Println("shutdown server...")
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) // 创建超时上下文
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("server shutdown:", err)
	}
	log.Println("server exiting")
}
测试
gin_shutdown % curl -X GET "http://127.0.0.1:8080/test"
test shutdown
^C2022/03/22 21:27:52 shutdown server...
[GIN] 2022/03/22 - 21:27:56 | 200 | 10.001138189s |       127.0.0.1 | GET      "/test"
2022/03/22 21:27:56 server exiting

模版渲染

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("template/*")
	r.GET("index", func(c *gin.Context) {
		c.HTML(200, "index.html", gin.H{
			"title": "index.html",
		})
	})
	r.Run()
}
测试
gin_template % curl -X GET "http://127.0.0.1:8080/index"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>
        index.html, test template
    </h1>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值