日常辣雞水文:一個關於 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()
註冊的 handler
是 test3
這個函數,然後執行 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 已經提出,就不知道合不合了。。。
好了,就先這樣吧。。明天還得搬磚,溜了,溜了。。