框架之 MIDDLEWARE

1 项目概述

1.1 背景介绍及目标

1.1.1 什么是中间件

中间件可以理解为是介于 HttpRequest 与 HttpResponse 处理之间的一道处理过程。

+---------------+             +----------------+
|  HttpRequest  |             |  Httpresponse  |
+-------+-------+             +--------^-------+
        |                              |
+-------|------------------------------|-------+
| +-----|------------------------------|-----+ |
| |     |        middleware 1          |     | |
| +-----|------------------------------|-----+ |
|       |                              |       |
| +-----|------------------------------|-----+ |
| |     |        middleware 2          |     | |
| +-----|------------------------------|-----+ |
+-------|------------------------------|-------+
        |                              |
+-------V------------------------------+-------+
|              Butterfly Handler               |
+----------------------------------------------+

中间件一般对全局有效的,可以在全局范围内改变输入和输出结果,因此需要谨慎使用,否则不仅会造成难以定位的错误,而且可能会影响整体性能。

1.1.2 中间件有什么用

如果想要修改 HttpRequest 或者 HttpResponse,就可以通过中间件来实现。

  • 登陆认证:在中间件中加入登陆认证,所有请求就自动拥有登陆认证,如果需要放开部分路由,只需要特殊处理就可以了。

  • 流量统计:可以针对一些渲染页面统计访问流量。

  • 恶意请求拦截:统计 IP 请求次数,可以进行频次限制或者封禁 IP。

1.2 名词说明

1.3 Roadmap

2 需求分析

需求分析重点是需求

2.1 功能需求

2.2 非功能需求

2.3 调研

2.3.1 Bottle 插件

2.3.1.1 Bottle 插件说明

bottle 是通过插件丰富装饰器,进而增加 Handler 的功能

Bottle 插件列表

2.3.1.2 Bottle 内置插件

class JSONPlugin(object):
    name = 'json'
    api = 2

    def __init__(self, json_dumps=json_dumps):
        self.json_dumps = json_dumps

    def setup(self, app):
        app.config._define('json.enable', default=True, validate=bool,
                          help="Enable or disable automatic dict->json filter.")
        app.config._define('json.ascii', default=False, validate=bool,
                          help="Use only 7-bit ASCII characters in output.")
        app.config._define('json.indent', default=True, validate=bool,
                          help="Add whitespace to make json more readable.")
        app.config._define('json.dump_func', default=None,
                          help="If defined, use this function to transform"
                               " dict into json. The other options no longer"
                               " apply.")

    def apply(self, callback, route):
        dumps = self.json_dumps
        if not self.json_dumps: return callback

        def wrapper(*a, **ka):
            try:
                rv = callback(*a, **ka)
            except HTTPResponse as resp:
                rv = resp

            if isinstance(rv, dict):
                #Attempt to serialize, raises exception on failure
                json_response = dumps(rv)
                #Set content type only if serialization successful
                response.content_type = 'application/json'
                return json_response
            elif isinstance(rv, HTTPResponse) and isinstance(rv.body, dict):
                rv.body = dumps(rv.body)
                rv.content_type = 'application/json'
            return rv

        return wrapper


class TemplatePlugin(object):
    """ This plugin applies the :func:`view` decorator to all routes with a
        `template` config parameter. If the parameter is a tuple, the second
        element must be a dict with additional options (e.g. `template_engine`)
        or default variables for the template. """
    name = 'template'
    api = 2

    def setup(self, app):
        app.tpl = self

    def apply(self, callback, route):
        conf = route.config.get('template')
        if isinstance(conf, (tuple, list)) and len(conf) == 2:
            return view(conf[0], **conf[1])(callback)
        elif isinstance(conf, str):
            return view(conf)(callback)
        else:
            return callback

bottle 中每个路由规则都会创建一个 Route 对象,主要包括了 path,method 和 callback 三个部分。

+Router---------------------------------+ <---------------- Router 类似 Butterfly 中的 Route
| +Route1-----------------------------+ | <---------------- Route 类似 Butterfly 中的 Protocol
| | callback                          | | <---------------- callback 类似 Butterfly 中的 handler
| +-----------------------------------+ |
| +Route2-----------------------------+ |
| | callback                          | |
| +-----------------------------------+ |
+---------------------------------------+

