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 已經提出,就不知道合不合了。。。

好了,就先這樣吧。。明天還得搬磚,溜了,溜了。。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。