fastapi微服务系列-之封装GRPC实现类似fastapi一个框架(已开源)-(4)-实现事件注册和中间件

3,635 阅读8分钟

前言

鉴于近期的手头的事有点忙,所以停更了,为不负所望,还是需要把之前一些东西继续写完,就算是给自己一个交代。

接上一篇中,我们主要讲到关于服务的批量注册,这一篇参考来定义我们的类似fastapi的事件及中间件的添加:

事件添加示例:

方式1@app.on_event('startup')
def startup():
    print("startup=========1》")
    sync_redis_client.init_app(app)

@app.on_event('shutdown')
def shutdown():
    print("shutdown=========》")

方式2# 添加事件处理
def ceshiguanbi():
    print("关闭了!")
app.add_event_handler(event_type='shutdown', func=ceshiguanbi)

# 添加服务处理

中间件添加示例:

方式1@app.add_middleware
class ServiceLogMiddleware2222(BaseMiddleware):

    def before_handler(self, request: Any, context: grpc.ServicerContext, method_name):
        pass
        print("我是自定义的中间件信息---》1")

    def after_handler(self, context: grpc.ServicerContext, response):
        pass
        print("我是自定义的中间件信息---》2")

    def error_grpc_exc_handler(self, error):
        pass
        
方式2# 添加服务处理
# app.add_servicer(Greeter)
from grpcframe.middlewares.tracer.middleware import Zyxopentracing
app.add_middleware(Zyxopentracing)

from example.hello_srv.middlewares.authinterceptor import AuthMiddleware
# PS: 关于中间件注册,最后注册,先执行,遵循的是洋葱模型,(需要统一基于Bsea等Middleware,原生的会首先执行!)
app.add_middleware(AuthMiddleware)

事件注册篇

事件注册方式

鉴于框架结构之前还有些不是非常好,所以新的有所调整了一下,具体调整如下:

相关的时间注册的示例如代码所示:


from grpcframe.zyxgrpc.base_app import FastGrpcApp
from grpcframe.exts.redis.client import sync_redis_client
from grpcframe.exts.register.consul import ConsulRegister
from grpcframe.zyxgrpc.base_middleware import BaseMiddleware,Any
import grpc

# 注册中心
register = ConsulRegister(host='127.0.0.1',port=8500, server_name='FastGrpcApp',server_id="23423424234")
# 实例化一个带有注册中心的服务对象
app = FastGrpcApp(port=45564,max_workers=20,is_certificate=False,register_center=None,debug=True)

@app.on_event('startup')
def startup():
    print("startup=========1》")
    sync_redis_client.init_app(app)

@app.on_event('shutdown')
def shutdown():
    print("shutdown=========》")


if __name__ == '__main__':
    # 单独的进行加载
    app.run_serve()

封装思路和步骤

这里主要基础参考fastapi的我设计,很简单不复杂。

第1步:定义存贮注册函数列表

  # 定义事件回调列表
        self.on_startup = [] if on_startup is None else list(on_startup)
        self.on_shutdown = [] if on_shutdown is None else list(on_shutdown)

第2步:定义添加事件函数或装饰器函数

# 通过装饰器添加事件处理
def on_event(self, event_type: str) -> typing.Callable:
    def decorator(func: typing.Callable) -> typing.Callable:
        self.add_event_handler(event_type, func)
        return func
    return decorator

# 添加事件信号处理器
def add_event_handler(self, event_type: str, func: typing.Callable) -> None:
    # 根据类型来识别添加不懂的函数对象
    assert event_type in ("startup", "shutdown")
    if event_type == "startup":
        self.on_startup.append(func)
    else:
        self.on_shutdown.append(func)

第3步:定义事件处理函数

# ===============事件处理模块机制===============
def startup(self) -> None:
    for handler in self.on_startup:
        handler()

def shutdown(self) -> None:
    for handler in self.on_shutdown:
        handler()

第4步:在合适的位置进行事件处理函数的调用

总结:其实总结起来就是使用立碑存贮一堆的函数,在恰当时机进行函数遍历调用。

中间件篇

中间件定义示例:



from grpcframe.zyxgrpc.base_middleware import BaseMiddlewareGrequestcontext
from typing import Any
from grpc_interceptor.exceptions import GrpcException
import grpc

from grpcframe.exts.grpclocal.request import Request
# 为了的到类型提示
from grpcframe.zyxgrpc.globals import grequestcontext
grequestcontext :Request= grequestcontext

# @app.add_middleware
class AuthMiddleware(BaseMiddlewareGrequestcontext):

    def before_handler(self):
        pass
        # 设置上下文信息处理
        print("我输出----没初始化吗?", grequestcontext.parse_request_metadata)
        print("我输---没初始化吗?AuthInterceptor")
        # 创建类似flask的上下文的对象
    def after_handler(self):
        pass

    def error_grpc_exc_handler(self,e:GrpcException):
        pass

    def finally_handler(self):
        pass

目前中间支持几种基础的继承的方式,和需要来定义继承扩展来实现:

