从 Flask 到 Gin —— 装饰器和中间件

文章目录

本文是 从 Flask 到 Gin 系列的第 5 篇。


Python 提供了一个有趣且有用的语法糖:装饰器。使用装饰器,我使用装饰器在 Flask 中实现了鉴权:用户进行数据提交的时候,需要提供一个 TOKEN,这个 TOKEN 如果解密成功,就正常进行 response 相关的逻辑,反之则直接返回 403 错误。

鉴权逻辑就是使用 Python 的装饰器实现的。但 Golang 没有装饰器特性,我会使用 Gin 的中间件机制来代替它。

实现 mjst_checker 装饰器

mjst_checker 是一个装饰器,经过它装饰的路由方法,会自动处理请求中带来的名为 mjst 的 token,token 中包含用户的权限信息,还有 token 有效期等等信息,如果信息正常,那么路由会继续正常处理,否则会得到 403 错误。

来看看 mjst_checker 装饰器的定义:

 1from functools import wraps
 2
 3class PYConf(dict):
 4    """基于 Python dict 的配置文件。
 5
 6    dict 默认不适合当作配置文件对象使用。如要有下面几点不便:
 7
 8    #. 对于不存在的 key,会 raise KeyError 错误;
 9    #. dict不能使用 ``.`` 语法访问。
10
11    :class:`PYConf` 解决了这些问题,还另外提供了一些方法在使用上更加方便。
12
13    """
14
15    def __missing__(self, key):
16        return None
17
18    def __getattr__(self, name):
19        return self[name]
20
21    def __setattr__(self, name, value):
22        self[name] = value
23
24    def __delattr__(self, name):
25        del self[name]
26
27
28def mjst_checker(*typeids):
29    """ MJST 的检测器,检测 HEADER 中传来的 MJST 是否正常
30    :param typeids: typeid 数组
31    """
32
33    def decorator(f):
34        @wraps(f)
35        def decorated_fun(*args, **kwargs):
36            # _get_mjst 从请求中获取 mjst 的值
37            mjst = _get_mjst()
38            # 将要加入 kwargs 中的mjst解析后的值,默认是一个错误,代表没有解析成功
39            mjstarg = PYConf({'error': True, 'code': 403, 'message': 'MJST DECODE ERROR!'})
40            try:
41                # _decode_mjst 为具体的解密方法
42                mjstobj = _decode_mjst(typeids, mjst)
43                if mjstobj is not None:
44                    mjstarg = mjstobj
45            except Exception as e:
46                logger.error('@mjst_checker decode_mjst:%s error:%s', mjst, str(e))
47            # 此处的 mjstarg 一定为非 None,至少包含上面提供的 403 错误。这里不使用 HTTP 403,让客户端知道出了什么错
48            kwargs['mjst'] = mjstarg
49            return f(*args, **kwargs)
50
51        return decorated_fun
52
53    return decorator

其中涉及的 _get_mjst_decode_mjst 方法的作用已经在注释中解释过了,具体实现就不提供了。让我们看看 mjst_checker 这个装饰器应该怎么使用。

mjst_checker 的用法

从 Flask 到 Gin —— SQLAlchemy 和 gorm 中我们定义了两个路由方法,registeractive 是没有增加鉴权功能的,现在可以补全了:

 1@audible.route('/register/', methods=['GET'])
 2@mjst_checker(51, 55)
 3def register(mjst):
 4    """ 获取注册数据
 5    """
 6    if mjst.error:
 7        return responseto(data=mjst)
 8    return _response_register_or_active(LogRegister)
 9
10
11@audible.route('/active/', methods=['GET'])
12@mjst_checker(51, 55)
13def active(mjst):
14    """ 获取活跃数据
15    """
16    if mjst.error:
17        return responseto(data=mjst)
18    return _response_register_or_active(LogActive)

如果 mjst 解析失败,得到的响应是一个 http code 200,但返回的 json 中的 code 值为 403。responseto 的具体定义见 从 Flask 到 Gin —— 处理 JSON

实现 MjstChecker 中间件

Gin 为我们提供了中间件支持,让我们可以在请求收到以及响应之间做许多事。看看 MjstChecker 这个中间件的定义:

 1package middlewares
 2
 3import (
 4	"net/http"
 5	"github.com/gin-gonic/gin"
 6)
 7// MjstChecker 检查 mjst 的权限是否匹配
 8// typeids 支持的 usertype
 9func MjstChecker(typeids []int) gin.HandlerFunc {
10	return func(c *gin.Context) {
11        // getMjst 从请求中获取 mjst 的值
12		mjstObj := getMjst(typeids, c)
13		if mjstObj == nil {
14			c.AbortWithStatusJSON(http.StatusOK, gin.H{
15				"error":   true,
16				"code":    403,
17				"message": "MJST DECODE ERROR!",
18			})
19			return
20		}
21		c.Set("mjst", mjstObj)
22		c.Next()
23	}
24}

getMjst 在注释中已经说明了用途,也就不提供具体定义了。和 Python 版本不同的是,这里把获取 mjst 请求参数以及解密 token 写到了一个方法中。如果解析失败的话,会直接返回一个 code 403 的 JSON 响应。

MjstChecker 的用法

从 Flask 到 Gin —— SQLAlchemy 和 gorm 中,我们定义了初始化路由的方法 InitAudible。现在进行一点点修改,将 MjstChecker 这个中间件串联到路由方法前面,增加鉴权功能:

 1package routers
 2
 3import (
 4	"mjp/middlewares"
 5	"github.com/gin-gonic/gin"
 6)
 7// InitAudible register the audible api
 8func InitAudible(router *gin.RouterGroup) {
 9    router.GET("/register", middlewares.MjstChecker([]int{51, 55}), AudibleRegister)
10	router.GET("/active", middlewares.MjstChecker([]int{51, 55}), AudibleActive)
11}

如果鉴权失败,客户端收到的结果为:

1{
2  "code": 403,
3  "error": true,
4  "message": "MJST DECODE ERROR!"
5}

参考


阅读系列所有文章:从 Flask 到 Gin

全文完