2.3.2 Django 中间件

2.3.2.1 Django 中间件使用

当 Django 处理一个 Request 的过程是首先通过中间件,然后再通过默认的 URL 方式进行的

在 django 项目的 settings 模块中,有一个 MIDDLEWARE_CLASSES 变量,其中每一个元素就是一个中间件

中间件中可以定义五个方法

  • Request 预处理函数:process_request(self, request)

  • View 预处理函数:process_view(self, request, callback, callback_args,callback_kwargs)

  • Template 模版渲染函数:process_template_response() (不常用)

  • Exception 后处理函数:process_exception(self, request, exception)(不常用)

  • Response 后处理函数:process_response(self, request, response)

以上方法的返回值可以是 None 或一个 HttpResponse 对象,如果是 None,则继续按照 django 定义的规则向后继续执行,如果是 HttpResponse 对象,则直接将该对象返回给用户。

前二个方法是从前往后执行的,后三个方法是从后往前执行的,具体流程如下:

django-middleware
def make_middleware_decorator(middleware_class):
    def _make_decorator(*m_args, **m_kwargs):
        middleware = middleware_class(*m_args, **m_kwargs)

        def _decorator(view_func):
            @wraps(view_func, assigned=available_attrs(view_func))
            def _wrapped_view(request, *args, **kwargs):
                # Request 预处理函数
                if hasattr(middleware, 'process_request'):
                    result = middleware.process_request(request)
                    if result is not None:
                        return result
                # View 预处理函数
                if hasattr(middleware, 'process_view'):
                    result = middleware.process_view(request, view_func, args, kwargs)
                    if result is not None:
                        return result
                try:
                    response = view_func(request, *args, **kwargs)
                except Exception as e:
                    # Exception 后处理函数
                    if hasattr(middleware, 'process_exception'):
                        result = middleware.process_exception(request, e)
                        if result is not None:
                            return result
                    raise
                if hasattr(response, 'render') and callable(response.render):
                    # Template 模版渲染函数
                    if hasattr(middleware, 'process_template_response'):
                        response = middleware.process_template_response(request, response)
                    # Defer running of process_response until after the template
                    # has been rendered:

                    # Response 后处理函数
                    if hasattr(middleware, 'process_response'):
                        def callback(response):
                            return middleware.process_response(request, response)
                        response.add_post_render_callback(callback)
                else:
                    # Response 后处理函数
                    if hasattr(middleware, 'process_response'):
                        return middleware.process_response(request, response)
                return response
            return _wrapped_view
        return _decorator
    return _make_decorator

2.3.2.2 Django 中间件请求流程

2.3.2.2.1 Django WSGI 入口

django/core/handlers/base.py:BaseHandler
    |
    +---django/core/handlers/wsgi.py:WSGIHandler

在初始化 WSGIHandler 时,程序会加载 middleware 插件

2.3.2.2.2 Django middleware 执行顺序

django-middleware-flow

从 django1.10 版本起,middleware 是继承自 django/utils/deprecation.py 中的 MiddlewareMixin 类,这是一个可调用的对象,其代码如下:

class MiddlewareMixin(object):
    def __init__(self, get_response=None):
        self.get_response = get_response
        super(MiddlewareMixin, self).__init__()

    def __call__(self, request):
        response = None
        if hasattr(self, 'process_request'):
            response = self.process_request(request)
        if not response:
            response = self.get_response(request)
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response

process_request(request)

参数为 HttpRequest 对象,在到达 view 之前,process_request(request) 函数依次被执行。
process_request(request) 函数的返回值为 None 或者 HttpResponse,
如果反回 None, 则继续执行剩下中间件的 process_request(request),
如果所有 process_request(request) 都返回 None, 则顺序执行中间件的 process_view() ,最后到的 view。

process_response(request,response)

process_response() 函数的参数分别问 HttpRequest 对象与 HttpResponse 对象或者 StreamingHttpResponse 对象。
process_response() 总是会被执行,并且返回一个 HttpResponse 或者 StreamingHttpResponse,效果参见 process_template_response() 函数。

