当前位置: 代码迷 >> 综合 >> python tornado 路由 web API式路由前后分离
  详细解决方案

python tornado 路由 web API式路由前后分离

热度:89   发布时间:2024-02-22 03:24:35.0

目前流行前后端分离, 前端一般用Vue或React 做前端, 后端用C#,python,java,php的 都可以.

而python 系的web框架有 , Django 和Flask, 还有 Tornado.

其中 Tornado 特别适合Api开发, 灵活轻量级, 自非阻塞式带服务器 , 据说性能强劲, 而且速度相当快。得利于其非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架. 更适合做前后端分离的后端. 其它两个框架 都有点臃肿的感觉. Flask不能支持多端口.
Django支持的是过时的Mvc, 很多用不到的东西影响性能, 而且上手要点时间. 所以我打算在Tornado做后端,

更多的好处咱就不多说了. 请移步到这里看
https://www.cnblogs.com/liujianzuo888/articles/5996114.html

Tornado官方给的默认路由示例是按照RestFull 形式来实现的.
按照get,post,方法来区分调用不同的方法.

# 实现一个最简单的tornado服务
import tornado.ioloop #监听循环
import tornado.web  #tornado包核心模块
import tornado.httpserver  #非阻塞服务器
import tornado.options  #class MainHandler(tornado.web.RequestHandler):def get(self):self.write('hello world get')def post(self):self.write('hello world post')application = tornado.web.Application(handlers=[(r'/',MainHandler),    #路由表]#列表,列表中包含元组。配置路径信息
) #实例化操作
#可以传入很多参数#配置参数项if __name__ == '__main__':  #用来做测试,自己调用tornado.options.parse_command_line() #打印请求信息(日志信息),不加这一行后端不会有日志信息http_server = tornado.httpserver.HTTPServer(application)http_server.listen(8080)  #监听,绑定端口,服务器由主动变为被动tornado.ioloop.IOLoop.current().start() #开启监听循环#单进程单线程epoll
#epoll ,主动监听,把监听的任务交给操作系统,不断询问。监听循环

如上的代码是一般常见的代码.默认使用方法.
在工程使用中. 一般会将MainHandler 放到一个单独的py文件中.
一般没人会把所有的代码都放在一个启动文件中吧…

但是即便分离了代码, 仍然是一个 url地址对应一个Handler Class
能不能地址映射到 Handler中的不同的函数呢?
例如
/api/user/login 对应 UserHandler中的 login 函数
/api/user/logout 对应 UserHandler中的 logout 函数
/api/user/userinfo 对应 UserHandler中的 userinfo 函数

经过半天的百度查找,始终没有找到合适的方案, (我发现百度真是越来越不中用了) 甚至到最后都打算自己写一个路由器了…

下班后, 又尝试着再github中找, 结果从好几种路由方式中, . 最后终于找到了比较合适的方案,

https://github.com/ginking/tornado_router
其核心代码只有一个文件,router.py

于是我把它复制到我的项目根目录下,改名成 ApiRouter.py
在它的 router.py 代码之上, 我只增加了一行.
更改后的源代码如下

