• [go]gin中间件


    Go Web轻量级框架Gin学习系列:中间件使用详解

    gin中间件使用

    - 中间件的作用:
    	请求到达http请求处理方法之前,拦截请求
    		认证
    		权限校验
    		限流
    		数据过滤
    		ip白名单
    	处理完请求后,拦截响应,冰进行相应的处理
    		统一添加响应头
    		数据过滤
    
    
    - 中间件加的位置
    	全局加
    	路由组加
    	路由明细加
    
    - 默认使用了Logger(), Recovery()全局作用了两个中间件.
    r:=gin.Default()
    
    func Default() *Engine {
    	debugPrintWARNINGDefault()
    	engine := New()
    	engine.Use(Logger(), Recovery())
    	return engine
    }
    
    
    - gin自带默认有这些中间件
    func BasicAuth(accounts Accounts) HandlerFunc
    func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
    func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定
    func ErrorLogger() HandlerFunc       //错误日志处理
    func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理
    func Logger() HandlerFunc //日志记录
    func LoggerWithConfig(conf LoggerConfig) HandlerFunc
    func LoggerWithFormatter(f LogFormatter) HandlerFunc
    func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
    func Recovery() HandlerFunc
    func RecoveryWithWriter(out io.Writer) HandlerFunc
    func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件
    func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件
    
    //去除默认全局中间件
    r := gin.New()//不带中间件
    
    - 全局中间件
    func main() {
    	r := gin.Default()
    
    	r.Use(func(c *gin.Context) {
    		fmt.Println("hello start")
    	})
    
    	r.GET("/", func(c *gin.Context) {
    		c.JSON(200, gin.H{"name": "m1"})
    	})
    
    	r.Run()
    }
    
    
    - 或者
    func M1(c *gin.Context) {
    	fmt.Println("hello start")
    }
    
    func main() {
    	r := gin.Default()
    
    	r.Use(M1)
    
    	r.GET("/", func(c *gin.Context) {
    		c.JSON(200, gin.H{"name": "m1"})
    	})
    
    	r.Run()
    }
    
    
    - 路由分组中使用中间件
    
    func main() {
    	r := gin.Default()
    	v1 := r.Group("/v1", gin.Logger(), gin.Recovery())
    	{
    		v1.GET("/", func(c *gin.Context) {
    			c.JSON(200, gin.H{"name": "m1"})
    		})
    		v1.GET("/test", func(c *gin.Context) {
    			c.JSON(200, gin.H{"name": "m1 test"})
    		})
    	}
    	
    	r.Run()
    }
    
    
    - 单个路由使用中间件
    
    func main() {
    	r := gin.Default()
    	r.GET("/", gin.Recovery(), gin.Logger(), func(c *gin.Context) {
    		c.JSON(200, gin.H{"name": "m1"})
    	})
    	r.Run()
    }
    
    
    - 自定义中间件
    
    //方法1
    func MyMiddleware(c *gin.Context) {
    	//中间件逻辑
    	fmt.Println("hello")
    }
    
    func main() {
    	r := gin.Default()
    
    	r.Use(MyMiddleware)
    
    	r.GET("/", func(c *gin.Context) {
    		c.JSON(200, gin.H{"name": "m1"})
    	})
    	r.Run()
    }
    
    
    
    //方法2: 返回一个中间件函数
    
    Gin框架自带的中间件方法,都是返回HandlerFunc类型
    type HandlerFunc func(*Context)
    
    
    func MyMiddleware() func(c *gin.Context) {
    	//自定义逻辑
    	fmt.Println("requesting...") //中间件不打印
    	//返回中间件
    	return func(c *gin.Context) {  
    		//中间件逻辑
    		fmt.Println("test2")
    	}
    }
    
    
    func main() {
    	r := gin.Default()
    
    	r.Use(MyMiddleware()) //加括号
    
    	r.GET("/", func(c *gin.Context) {
    		c.JSON(200, gin.H{"name": "m1"})
    	})
    	r.Run()
    }
    
    - 值传递
    
    func MyMiddleware(c *gin.Context) {
    	c.Set("mykey", 10)
    	c.Set("mykey2", "m1")
    }
    func main() {
    	//自定义中间件
    	r := gin.New()
    	r.GET("", MyMiddleware, func(c *gin.Context) {
    		mykey := c.GetInt("mykey") //我们知道设置进行的是整型,所以使用GetInt方法来获取
    		mykey2 := c.GetString("mykey2")
    		c.JSON(200, gin.H{
    			"mykey":  mykey,
    			"mykey2": mykey2,
    		})
    	})
    	r.Run()
    }
    
    
    //gin set get取参数
    func (c *Context) Set(key string, value interface{})
    //判断key是否存在 c.Get
    func (c *Context) Get(key string) (value interface{}, exists bool)
    
    func (c *Context) GetBool(key string) (b bool)
    func (c *Context) GetDuration(key string) (d time.Duration)
    func (c *Context) GetFloat64(key string) (f64 float64)
    func (c *Context) GetInt(key string) (i int)
    func (c *Context) GetInt64(key string) (i64 int64)
    func (c *Context) GetString(key string) (s string)
    func (c *Context) GetStringMap(key string) (sm map[string]interface{})
    func (c *Context) GetStringMapString(key string) (sms map[string]string)
    func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string)
    func (c *Context) GetStringSlice(key string) (ss []string)
    func (c *Context) GetTime(key string) (t time.Time)
    
    func (c *Context) MustGet(key string) interface{} //必须有, 否则panic
    
    //gin set get取参数 将值放在了context的keys键上(源码里context.go 叫: METADATA MANAGEMENT)
    
    func main() {
    	r := gin.Default()
    	r.GET("/", func(c *gin.Context) {
    		c.Set("name", "mm")
    		fmt.Printf("%#v
    ", c)
    		c.JSON(200, gin.H{"name": "m1"})
    	})
    	r.Run()
    }
    
    &gin.Context{
    	writermem:gin.responseWriter{ResponseWriter:(*http.response)(0xc00024c1c0),
    									size:-1,
    									status:200
    								},
    	Request:(*http.Request)(0xc000208100),
    	Writer:(*gin.responseWriter)(0xc00020c410),
    	Params:gin.Params(nil),
    	handlers:gin.HandlersChain{(gin.HandlerFunc)(0x8fbd50),
    	(gin.HandlerFunc)(0x8fccc0),
    	(gin.HandlerFunc)(0x8fe720)},
    	index:2,
    	fullPath:"/",
    	engine:(*gin.Engine)(0xc000222280),
    	Keys:map[string]interface {}{"name":"mm"},   //放在了这个map里
    	Errors:gin.errorMsgs(nil),
    	Accepted:[]string(nil),
    	queryCac
    	he:url.Values(nil),
    	formCache:url.Values(nil)
    }
    
    
    - 拦截器
    
    func MyMiddleware(c *gin.Context){
        //请求前逻辑
        c.Next()
        //请求后逻辑
    }
    
    - gin内置的几个中断用户请求的方法: 返回200,但body里没数据
    func (c *Context) Abort()
    func (c *Context) AbortWithError(code int, err error) *Error
    func (c *Context) AbortWithStatus(code int)
    
    func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{}) //中断请求后,返回json格式的数据
    
    // 示例1:
    func MyMiddleware(c *gin.Context) {
    	c.Set("key", 1000) //请求前
    	c.Next()
    	c.JSON(http.StatusOK, c.GetInt("key")) //请求后
    }
    func main() {
    	r := gin.New()
    	r.GET("test", MyMiddleware, func(c *gin.Context) {
    		k := c.GetInt("key")
    		c.Set("key", k+2000)
    	})
    	r.Run()
    }
    
    

    实例

    gin.BasicAuth中间件

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    //type HandlerFunc func(*Context)
    
    // 模拟一些私人数据
    var secrets = gin.H{
    	"foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
    	"austin": gin.H{"email": "austin@example.com", "phone": "666"},
    	"lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
    }
    
    func main() {
    
    	r := gin.Default()
    
    	r.GET("/", func(c *gin.Context) {
    		c.JSON(200, secrets)
    		//c.String(200, "index")
    	})
    	// 为/admin路由组设置auth
    	// 路由组使用 gin.BasicAuth() 中间件
    	// gin.Accounts 是 map[string]string 的一种快捷方式
    	authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
    		"foo":    "bar",
    		"austin": "1234",
    		"lena":   "hello2",
    		"manu":   "4321",
    	}))
    
    	// /admin/secrets 端点
    	// 触发 "localhost:8080/admin/secrets
    	authorized.GET("/secrets", func(c *gin.Context) {
    		// 获取用户,它是由 BasicAuth 中间件设置的
    		user := c.MustGet(gin.AuthUserKey).(string)
    		if secret, ok := secrets[user]; ok {
    			c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
    		} else {
    			c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
    		}
    	})
    
    	// 监听并在 0.0.0.0:8080 上启动服务
    	r.Run(":8080")
    }
    

    参考

    一文读懂HTTP Basic身份认证

    - http base认证
    
    str=base64(admin:123456)
    //request请求头部携带这个
    Authorization: Basic YWRtaW46MTIzNDU2
    
    - 步骤1:用户访问受限资源
    GET /protected_docs HTTP/1.1
    Host: 127.0.0.1:3000
    
    - 步骤2:服务端返回401要求身份认证
    HTTP/1.1 401 Unauthorized
    WWW-Authenticate: Basic realm=protected_docs
    
    - 步骤3:用户发送认证请求
    GET /protected_docs HTTP/1.1
    Authorization: Basic Y2h5aW5ncDoxMjM0NTY=
    
    Authorization首部的格式为Basic base64(userid:password)实际代码如下:
    Buffer.from('chyingp:123456').toString('base64'); // Y2h5aW5ncDoxMjM0NTY=
    
    - 步骤4:服务端验证请求
    服务端收到用户的认证请求后,对请求进行验证。验证包含如下步骤:
    
        根据用户请求资源的地址,确定资源对应的realm。
        解析 Authorization 请求首部,获得用户名、密码。
        判断用户是否有访问该realm的权限。
        验证用户名、密码是否匹配。
    一旦上述验证通过,则返回请求资源。如果验证失败,则返回401要求重新认证,或者返回403(Forbidden)。
    
    缺陷:
    1.密码明文传输
    2.无法吊销认证
    

    中间件计算一次请求的耗时: c.Next前置-后置

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    	"log"
    	"net/http"
    	"time"
    )
    
    //type HandlerFunc func(*Context)
    func StatCost() gin.HandlerFunc {
        return func(c *gin.Context) {
            t := time.Now()
    
            //可以设置一些公共参数
            c.Set("example", "12345")
            //等其他中间件先执行
            c.Next()
            //获取耗时
            latency := time.Since(t)
            log.Printf("total cost time:%d us", latency/1000)
        }
    }
    
    func main() {
        //r := gin.New()
        r := gin.Default()
        r.Use(StatCost())
    
        r.GET("/", func(c *gin.Context) {
            example := c.MustGet("example").(string)
    
            // it would print: "12345"
            log.Println(example)
            c.JSON(http.StatusOK, gin.H{
                "message": "success",
            })
        })
    
        // Listen and serve on 0.0.0.0:8080
        r.Run()
    }
    
  • 相关阅读:
    centos6.5 mysql配置整理
    第四章 Web表单
    第三章 模板
    第二章 程序的基本结构
    第一章 安装
    常见网络错误代码(转)
    微软消息队列MessageQueue(MQ)
    基于.NET平台常用的框架整理(转)
    Sqlserver更新数据表xml类型字段内容某个节点值的脚本
    正则表达式_基础知识集合
  • 原文地址:https://www.cnblogs.com/iiiiiher/p/12202091.html
Copyright © 2020-2023  润新知