process_exception(request,response)

只有当 view 抛出异常的时候才会触发这个函数

2.3.2.3 Django 的中间件与 Python 的装饰器

装饰器是 Python 的一种语法应用,利用闭包的原理去更改一个函数的功能,即让一个函数执行之前先到另外一个函数中执行其他需求语句,再执行该函数

联系:中间件是利用装饰器原理实现的,具体通过两个函数嵌套、或通过改写类中的__ call __方法实现一个装饰器功能。使之过来的 Request 的请求,先到 call 方法中进行处理。配置中间件,实现过程干预。 区别:一个是 Python 的语法功能,一个是 Django 框架自带的轻量级插件系统。

2.3.2.4 Django middleware 例子

from  django.utils.deprecation import MiddlewareMixin


class M1(MiddlewareMixin):
    def process_request(self, request):
        print('M1.request')

    def process_view(self, request,callback,callback_args,callback_kwargs ):
        print("M1.process_view")

    def process_response(self, request, response):
        print('M1.response')
        return response



class M2(MiddlewareMixin):
    def process_request(self, request):
        print('M2.request')

    def process_view(self, request,callback,callback_args,callback_kwargs ):
        print("M2.process_view")

    def process_response(self, request, response):
        print('M2.response')
        return response

2.3.3 Flask

视图装饰器

https://flask.palletsprojects.com/en/2.0.x/quickstart/?highlight=middle#hooking-in-wsgi-middleware

2.3.3.1 例子

Creating Middlewares with Python Flask

middleware.py

from werkzeug.wrappers import Request, Response, ResponseStream

class middleware():
    '''
    Simple WSGI middleware
    '''

    def __init__(self, app):
        self.app = app
        self.userName = 'Tony'
        self.password = 'IamIronMan'

    def __call__(self, environ, start_response):
        request = Request(environ)
        userName = request.authorization['username']
        password = request.authorization['password']
        
        # these are hardcoded for demonstration
        # verify the username and password from some database or env config variable
        if userName == self.userName and password == self.password:
            environ['user'] = { 'name': 'Tony' }
            return self.app(environ, start_response)

        res = Response(u'Authorization failed', mimetype= 'text/plain', status=401)
        return res(environ, start_response)

server.py

from flask import Flask, request
from middleware import middleware

app = Flask('DemoApp')

# calling our middleware
app.wsgi_app = middleware(app.wsgi_app)

@app.route('/', methods=['GET', 'POST'])
def hello():
    # using
    user = request.environ['user']
    return "Hi %s"%user['name']

if __name__ == "__main__":
    app.run('127.0.0.1', '5000', debug=True)

2.3.3.2 源码解读

class Flask(Scaffold):
    ...
    def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
        """The actual WSGI application. This is not implemented in
        :meth:`__call__` so that middlewares can be applied without
        losing a reference to the app object. Instead of doing this::
            app = MyMiddleware(app)
        It's a better idea to do this instead::
            app.wsgi_app = MyMiddleware(app.wsgi_app)
        Then you still have the original application object around and
        can continue to call methods on it.
        .. versionchanged:: 0.7
            Teardown events for the request and app contexts are called
            even if an unhandled error occurs. Other events may not be
            called depending on when an error occurs during dispatch.
            See :ref:`callbacks-and-errors`.
        :param environ: A WSGI environment.
        :param start_response: A callable accepting a status code,
            a list of headers, and an optional exception context to
            start the response.
        """
        ctx = self.request_context(environ)
        error: t.Optional[BaseException] = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

    def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
        """The WSGI server calls the Flask application object as the
        WSGI application. This calls :meth:`wsgi_app`, which can be
        wrapped to apply middleware.
        """
        return self.wsgi_app(environ, start_response)

2.3.4 Gin(GO)

厂内大部分使用 Gin 框架

2.3.4.1 全局中间件

package main

import (
    "fmt"
    "time"

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

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到 Context 的 key 中,可以通过 Get() 取
        c.Set("request", "中间件")
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1. 创建路由
    // 默认使用了 2 个中间件 Logger(), Recovery()
    r := gin.Default()
    // 注册中间件
    r.Use(MiddleWare())
    // {}为了代码规范
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}

