Manjusaka

Manjusaka

日常辣鸡水文:一个关于 Sanic 的小问题的思考

日常辣鸡水文:一个关于 Sanic 的小问题的思考#

睡不着,作为一个 API 复制粘贴工程师来日常辣鸡水文一篇

正文#

最近迁移组内代码到 Sanic ,遇到一个很有意思的情况

首先标准的套路应该是这样的

from sanic import Sanic,reponse
app=Sanic(__name__)

def return_value(controller_fun):
    """
    返回参数的装饰器
    :param controller_fun:  控制层函数
    :return:
    """

    async def __decorator(*args, **kwargs):
        ret_value = {
            "version": server_current_config.version,
            "success": 0,
            "message": u"fail query"
        }
        ret_data, code = await controller_fun(*args, **kwargs)

        if is_blank(ret_data):
            ret_value["data"] = {}
        else:
            ret_value["success"] = 1
            ret_value["message"] = u"succ query"
            ret_value["data"] = ret_data
            ret_value["update_time"] = convert_time_to_time_str(get_now())
        print(ret_value)
        return response.json(body=ret_value, status=code)

    return __decorator

async def test1():
    return {"a":1"}
@return_value
async def test2():
    return await test1(),200



@app.route("/wtf")
async def test3():
    return await test2()

中规中举,没什么太大问题

不过如果上面的代码变成下面这样

from sanic import Sanic,reponse
app=Sanic(__name__)

async def test1():
    return {"a":1"}
@return_value
async def test2():
    return await test1()


@app.route("/wtf")
def test3():
    return test2()

一般会以为这样会产生报错的,因为没有 await test2() ,直接 return test2() 的话,返回的是一个 Coroutine 的对象,这样应该是会抛错的,但是实际上是正常运行的,最开始很迷,不过后面看了下 Sanic 中关于 handle_request 的部分,有点意思

    async def handle_request(self, request, write_callback, stream_callback):
        """Take a request from the HTTP Server and return a response object
        to be sent back The HTTP Server only expects a response object, so
        exception handling must be done here

        :param request: HTTP Request object
        :param write_callback: Synchronous response function to be
            called with the response as the only argument
        :param stream_callback: Coroutine that handles streaming a
            StreamingHTTPResponse if produced by the handler.

        :return: Nothing
        """
        try:
            # -------------------------------------------- #
            # Request Middleware
            # -------------------------------------------- #

            request.app = self
            response = await self._run_request_middleware(request)
            # No middleware results
            if not response:
                # -------------------------------------------- #
                # Execute Handler
                # -------------------------------------------- #

                # Fetch handler from router
                handler, args, kwargs, uri = self.router.get(request)
                request.uri_template = uri
                if handler is None:
                    raise ServerError(
                        ("'None' was returned while requesting a "
                         "handler from the router"))

                # Run response handler
                response = handler(request, *args, **kwargs)
                if isawaitable(response):
                    response = await response
        except Exception as e:
            # -------------------------------------------- #
            # Response Generation Failed
            # -------------------------------------------- #

            try:
                response = self.error_handler.response(request, e)
                if isawaitable(response):
                    response = await response
            except Exception as e:
                if self.debug:
                    response = HTTPResponse(
                        "Error while handling error: {}\nStack: {}".format(
                            e, format_exc()))
                else:
                    response = HTTPResponse(
                        "An error occurred while handling an error")
        finally:
            # -------------------------------------------- #
            # Response Middleware
            # -------------------------------------------- #
            try:
                response = await self._run_response_middleware(request,
                                                               response)
            except:
                log.exception(
                    'Exception occured in one of response middleware handlers'
                )

        # pass the response to the correct callback
        if isinstance(response, StreamingHTTPResponse):
            await stream_callback(response)
        else:
            write_callback(response)

核心代码是这样一段

                handler, args, kwargs, uri = self.router.get(request)
                request.uri_template = uri
                if handler is None:
                    raise ServerError(
                        ("'None' was returned while requesting a "
                         "handler from the router"))

                # Run response handler
                response = handler(request, *args, **kwargs)
                if isawaitable(response):
                    response = await response

大概就是,首先按照 route->add_route 的顺序注册对应的处理函数和 URL 到一个映射里,然后当请求发过来时,取出对应的 handler ,然后进一步处理

最开始正常的中规中矩的做法里

@app.route("/wtf")
async def test3():
    return await test2()

注册的 handlertest3 这个函数,然后执行 response = handler(request, *args, **kwargs) ,初始化了一个 Coroutine 对象,紧接着这个对象是 awaitable 的,于是进入后面的 response = await response 流程。

好了,来看看非主流的做法

@app.route("/wtf")
def test3():
    return test2()

老规矩,先注册,然后取出 test3 这个函数作为 handler ,然后执行,因为是普通函数,于是 response 的值便是 test3 中初始化的那个 Coroutine 对象,然后同样是 awaitable 的,进入后面的 response = await response 流程。

两种方式殊途同归,这也解释了为什么第二中不清真的方式也能得到正确的结果

思考#

Sanic 这样的处理方式,相当于增强了整个框架的容错性。也可能让用户写出向之前那样不清真的代码。不过我也没法说这个是好是坏,各有看法吧。不过有一点是肯定的,在 debug 模式下,如果用户利用 app.route 添加了一个非 async 的函数,是有必要抛出一个 warning 的,不过,Sanic 还有,PR 已经提出,就不知道合不合了。。。

好了,就先这样吧。。明天还得搬砖,溜了,溜了。。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。