徒手撸出一个类Flask微框架路由及路由注册的演变
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了徒手撸出一个类Flask微框架路由及路由注册的演变相关的知识,希望对你有一定的参考价值。
路由的基本概念:
根据不同的访问路径调用不同的方法或者类
from webob import Response,Request,dec from wsgiref.simple_server import make_server,demo_app def index(request:Request): res = Response() res.body = 'index.html'.encode() return res def showpython(request:Request): res = Response() res.body = 'show_python'.encode() return res def notfound(): res = Response() res.status_code = 404 return res @dec.wsgify def app(request:Request): if request.path == '/': return index(request) elif request.path == '/python': return showpython(request) else: return notfound()
如果用字典描述它是否更好
如果找不到则调用notfound
@dec.wsgify def app(request:Request): ROUTE.get(request.path,notfound)(request) if __name__ == "__main__": ip = '127.0.0.1' port = 9999 server = make_server(ip,port,app) server.serve_forever() server.server_close()
实现注册
这样的话就可以将路由表设置为空
用这个路由的话起码保证了已经注册过了
def index(reques:Request): res = Response() res.body = 'index.html'.encode() return res def showpython(reques:Request): res = Response() res.body = 'python.html'.encode() return res def notfound(): res = Response() res.status_code = 404 res.body = 'Not Found'.encode() return res # Route_Table ROUTERTABLE = {} def register(path,handler): ROUTERTABLE[path] = handler register('/',index) register('/python',showpython) @wsgify def app(request:Request): ROUTE.get(request.path, notfound)(request)
路由的封装
class APP: def index(self,request:Request): res = Response() res.body = 'hello'.encode() return res def showpython(self,request:Request): res = Response() res.body = 'show python'.encode() return res
分析好处与瓶颈分别在哪里
对一个web框架来讲,这些方法都应该是用户自己去定义,而并非是框架内去完成的,因为框架是提供用户去使用,所以是由使用者去实现
那么对于对于哪些是放在外面,合理的规划如下:
class APPlication: def __init__(self,env,start_response): pass def notfound(request:Request): res = Response res.status_code = 404 res.body = '404'.encode() return res #路由表 ROUTERTABLE = {} def register(path,handler): ROUTERTAB[path] = handler @dec.wsgify def app(request:Request): return ROUTETABLE.get(request.path,notfound)(request) #在外部定义执行的函数 def index(self,request:Request): res = Response() res.body = 'hello'.encode() return res
类的实例化
如果通过类进行定义那么必须实例化,因为需要__init__的支撑
对于一个类中要么在init中实现,要么在__call__ 中实现
__init__:如果定义在init中,实例化后则是被写死,显然不符当前调用的灵活性,相当于在调用缺省的两个函数
class APPlication: def __init__(self,env,start_response):
__call__:如果在__call__中定义,显然调用和传参是比较灵活的
思考一个问题:__call__是如何调用的
改进:
对于server来讲
server = make_server(ip,port,app)
一定是看到两个参数调用,因为有装饰器被装饰一定保证这个__call__ 一定被转化成wsgify
class Application: def notfound(request:Request): res = Response() res.status_code = 404 res.body = 'not found~~'.encode() return res ROUTETABLE = {} # 注册 def register(path,handler): ROUTETABLE[path] = handler @dec.wsgify def __call__(self,request:Request): return ROUTETABLE.get(request.path, notfound)(request) def index(request:Request): res = Response res.body = 'hh'.encode() return res
这样的话通过装饰器进行修饰之后,变成了我们想要的方式
最开始的__call__方法必须满足两个参数 request 和 response 必须保证进出,那如果通过wsgify的装饰之后,那么确保这个__call__变成了接口,满足了接口定义的要求
这里request就是业务上的第一个参数
通过request传进之后,返回一个返回值,可以达到最后要求,只不过是用装饰器来完成
返回值需要通过装饰器 start_response 保证最后是一个可迭代对象
简单的来讲,就是将这个例子:
@wsgify
def myfunc(request:Request):
return Response('hey here')
转为了另一个例子:
@wsgify
#def __call__(self, *args, **kwargs):
def __call__(self,request:Request):
return ROUTER.get(request.path, self.notfound)(request)
通过__call__ 方法可以直接拿来做函数使用,这就省去了一些不必要的麻烦
添加异常,避免访问出现问题
@dec.wsgify
def __call__(self,request:Request):
try:
print(request) #将request 传递给了index(request)
return cls.ROUTE[request.path](request)
except:
return self.notfound(request)
加入注册
倾向于使用类方法,将注册函数加入到类中
先到server中由其交给业务函数管理,业务函数是用户根据自己的需求自己填写内容
from webob import Response,Request,dec,exc from wsgiref.simple_server import make_server,demo_app import re class Application: def notfound(self,request:Request): res = Response() res.status_code = 404 res.body = 'hel'.encode() return res ROUTE = { } @classmethod def register(cls,path,handler): cls.ROUTE[path] = handler @dec.wsgify def __call__(self, request:Request): return self.ROUTE.get(request.path, self.notfound)(request) def index(request:Request): res = Response() res.status_code = 200 res.body = 'hhh'.encode() return res Application.register('/',index) if __name__ == "__main__": ip = '127.0.0.1' port = 9999 server = make_server(ip,port,Application()) server.serve_forever() server.server_close()
exc 异常模块及状态码相关处理
exc主要提供了异常模块及状态码相关的处理功能
导入模块
from webob import Response,Request,dec,exc
查看源码:
看到源码内定义了几乎所有的HTTP_Code 每个都是通过单独类来实现,并继承自HTTPClientxxxx
比如:200的类
class HTTPOk(WSGIHTTPException): """ Base class for the 200's status code (successful responses) code: 200, title: OK """ code = 200 title = 'OK'
再比如404的类
class HTTPNotFound(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server did not find anything matching the Request-URI. code: 404, title: Not Found """ code = 404 title = 'Not Found' explanation = ('The resource could not be found.')
发现每个状态码的类都是子类,继续跟进父类查看:
查看HTTPClientError
class HTTPClientError(HTTPError): code = 400 title = 'Bad Request' explanation = ('The server could not comply with the request since\r\n' 'it is either malformed or otherwise incorrect.\r\n') 也是一个子类,再次跟进HTTPError class HTTPError(WSGIHTTPException): WSGIHTTPException 代码如下 class WSGIHTTPException(Response, HTTPException): ## You should set in subclasses: # code = 200 # title = 'OK' # explanation = 'why this happens' # body_template_obj = Template('response template') code = 500 title = 'Internal Server Error' explanation = '' body_template_obj = Template(''' ${explanation}<br /><br /> ${detail} ${html_comment} ''')
它的子类将其方法进行逐层覆盖,并返回其实还是调用的这个方法
并调用了wsgi的接口函数wsgi_response
最后实际调用的是__call__并传递两个参数,environ 请求的所有信息 以及 start_response
所谓http code就是封装一个编号并返回response body的内容
exc是多继承,继承了response,将一个缺省的body可以传递
如果出现某些问题则调用异常,这个异常本身继承resopnse进行raise出去
所以直接调用exc.类名就可以了
@dec.wsgify def __call__(self,request:Request): try: print(request) return self.ROUTE[request.path](request) except: return exc.HTTPNotFound('hahaha')
所以,只要抛异常就是没有正常获取,直接抛异常即可
改进注册过程,使用装饰器进行包装
我们看到效果,当注册路由的时候,使用Application.reg功能进行调用,但是明显是不灵活的
能否像其他框架直接在方法或者函数上面写明要指定的路由路径呢
比如
@xxxxx.register('/',index) def index(request:Response): res = Response() res.body = 'index'.encode() return res 目前来看,只能使用装饰器来完成当前效果了 等价式:--> index = app.reg('/')(index) @dec.wsgify def __call__(self,request:Request): try: print(request) return self.ROUTE[request.path](request) # return self.ROUTE.get(request.path, self.notfound)(request) except: return exc.HTTPNotFound('hahaha') # @Application.reg('/',index) 带参装饰器 --> Application() --> func(path)(index) @Application.reg('/') def index(request:Response): res = Response() res.body = 'index'.encode() return res
路由主要是解决了后端问题
完整如下:
from webob import Response,Request,exc from webob.dec import wsgify from wsgiref.simple_server import make_server,demo_app import re # @wsgify # def index(request:Request): # res = Response() # res.body = 'index.html'.encode() # return res class Application: ROUTER_TABLE = {} @classmethod def register(cls,path): def wapper(handler): cls.ROUTER_TABLE[path] = handler print(cls.ROUTER_TABLE) return handler return wapper @wsgify def __call__(self, request): try: print(request.path) print(request) print(self.ROUTER_TABLE[request.path]) return self.ROUTER_TABLE[request.path](request) except: raise exc.HTTPNotFound('not funod~~') @Application.register('/') # --> index = app.reg('/')(index) def index(request:Request): res = Response() res.body = 'index.html'.encode() return res @Application.register('/python') # --> index = app.reg('/')(index) def showpython(request:Request): res = Response() res.body = 'python'.encode() return res if __name__ == "__main__": ip = '127.0.0.1' port = 9999 server = make_server(ip,port,Application()) server.serve_forever() server.server_close()
以上是关于徒手撸出一个类Flask微框架路由及路由注册的演变的主要内容,如果未能解决你的问题,请参考以下文章
徒手撸出一个类Flask微框架 理解HTTP请求 request和response