主要的几个模式有:

from grpc_interceptor import ServerInterceptor
from grpc_interceptor.exceptions import GrpcException
import abc
import grpc
from typing import Any, Callable
from grpcframe.zyxgrpc.compose_app import ComposeApp


# 扩展需要的错误处理
class ErrorBase(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def error_grpc_exc_handler(self, exception: GrpcException):
        raise NotImplementedError()


# 扩展需要的错误处理
class FinallyBase(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def finally_handler(self):
        raise NotImplementedError()


class BeforeAfterBase(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def before_handler(self, request: Any, context: grpc.ServicerContext, method_name):
        raise NotImplementedError()

    @abc.abstractmethod
    def after_handler(self, context: grpc.ServicerContext, response):
        raise NotImplementedError()


class BeforeAfterBaseNoParameter(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def before_handler(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def after_handler(self):
        raise NotImplementedError()


class BaseMiddlewareNoParameter(grpc.ServerInterceptor, BeforeAfterBaseNoParameter):
    '''
    如果是基于grpc.ServerInterceptor实现的中间件,在进行中间件的循环的时候,不遵循了洋葱模型,到账可能对应的回去不到对应的信息了,
    # 它是永远的第一个去执行了!!!!!!!
    '''

    def intercept_service(self, continuation, handler_call_details):
        self.before_handler()
        resp = continuation(handler_call_details)
        self.after_handler()
        return resp


# 需要先继承ServerInterceptor再继承ABCMeta
class BaseComposeAppMiddleware(ServerInterceptor, ErrorBase, BeforeAfterBase, ComposeApp):

    def intercept(self, method: Callable, request: Any, context: grpc.ServicerContext, method_name: str, ) -> Any:
        # 创建上下文对象
        try:
            # 回调调用必要的前置的函数处理
            self.before_handler(request, context, method_name)
            response = method(request, context)
            # 调用必要的后置的函数处理
            self.after_handler(context, response)
            return response

        except GrpcException as e:
            self.error_grpc_exc_handler(e)
            # context.set_code(e.status_code)
            # context.set_details(e.details)
            raise
        finally:
            pass


# 有request, context回调处理的基础
class BaseMiddleware(ServerInterceptor, ErrorBase, BeforeAfterBase):

    def intercept(self, method: Callable, request: Any, context: grpc.ServicerContext, method_name: str, ) -> Any:
        # 创建上下文对象
        try:
            # 回调调用必要的前置的函数处理
            self.before_handler(request, context, method_name)
            response = method(request, context)
            # 调用必要的后置的函数处理
            self.after_handler(context, response)
            return response
        except GrpcException as e:
            self.error_grpc_exc_handler(e)
            # context.set_code(e.status_code)
            # context.set_details(e.details)
            raise
        finally:
            pass


# 有request, context回调处理的基础
class BaseMiddlewareGrequestcontext(ServerInterceptor, ErrorBase, BeforeAfterBaseNoParameter, FinallyBase):

    def intercept(self, method: Callable, request: Any, context: grpc.ServicerContext, method_name: str, ) -> Any:
        # 创建上下文对象
        try:
            # 回调调用必要的前置的函数处理
            self.before_handler()
            response = method(request, context)
            # 调用必要的后置的函数处理
            self.after_handler()
            return response
        except GrpcException as e:
            self.error_grpc_exc_handler(e)
            # context.set_code(e.status_code)
            # context.set_details(e.details)
            raise
        finally:
            self.finally_handler()

中间件注册使用的示例代码如下:


from grpcframe.zyxgrpc.base_app import FastGrpcApp
from grpcframe.exts.redis.client import sync_redis_client
from grpcframe.exts.register.consul import ConsulRegister
from grpcframe.zyxgrpc.base_middleware import BaseMiddleware,Any
import grpc

# 注册中心
register = ConsulRegister(host='127.0.0.1',port=8500, server_name='FastGrpcApp',server_id="23423424234")
# 实例化一个带有注册中心的服务对象
app = FastGrpcApp(port=45564,max_workers=20,is_certificate=False,register_center=None,debug=True)


@app.add_middleware
class ServiceLogMiddleware2222(BaseMiddleware):

    def before_handler(self, request: Any, context: grpc.ServicerContext, method_name):
        pass
        print("我是自定义的中间件信息---》1")

    def after_handler(self, context: grpc.ServicerContext, response):
        pass
        print("我是自定义的中间件信息---》2")

    def error_grpc_exc_handler(self, error):
        pass
        
# 添加服务处理
# app.add_servicer(Greeter)
from grpcframe.middlewares.tracer.middleware import Zyxopentracing
app.add_middleware(Zyxopentracing)

from example.hello_srv.middlewares.authinterceptor import AuthMiddleware
# PS: 关于中间件注册,最后注册,先执行,遵循的是洋葱模型,(需要统一基于Bsea等Middleware,原生的会首先执行!)
app.add_middleware(AuthMiddleware)

if __name__ == '__main__':
    # 单独的进行加载
    app.run_serve()

封装思路和步骤

部分中间件前置说明

部分的中间件引用用到对应的一个全局quest的上下文的代理,需要需要默认的注册一个全局代理中间件。

关于代理模式,在之前一篇有相关介绍,这里新增几个需要代理的对象,

import contextvars
import typing
from dataclasses import dataclass
from grpcframe.utils.singleton import Singleton
from grpcframe.exts.grpclocal.local import LocalProxy
from functools import partial


@dataclass
class GManager(metaclass=Singleton):
    # 记录当前请求的请求
    current_app_request = contextvars.ContextVar('current_app_request', default=None)
    # 记录当前请求的请求
    current_app_context = contextvars.ContextVar('current_app_context', default=None)
    # 记录当前被激活的追踪链路的span
    active_tracer_span = contextvars.ContextVar('current_active_tracer_span', default=None)
    # 包含咯当前的请求乡下问,和context上下文的对象
    current_app_request_context = contextvars.ContextVar('current_app_request_context', default=None)


_g = GManager()


def set_current_app_request(value):
    _g.current_app_request.set(value)


def get_current_app_request(_g=None):
    # 代理里面的get方法直接的返回,这样就不需要显示的调用get()
    return _g.current_app_request.get()


def set_current_app_request_context(value):
    _g.current_app_request_context.set(value)


def get_current_app_request_context(_g=None):
    # 代理里面的get方法直接的返回,这样就不需要显示的调用get()
    return _g.current_app_request_context.get()


def get_current_app_request_context2():
    # 代理里面的get方法直接的返回,这样就不需要显示的调用get()
    return _g.current_app_request_context.get()


def set_current_app_context(value):
    _g.current_app_context.set(value)


def get_current_app_context(_g=None):
    # 代理里面的get方法直接的返回,这样就不需要显示的调用get()
    return _g.current_app_context.get()


def set_active_tracer_span(value):
    _g.active_tracer_span.set(value)


def get_active_tracer_span(_g=None):
    # 代理里面的get方法直接的返回,这样就不需要显示的调用get()
    return _g.active_tracer_span.get()


# 定义 一个全局代理g里面的current_app_request 的对象,这样也可以实现类似flask上线文的效果
grequest = LocalProxy(partial(get_current_app_request, _g))
gcontext = LocalProxy(partial(get_current_app_context, _g))
gactive_tracer_span = LocalProxy(partial(get_active_tracer_span, _g))
grequestcontext = LocalProxy(partial(get_current_app_request_context, _g))


其他步骤说明:

第1步:定义基础的相关中间件基类

不同的基类实现不同的需要实现的方式,具体可以看上面的基类模式。

第2步:通过添加函数的方式进行添加

使用装饰器形式的话是对类的装饰,这个地方有需要的话你可以另外自己封装处理一下。


from example.hello_srv.middlewares.authinterceptor import AuthMiddleware
# PS: 关于中间件注册,最后注册,先执行,遵循的是洋葱模型,(需要统一基于Bsea等Middleware,原生的会首先执行!)
app.add_middleware(AuthMiddleware)

上面两点是关于事件和中间件模块的一些使用和思路简单介绍,鉴于时间有限,叙述不够详细。如果有相关疑问的话,并且当前这个封装应该是会有坑的,如果你在使用过程有相关的问题,也愿意和我沟通交流的话,可以随时的联系我。我也是小白,相互学习学习!

关于开源

fastapi脚手架封装

之前第一版fastapi开源后一个脚手架,里面部分内容其实封装不是很合理,但是也的到一些网友门的关注,实在是受宠若惊,

其实第一版发布之后忙于其他事情,就暂时搁置,后来在闲暇之余也重构了一版。

以下是最新版本的一些结构,新版中主要新增了一些批量加载又有和一些基础公共的扩展封装。

后续等手头忙完之后就会开源更新一下。

grpc脚手架封装

该框架,基本主要是想把使用grpc的方式改造成和使用fastapi的模式差不多的。但是功力有限,部分的东西目前还在时间继续深入改造,比如改造为使用route这种方式来实现相关的方式注册。目前主要是还是CBV的方式来实现。

主要实现的功能点有:

  • 事件注册
  • 服务批量注册
  • 中间件添加
  • 全局上下文代理实现
  • 基于信号库的事件处理
  • 引入pydantic做简单的参数校验
  • 默认健康检测的实现

项目结构如下:

开源地址

https://gitee.com/xiaozhong1988/fastgrpc

有坑在所难免,希望大佬可以多多指教!

总结

以上仅仅是个人结合自己的实际需求,做学习的实践笔记!如有笔误!欢迎批评指正!感谢各位大佬!

结尾

END

简书:www.jianshu.com/u/d6960089b…

掘金:juejin.cn/user/296393…

公众号:微信搜【小儿来一壶枸杞酒泡茶】

小钟同学 | 文 【欢迎一起学习交流】| QQ:308711822