2.3.4.2 Next() 方法

package main

import (
    "fmt"
    "time"

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

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到 Context 的 key 中,可以通过 Get() 取
        c.Set("request", "中间件")
        // 执行函数
        c.Next()
        // 中间件执行完后续的一些事情
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1. 创建路由
    // 默认使用了 2 个中间件 Logger(), Recovery()
    r := gin.Default()
    // 注册中间件
    r.Use(MiddleWare())
    // {}为了代码规范
    {
        r.GET("/ce", func(c *gin.Context) {
            // 取值
            req, _ := c.Get("request")
            fmt.Println("request:", req)
            // 页面接收
            c.JSON(200, gin.H{"request": req})
        })

    }
    r.Run()
}

2.3.4.3 局部中间件

package main

import (
    "fmt"
    "time"

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

// 定义中间
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        t := time.Now()
        fmt.Println("中间件开始执行了")
        // 设置变量到 Context 的 key 中,可以通过 Get() 取
        c.Set("request", "中间件")
        // 执行函数
        c.Next()
        // 中间件执行完后续的一些事情
        status := c.Writer.Status()
        fmt.Println("中间件执行完毕", status)
        t2 := time.Since(t)
        fmt.Println("time:", t2)
    }
}

func main() {
    // 1. 创建路由
    // 默认使用了 2 个中间件 Logger(), Recovery()
    r := gin.Default()
    // 局部中间键使用
    r.GET("/ce", MiddleWare(), func(c *gin.Context) {
        // 取值
        req, _ := c.Get("request")
        fmt.Println("request:", req)
        // 页面接收
        c.JSON(200, gin.H{"request": req})
    })
    r.Run()
}

2.3.4.4 中间件推荐

https://github.com/gin-gonic/contrib/blob/master/README.md

  • RestGate - REST API 端点的安全身份验证

  • staticbin - 用于从二进制数据提供静态文件的中间件 / 处理程序

  • gin-cors - CORS 杜松子酒的官方中间件

  • gin-csrf - CSRF 保护

  • gin-health - 通过 gocraft/health 报告的中间件

  • gin-merry - 带有上下文的漂亮 打印 错误的中间件

  • gin-revision - 用于 Gin 框架的修订中间件

  • gin-jwt - 用于 Gin 框架的 JWT 中间件

  • gin-sessions - 基于 mongodb 和 mysql 的会话中间件

  • gin-location - 用于公开服务器的主机名和方案的中间件

  • gin-nice-recovery - 紧急恢复中间件,可让您构建更好的用户体验

  • gin-limit - 限制同时请求;可以帮助增加交通流量

  • gin-limit-by-key - 一种内存中的中间件,用于通过自定义键和速率限制访问速率。

  • ez-gin-template - gin 简单模板包装

  • gin-hydra - gin 中间件 Hydra

  • gin-glog - 旨在替代 Gin 的默认日志

  • gin-gomonitor - 用于通过 Go-Monitor 公开指标

  • gin-oauth2 - 用于 OAuth2

  • static gin 框架的替代静态资产处理程序。

  • xss-mw - XssMw 是一种中间件,旨在从用户提交的输入中“自动删除 XSS”

  • gin-helmet - 简单的安全中间件集合。

  • gin-jwt-session - 提供 JWT / Session / Flash 的中间件,易于使用,同时还提供必要的调整选项。也提供样品。

  • gin-template - 用于 gin 框架的 html / template 易于使用。

  • gin-redis-ip-limiter - 基于 IP 地址的请求限制器。它可以与 redis 和滑动窗口机制一起使用。

  • gin-method-override - _method 受 Ruby 的同名机架启发而被 POST 形式参数覆盖的方法

  • gin-access-limit - limit- 通过指定允许的源 CIDR 表示法的访问控制中间件。

  • gin-session - 用于 Gin 的 Session 中间件

  • gin-stats - 轻量级和有用的请求指标中间件

  • gin-statsd - 向 statsd 守护进程报告的 Gin 中间件

  • gin-health-check - check- 用于 Gin 的健康检查中间件

  • gin-session-middleware - 一个有效,安全且易于使用的 Go Session 库。

  • ginception - 漂亮的例外页面

  • gin-inspector - 用于调查 http 请求的 Gin 中间件。

  • gin-dump - Gin 中间件 / 处理程序,用于转储请求和响应的标头 / 正文。对调试应用程序非常有帮助。

  • go-gin-prometheus - Gin Prometheus metrics exporter

  • ginprom - Gin 的 Prometheus 指标导出器

  • gin-go-metrics - Gin middleware to gather and store metrics using rcrowley/go-metrics

  • ginrpc - Gin 中间件 / 处理器自动绑定工具。通过像 beego 这样的注释路线来支持对象注册