# 此类来自于 https://github.com/ginking/tornado_router import logging
import base64
import traceback
import functools
import json
import tornado.gen
import tornado.web
import tornado.escapelogger = logging.getLogger(__name__)class Router():def __init__(self, base_handler, redirect=True):self._handlers = []self._requests = []self._base_handler = base_handler# redirect if true, not return redirect_urlself._redirect = redirect@propertydef handlers(self):return self._handlers@propertydef requests(self):return self._requests# authentication wrapperdef _auth_wrap(self, f):@functools.wraps(f)@tornado.gen.coroutinedef auth_request(handler):user = yield handler.get_cur_user()if not user:redirect_url = handler.get_login_url() + '?referrer=' + str(base64.b64encode(handler.request.uri.encode('ascii')))[2:-1]if self._redirect:# redirecthandler.redirect(redirect_url)else:# return json responsehandler.set_header('Content-Type', 'application/json')handler.write(tornado.escape.json_encode({
    'redirect': redirect_url}))returnyield f(handler)return auth_request# json wrapperdef _json_wrap(self, f):@functools.wraps(f)@tornado.gen.coroutinedef json_request(handler):# todo: there's no need to prepare dict input??try:resp = yield f(handler)handler.set_header('Content-Type', 'application/json')handler.write(tornado.escape.json_encode(resp))except Exception as e:raise tornado.web.HTTPError(500, str(e))# todo: raise HTTPError#handler.logger.warn(str(e) + '\n' + repr(traceback.format_stack()))#traceback.print_exc()return json_request# routedef route(self, method='get', url=None, auth=False, json=False, xsrf=True):# self.application.settings.get("xsrf_cookies")if method.upper() not in tornado.web.RequestHandler.SUPPORTED_METHODS:raise ValueError('invalid HTTP method {} found! tornado only supports HTTP methods in {}'.format(method, tornado.web.RequestHandler.SUPPORTED_METHODS))def req_wrap(f):if not self._base_handler:raise RuntimeError('base_handler must be initialized!')class InnerHandler(self._base_handler):passreq = self._json_wrap(tornado.gen.coroutine(f)) if json else tornado.gen.coroutine(f)req = self._auth_wrap(req) if auth else reqsetattr(InnerHandler, method.lower(), req)if not xsrf:setattr(InnerHandler, "check_xsrf_cookie", lambda self: None)InnerHandler.logger = logging.getLogger(f.__name__)f_url = urlif not f_url:f_url = '/' + f.__name__self._handlers.append((f_url, InnerHandler))@functools.wraps(f)def request():passself._requests.append(request)return requestreturn req_wrapclass BaseHandler(tornado.web.RequestHandler):_login_url = '/login'def get_login_url(self):return self._login_url@tornado.gen.coroutinedef get_cur_user(self):if not hasattr(self, '_cur_user'):self._cur_user = Nonecurrent_user = self._cur_userif not current_user:user_cookie = self.get_secure_cookie('user', max_age_days=1)if not user_cookie:return Nonecurrent_user = json.loads(user_cookie.decode('utf-8'))self._cur_user = current_userreturn self._cur_user@tornado.gen.coroutinedef set_cur_user(self, user):self.set_secure_cookie('user', json.dumps(user).encode('utf-8'), expires_days=1, httponly=True)self._cur_user = user@tornado.gen.coroutinedef clear_cur_user(self):self.clear_cookie('user')self._cur_user = None#全局路由注册器
GloabelRouter = Router(base_handler=BaseHandler)

我只增加了最后的一句话

#全局路由注册器
GloabelRouter = Router(base_handler=BaseHandler)

这样在使用的时候就很方便了.

auth.py文件是具体业务逻辑的代码如下.

# -*- coding: UTF-8 -*-import os
import os.path
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web 
import json
from ApiRouter import GloabelRouter# class LoginHandler(tornado.web.RequestHandler):@GloabelRouter.route(method='post', url='/api/auth/login', auth=False)
def Login_Post(handler): username= handler.get_argument("username",default="")password= handler.get_argument("password",default="") ....return " 登录成功"@GloabelRouter.route(method='post', url='/api/auth/logout', auth=False)
def Logout_Post(handler): username= handler.get_argument("username",default="")password= handler.get_argument("password",default="") ......return " 退出成功"
.....
.....
.....

如此简单的定义很多函数即可. 在每个函数的上面加上注解. 方便又轻松.
其他的页面也是如此的道理, 只需要引入然后加上注解

from ApiRouter import GloabelRouter@GloabelRouter.route(method='post', url='/api/auth/logout', auth=False)

至于如何在主程序中使用还需要改成下面的代码
StartWeb.py

