Tornado路由与Application的实现
作者:tracy小猫 时间:2021-01-26 19:56:59
路由原理
在Tornado框架中,路由是指将请求的URL映射到对应的处理函数上,这个过程需要通过正则表达式来实现。Tornado使用了一种叫做Application的类来封装整个Web应用程序.我们定制一个Applicaiton继承tornado的Application
from tornado.web import Application as tornadoApp
class Application(tornadoApp):
""" 定制 Tornado Application 集成日志、sqlalchemy 等功能 """
def __init__(self):
self.ops_mongo = None # ops mongo
self.redis_cluster = None # redis基础session
self.redis_manager = None # redis_manager session
self.redis = None
tornado_settings = {
'autoreload': settings.service.server.autoreload,
'debug': settings.service.server.debug,
"default_handler_class": DefaultHandler,
}
route = getRoutes(settings)
self.prepare()
super(Application, self).__init__(handlers=route, **tornado_settings)
def prepare(self):
self.redis_cluster = asyncio.get_event_loop().run_until_complete(
init_cluster()
)
self.ops_mongo = asyncio.get_event_loop().run_until_complete(
init_ops_mongodb()
)
loop = asyncio.get_event_loop()
self.redis = RedisPool(loop=loop).get_conn()
self.redis_manager = RedisManager(self.redis)
asyncio.get_event_loop().run_until_complete(
create_all_indexes()
)
async def on_close(self, server_conn: object) -> None:
await self.redis_manager.close()
logger.info("close redis")
self.ops_mongo.close()
logger.info("close mongo")
这段代码定义了一个继承自 Tornado Application 的自定义 Application 类,并在初始化中进行了一些准备工作。
首先,定义了几个实例变量,包括 ops_mongo(mongoDB 数据库连接)、redis_cluster(基于 redis 的 session)、redis_manager(管理 redis 连接)和 redis(redis 数据库连接)。
然后,根据配置文件中的设置,设定了 Tornado Application 的一些参数,并根据路由设置得到路由列表。
接下来,调用了 prepare 方法,该方法中通过异步方法初始化了 ops_mongo(mongoDB 数据库连接)、redis_cluster(基于 redis 的 session)和 redis(redis 数据库连接),并创建了 redis_manager 实例管理 redis 连接。最后,调用了异步方法 create_all_indexes,创建了所有的索引。
最后,定义了一个异步方法 on_close,在 Web 服务关闭时关闭了 redis_manager 实例和 ops_mongo 实例。同时加入了一些对于关闭的日志信息。
class DefaultHandler(tornado.web.RequestHandler):
def get(self):
raise tornado.web.HTTPError(
status_code=404,
reason="Not Found"
)
def write_error(self, status_code, exc_info=None, **kwargs):
self.finish({"error": self._reason})
def getRoutes(setting):
Routes = [
(r"/redis", home.TestRedisHandler),
]
return Routes
该代码是使用 Tornado 框架定义一个继承自 tornado.web.RequestHandler 的 DefaultHandler 类,包含两个方法 get 和 write_error。
get 方法会抛出一个 HTTPError,表示请求的页面不存在或其他错误,会返回一个 404 的状态码和 Not Found 的原因。
write_error 方法会在遇到未处理的异常时自动调用,可以自定义错误响应的方式。这里定义的行为是结束响应并返回一个包含错误信息的 JSON 对象
另外,getRoutes 方法定义了一个以元组形式表示的 URL 路由表,将 /redis 映射到名为 TestRedisHandler 的类。
RequestHandler的功能
在Tornado中,RequestHandler是处理每个HTTP请求的基础类,所有的请求处理类都应该继承自它。RequestHandler有如下几个常用的方法:
initialize():初始化方法,在请求处理类实例化时自动调用,用于设置一些参数或进行一些初始化操作。
prepare():在处理请求前调用,用于进行一些安全检查、身份验证等操作,并且可以自定义错误页面。
get()、post()等:根据HTTP请求方式的不同,提供相应的get()、post()等方法来处理请求,并返回相应的HTTP响应。
write()、finish():用于返回响应数据,并结束请求处理。
redirect()、set_header()等:提供一些常用的HTTP响应辅助方法。
class BaseHandler(RequestHandler, ABC):
traceid = None
start_time = None
# 普通返回
response_dict = {'code': 0, 'message': "", 'tid': "", 'data': None}
# 分页返回
response_page_dict = {'code': 0, 'message': "", 'tid': "", 'data': None, "pagination":
{"pagesize": 0, "pages": 0, "items": 0, "pageno": 0}
}
@classmethod
def return_response(cls):
resp = AttrDict(cls.response_dict)
return resp
@classmethod
def return_page_response(cls):
resp = AttrDict(cls.response_page_dict)
return resp
@classmethod
def create_page(cls, response: AttrDict, total=None, pagesize=0, pageno=None):
response.pagination["items"] = total
response.pagination["pagesize"] = pagesize
response.pagination["pages"] = total // pagesize if total % pagesize == 0 else total // pagesize + 1
response.pagination["pageno"] = pageno
return response
def get_request(self):
request_body = None
if self.request.body:
request_body = tornado.escape.json_decode(self.request.body)
return request_body
def return_json(self, response):
*"""*
**返回统一的*json*格式结果
*****:param*** *response:*
*"""*
**
**self.set_header("Content-Type", "application/json; charset=UTF-8")
self.response_dict = json.dumps(response, default=json_util.default)
self.write(self.response_dict)
def prepare(self):
trace_id = self.request.headers.get("tid", None)
self.__class__.start_time = datetime.datetime.now()
if not trace_id:
trace_id = str(uuid.uuid4())
self.request.headers["tid"] = trace_id
self.__class__.traceid = trace_id
def write_error(self, status_code, exc_info=None, api_code=None, **kwargs):
resp = self.return_response()
resp.code = status_code
resp.tid = self.traceid
ex = exc_info[1]
if isinstance(ex, Exception):
err_msg = str(ex)
resp.message = err_msg
if isinstance(ex, tornado.web.HTTPError):
err_msg = str(ex)
resp.message = err_msg
if isinstance(ex, tornado.web.HTTPError) and ex.log_message:
err_msg = str(ex.log_message)
resp.message = err_msg
self.response_dict = json.dumps(resp, default=json_util.default)
self.finish(resp)
def on_finish(self):
# 处理完请求后输出日志,将请求头信息和响应结果json化
end_time = datetime.datetime.now()
request_data = {
"tid": self.request.headers.get("tid", None),
"url": self.request.uri,
"method": self.request.method,
"remote_ip": self.request.remote_ip,
"user_agent": self.request.headers["User-Agent"],
"request_body": json.loads(self.request.body) if self.request.body else ""
}
response_data = {
"status": self.get_status(),
"response_body": json.loads(self.response_dict) if not isinstance(self.response_dict,
dict) else self.response_dict,
"response_time": (end_time - self.start_time).microseconds / 1000
}
logger = get_logger("access")
logger.bind(**request_data).bind(**response_data).info("request success")
BaseHandler 的类,它继承了 tornado.web.RequestHandler 和 abc.ABC 两个类。该类提供了一些基础方法,可以被继承使用来构建业务处理类。
这个类定义了两种返回方式:普通返回和分页返回。普通返回返回的数据格式有 code、message、tid 和 data 四个字段,其中 code 表示请求状态码,message 表示请求状态消息,tid 表示请求追踪 id,data 表示响应数据。分页返回在普通返回的基础上加入了分页参数信息,即 pageSize、pages、items 和 pageNo。
该类实现了三个类方法:return_response、return_page_response 和 create_page。return_response 返回普通返回的响应对象,return_page_response 返回分页返回的响应对象,create_page 用于创建分页响应。
该类实现了三个实例方法:get_request、return_json 和 write_error。get_request 方法用于获取请求体的 json 数据,return_json 方法用于返回 json 格式的响应,write_error 方法用于处理请求出错时返回的响应。
该类实现了两个钩子函数:prepare 和 on_finish。prepare 在请求进入处理前被调用,可以用来获取请求头信息等,on_finish 在请求处理完后被调用,用于记录日志等操作。
处理错误
在Tornado中,异常处理同样也是一个非常重要的问题,异常处理可以让我们在遇到错误时能够有更好的反应和处理。可以通过使用@tornado.web.appadvice()装饰器或者重写write_error()方法来进行自定义错误处理。
import tornado.web
class CustomErrorHandler(tornado.web.RequestHandler):
def write_error(self, status_code, **kwargs):
if self.settings.get("serve_traceback"):
self.write('Some Exception occurs: {}'.format(kwargs['exc_info'][1]))
else:
self.write('Some Exception occurs. We are on it!')
self.set_status(status_code)
class ErrorExample(tornado.web.RequestHandler):
def get(self, error_printer):
if error_printer == "raise":
raise Exception("An unknown error occurred.")
elif error_printer == "syntax":
raise SyntaxError("There is a syntax error in your code.")
else:
self.write("This is normal operation!")
上述代码中,使用write_error()重写了RequestHandler的默认错误处理方式。当我们访问http://localhost:8888/err?error=syntax 时,会输出 Some Exception occurs: There is a syntax error in your code
处理完成
tornado的RequestHandler是用于请求处理的基本类,其中on_finish是RequestHandler的一个方法,可以在请求处理完成后被调用。当客户端请求处理完成时,可以使用此方法进行一些清理工作或日志记录等操作,并将tornado 对象释放回底层IOLoop。在on_finish方法内可以进行资源释放、统计请求处理时间等工作。此外,在建立连接之前初始化某些对象或资源,也可以在on_finish方法中完成清理。
以下是RequestHandler的on_finish方法的声明:
def on_finish(self) -> None:
"""
Finish this response, ending the HTTP request.
"""
pass
Tornado提供了很多的错误处理接口来帮助我们进行异常处理。为了使Web程序更加的健壮,我们需要尽可能的考虑到各种可能出现的错误情况,并对异常情况进行适当的处理。
来源:https://juejin.cn/post/7232824323753001015