2.3.5 st2

st2/st2common/st2common/middleware/request_id.py

from __future__ import absolute_import
import uuid

from webob.headers import ResponseHeaders

from st2common.constants.api import REQUEST_ID_HEADER
from st2common.router import Request


class RequestIDMiddleware(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        # The middleware adds unique `X-Request-ID` header on the requests that don't have it and
        # modifies the responses to have the same exact header as their request. The middleware
        # helps us better track relation between request and response in places where it might not
        # be immediately obvious (like logs for example). In general, you want to place this header
        # as soon as possible to ensure it's present by the time it's needed. Certainly before
        # LoggingMiddleware which relies on this header.
        request = Request(environ)

        if not request.headers.get(REQUEST_ID_HEADER, None):
            req_id = str(uuid.uuid4())
            request.headers[REQUEST_ID_HEADER] = req_id

        def custom_start_response(status, headers, exc_info=None):
            headers = ResponseHeaders(headers)

            req_id_header = request.headers.get(REQUEST_ID_HEADER, None)
            if req_id_header:
                headers[REQUEST_ID_HEADER] = req_id_header

            return start_response(status, headers._items, exc_info)

        return self.app(environ, custom_start_response)

3 总体设计

总体设计重点是设计与折衷

3.1 系统架构

一般来说会有个简单的架构图,并配以文字对架构进行简要说明;

                                                     Redis RQ
            |                                           ^
            |                                           |
            | HTTP Request                              | Async Request(MQ)
            |                                           |
            V                                           |
----xlib/httpgateway.py----                 ----xlib/mq/worker.py----
                                    |
                                    |
                                    | Request
                                    |
                                    V
+self.apicube--------------------------------------------------------+
|                                                                    |
| +protocol_json---------------------------------+ +protocol_file--+ |
| |                             +---+            | |               | |
| |                             | M |            | |               | |
| |                             | I |            | |               | |
| |             req             | D |            | |               | |
| |---------------------------->| D |            | |               | |
| |                             | I |  handler1  | |    handler3   | |
| |                             | E |  handler2  | |    handler4   | |
| |httpstatus, headers, content | W |            | |               | |
| |<----------------------------| A |            | |               | |
| |                             | R |            | |               | |
| |                             | E |            | |               | |
| |                             +---+            | |               | |
| +----------------------------------------------+ +---------------+ |
+--------------------------------------------------------------------+

3.2 模块简介

架构图中如果有很多模块,需要对各个模块的功能进行简要介绍;

3.3 设计与折衷

设计与折衷是总体设计中最重要的部分;

3.3.1 middleware 添加的位置

  • 方案一:wsgi application(flask)

    • Butterfly 不仅需要支持 Http 请求,还需要支持异步处理 MQ 的请求,故仅在 wsgi application 处做过滤不可行

  • 方案二:protocol_json

折衷考虑放到 protocol_json 处, 即方案二

3.3.2 middleware 实现方式

  • 方案一:创建 protocol_json 时,通过参数进行传入 middleware

  • 方案二:protocol_json 改造, 通过类 flask 方式进行加载

    • app.wsgi_app = middleware(app.wsgi_app)

方案二对代码侵入性较小,故使用方案二

# 改造前
class Protocol(object):
    ...
    def __call__(self, req):
        ...
        return httpstatus, headers, content
        
# 改造后
class Protocol(object):
    ...
    def do_process(self, req):
        ...
        return httpstatus, headers, content

    def __call__(self, req):
        return self.do_process(self, req)

3.4 潜在风险

4 详细设计

详细设计重点在“详细”

4.1 全局中间件

4.1.1 交互流程

butterfly/wsgiapp.py

route = urls.Route(logger_conf.initlog, logger_conf.errlog)
# 自动添加全局 middleware
route.autoload_middleware("handlers")
# 自动将 handlers 目录加 package 自动注册
route.autoload_handler("handlers")
# 手动添加注册(访问 /ping ,则会自动转到 apidemo.ping)
# route.addapi("/ping", apidemo.ping, True, True)
apicube = route.get_route()

启动时 route 从 handlers 目录中加载 middleware 模块, 若加载成功则自动添加 middleware

4.1.2 数据库设计

4.1.3 接口形式

4.1.4 实现原理

4.1.5 例子

/handlers/middleware.py

import logging

log = logging.getLogger("butterfly")

class Middleware():
    '''
    Middleware
    '''

    def __init__(self, protocol):
        self.protocol = protocol

    def __call__(self, req):
        log.info("ceshi")
        httpstatus, headers, content = self.protocol(req)
        return httpstatus, headers, content

4.2 局部中间件

(有了数据库 + 接口 + 流程,别的同学拿到详设文档,基本也能够搞定了)

4.2.1 交互流程

简要的交互可用文字说明,复杂的交互建议使用流程图,交互图或其他图形进行说明

4.2.2 数据库设计

4.2.3 接口形式

4.2.4 实现原理

4.2.4.1 wrapt

使用装饰器时 functools.wraps() 可以帮我们解决__name__和__doc__ 的问题,但对于获取函数的参数(argument)或源代码(source code)则束手无策

butterfly 在自动生成路由时,需要使用到 __name__ 和获取函数参数 (argument), 所以需要用到如下 wrapt

4.2.4.1.1 不带参数的装饰器

import wrapt

# without argument in decorator
@wrapt.decorator
def logging(wrapped, instance, args, kwargs):  # instance is must
    print "[DEBUG]: enter {}()".format(wrapped.__name__)
    return wrapped(*args, **kwargs)

@logging
def say(something):
    pass

wrapt 使用

使用 wrapt 你只需要定义一个装饰器函数,但是函数签名是固定的,必须是 (wrapped, instance, args, kwargs),

注意第二个参数 instance 是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据 instance 的值你能够更加灵活的调整你的装饰器。

如装饰 handle hello 函数时
(1) wrapped ----- <function hello at 0x10ccf05f0>
(2) instance ---- None
(3) args -------- (<xlib.httpgateway.Request object at 0x10cce9a90>, 'ceshi') # 接口调试时
(4) kwargs ------ {}

4.2.4.1.2 带参数的装饰器

def logging(level):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print "[{}]: enter {}()".format(level, wrapped.__name__)
        return wrapped(*args, **kwargs)
    return wrapper

@logging(level="INFO")
def do(work):
    pass

4.2.4.2 示例

4.2.4.2.1 关闭 db 连接 -- (使用数据库时,建议使用自动重连配置,则无需再添加关闭 db 连接配置)

from xlib.db import  my_database
from xlib.util import wrapt

@wrapt.decorator
def db_close(wrapped, instance, args, kwargs):
    result = wrapped(*args,**kwargs)
    if not my_database.is_closed():
        my_database.close()
    return result

4.2.4.2.2 检查是否已登录 -- (使用『火眼』组件,无需通过装饰器再实现)

from xlib import auth

@wrapt.decorator
def login_required(wrapped, instance, args, kwargs):
    """
    使用 web 接口访问时,是通过 kwargs 进行传值
    使用 test_handler.py 进行接口调试时,则是使用 args 进行传值,所以接口调试时会自动绕过身份验证
    """
    if "req" in kwargs:
        req = kwargs["req"]
        token_check = auth.is_token_valid(req)
        if not token_check.success:
            return token_check.err_content
    result = wrapped(*args,**kwargs)
    return result

Last updated