一、gin简介
Gin 是一个 go 写的 web 框架,具有高性能的优点。
二、快速使用
2.1 引入依赖
go get -u github.com/gin-gonic/gin
2.2 示例代码
type User struct {
USERNAME string `json:"username"`
}
func main() {
router := gin.Default()
router.POST("/hello", func(c *gin.Context) {
var user User
c.Bind(&user)
c.JSON(200, gin.H{
"user": user,
})
})
router.Run(":8080")
}
2.3验证
2.4 中间件
2.4.0 wrapper middle
type responseWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (rw responseWriter) Write(p []byte) (n int, err error) {
rw.body.Write(p)
return rw.ResponseWriter.Write(p)
}
func WrapperMiddle() gin.HandlerFunc {
return func(c *gin.Context) {
// 记录请求体
bodyBytes, _ := io.ReadAll(c.Request.Body)
// 替换请求体,可重复读
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 替换writer, 可以获取body
c.Writer = &responseWriter{
ResponseWriter: c.Writer,
body: bytes.NewBufferString(""),
}
}
}
2.4.1 access log 中间件
func LogMiddle() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
method := c.Request.Method
// 记录请求体
bodyBytes, _ := io.ReadAll(c.Request.Body)
// 请求处理
c.Next()
// 打印日志
reqBody := string(bodyBytes)
// 如果请求body 含有 password,则替换为****
reqBody = strings.ReplaceAll(reqBody, "password", "****")
respBody := string(c.Writer.(*responseWriter).body.Bytes())
slog.Info("method:[%s] path:%s reqBody:%s respBOdy:%s duration:%s\n", method, path, reqBody, respBody, time.Since(start))
}
}
2.4.2 recover中间件
func RecoverMiddle() gin.HandlerFunc {
return func(c *gin.Context) {
bodyBytes, _ := io.ReadAll(c.Request.Body)
// 替换请求体,可重复读
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
req := string(bodyBytes)
defer func(c *gin.Context) {
if err := recover(); err != nil {
// 打印错误日志
fmt.Printf("error:%v,path:%s body:%s", err, c.Request.URL.Path, req)
// http 请求返回错误
c.AbortWithStatusJSON(http.StatusInternalServerError, map[string]interface{}{"code": "InnerError", "msg": "出现内部错误"})
}
}(c)
c.Next()
}
}
2.4.3 cors 中间件
func CorsMiddle() gin.HandlerFunc {
corsConfig := struct {
AllowOrigins []string
AllowMethods []string
}{
AllowOrigins: []string{"baidu.com","vicyor.com"},
AllowMethods: []string{"POST","GET", "OPTIONS"},
}
return func(c *gin.Context) {
// 判断是否跨域
if origin := c.Request.Header.Get("Origin"); len(origin) > 0 {
oUrl,_ := url.Parse(origin)
oHost := oUrl.Host
oPort := oUrl.Port()
oScheme :=oUrl.Scheme
// 非同源
if oHost != "vicyor.com" || (oScheme != "http" && oPort != "8080") || (oScheme != "https" && oPort != "443"){
if !slices.Contains(corsConfig.AllowOrigins, oHost) {
c.AbortWithStatusJSON(http.StatusForbidden, c.Header)
return
}
if !slices.Contains(corsConfig.AllowMethods, c.Request.Method) {
c.AbortWithStatusJSON(http.StatusForbidden, c.Header)
return
}
}
}
c.Header("Access-Control-Allow-Origin", strings.Join(corsConfig.AllowOrigins, ","))
// 允许凭证,包括cookie 和 authorization
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Allow-Methods", strings.Join(corsConfig.AllowMethods, ","))
c.Header("Access-Control-Allow-Headers", "Authorization,Content-Type")
// 没有跨域,option请求直接返回
if c.Request.Method == "OPTIONS" { // 如果是预检请求,直接返回
c.AbortWithStatus(http.StatusNoContent)
return
}
}
}
1. 判断是否同源,如果同源,直接通过。
2.非同源,判断host,method是否在自定义跨域配置中。
若不在,返回403
3.判断是否是option请求,如果是option请求,直接返回200 + 没有内容。