徒手撸出一个类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

Flask 学习-43.Flask-RESTX 路由注册的2种方式

深入学习Flask框架之视图及路由

徒手打造你的MVC框架

徒手打造你的MVC框架

flask框架初识 1