#完整打分流程V2
import os
import os.path
# import sys
# sys.path.append('../') import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.autoreload
from ApiRouter import GloabelRouterimport cv2 
import json 
import yolov5.models
import common.Grade
import uuid
import base64
import ssl#解决yolov5的反序列化问题
import os,sys
root_path = os.getcwd()
sys.path.insert(0,root_path+"/yolov5")#解决 tornado 取得中文 出现多余字符的问题.
# print(sys.getdefaultencoding())#reload(sys)
#sys.setdefaultencoding('utf-8')from handlers import  Auth#这里的Auth是文件名 
from tornado.options import define, options # windows 系统下 tornado 使用 使用 SelectorEventLoop 解决一个bug start
import platform
if platform.system() == "Windows":import asyncioasyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# windows 系统下 tornado 使用 使用 SelectorEventLoop 解决一个bug end# print(context.options)
# 定义端口用于指定HTTP服务监听的端口
# 如果命令行中带有port同名参数则会称为全局tornado.options的属性,若没有则使用define定义。
define("port", type=int, default=443, help="run on the given port")
# 调试模式
define("debug", type=bool, default=True, help="debug mode")static_path = os.path.join(os.path.dirname(__file__), 'static')
static_path = static_path.replace("\\","/")chainpath =  os.getcwd()+ "/https/ai.***.com_chain.crt"
crtpath =  os.getcwd()+ "/https/ai.***.com_public.crt"
keypath =  os.getcwd()+ "/https/ai.***.com.key"
#print(crtpath,keypath)
if __name__ == '__main__':tornado.options.parse_command_line()settings = {
    'cookie_secret': base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)}app = tornado.web.Application(handlers = GloabelRouter.handlers,**settings,# handlers=[ # (r'/', OnlineRecognition.OnlineRecognitionHandler), # (r'/OnlineRecognition', OnlineRecognition.OnlineRecognitionHandler), # (r'/OnlineRecognitionYolo5', OnlineRecognitionYolo5.OnlineRecognitionYolo5Handler), # (r'/XiuZheng', XiuZheng.XiuZhengHandler),#修整AI算法的识别名称# (r'/api/DatasetManage', DatasetManage.DatasetManageHandler),## (r'/api/auth/login', Auth.LoginHandler),## (r'/api/auth/logout', Auth.LogoutHandler),## (r'/api/user/info', Auth.UserInfoHandler),## ],template_path=os.path.join(os.path.dirname(__file__), "templates"),settings = {
     'static_path' : static_path ,'debug' : True, "compiled_template_cache":False },static_path =static_path)ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH, cafile=chainpath)# ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) #这种是强制使用TLSV1.2协议ssl_ctx.verify_mode = ssl.CERT_NONEssl_ctx.check_hostname =Falsessl_ctx.load_cert_chain(crtpath, keypath)http_server = tornado.httpserver.HTTPServer(app,ssl_options = ssl_ctx# ssl_options={
    # "certfile": crtpath,# "keyfile": keypath# })print("web https 服务正在监听",options.port)http_server.listen(options.port)print("web https 服务已启动...")#8181 再起一个服务器http_server2 = tornado.httpserver.HTTPServer(app)print("web http 服务正在监听",8181)http_server2.listen(8181)print("web http 服务已启动...")# tornado.ioloop.IOLoop.instance().start()instance = tornado.ioloop.IOLoop.instance()tornado.autoreload.start(5)instance.start()

这段代码里面包含了,
1.如何启用https,
2.如何同时监听https和http两个端口…
3.如果你想, 甚至可以通知监听10 几个以上的端口.
4.如何设置静态资源路径

与本文相关的关键代码是

from handlers import  Auth#这里的Auth是文件名 app = tornado.web.Application(handlers = GloabelRouter.handlers, #GloabelRouter.handlers 中包含了很多的注册信息**settings,.....)