python 学习记录:pecan 框架原理分析和示例

原理分析

1 pecan库原理

Pecan是一个路由对象分发的python web框架。
本质上可以将url通过分割为每一部分,然后对每一部分查找对应处理该URL部分的处理类,
处理后,继续交给后面部分的URL处理,直到所有URL部分都被处理后,
调用最后分割的URL对应的处理函数处理。

2 pecan逻辑处理流程

2.1 当一个请求从wsgiserver转发过来,首先处理的是Pecan中的__call__方法

主要调用了find_controller和invoke_controller方法。find_controller根据对象分发机制找到url的处理方法,如果没找到,则抛出异常,由后面的except代码块处理,找到了就调用invoke_controller执行该处理方法,将处理结果保存到state中。

2.2 find_controller方法中主要调用了route方法

route(req, self.root, path):
req:WebOb的Request对象,存储了请求的信息
self.root:是第一个处理对象(config.py中定义的root对象)
path:路径信息,如:/v1/books

2.3 route方法中调用了lookup_controller方法对截取后的路径进行继续处理

lookup_controller针对每一个controller对象,在其中查找对应的处理方法,
如果没找到,则会继续找_default,如果没定义_default,
则找_lookup,然后继续循环调用lookup_controller,直到找到对应的方法,或notfound_handlers 为空抛出异常

2.4 lookup_controller方法中调用find_object方法

obj, remainder = find_object(obj, remainder, notfound_handlers, request)具体查找:
obj:当前的controller对象
remainder:路由信息,如[‘v1’, ‘books’]
notfound_handlers:该controller中没找到时,存储_default或者_lookup
request:请求信息

2.5 find_object方法

find_object 首先会处理自定义的路由信息,然后存储_default和_lookup,最后处理默认路由
routing.py中的lookup_controller 和 find_object是核心路由方式的实现,从代码中可以看出,最终找到处理方法的方式是根据路径(/v1/books)中每一个segment来查找对应的对象,然后根据当前对象再查找下一个对象,所以pecan的路由机制叫做对象分发

3 expose方法

标识了这个被装饰的方法可以被路由找到
在routing.py中find_object方法会返回找到的subcontroller,它是有@expose装饰的一个方法

4 总结

pecan库首先根据配置文件中配置的主入口Controller类获取到请求,
然后对url分割,查找对应的Controller对象,经过处理后,根据default或者lookup方法
把除了当前路径外剩余的路径给当前Controller下一个处理的Controller对象处理,直到所有URL都被处理。

参考:
https://blog.csdn.net/qq527631128/article/details/90245555
https://blog.csdn.net/qq527631128/article/details/90426673
https://blog.csdn.net/qq527631128/article/details/91478794

示例

1 实现 hello world

我们先All in One把代码放到一个文件里。
新建一个app.py文件,代码如下:

# /usr/bin/env python
# coding=utf-8

from wsgiref import simple_server
import pecan


class RootController(object):

    @pecan.expose()
    def index(self):
        return "Hello World"


app = pecan.Pecan(RootController())


if __name__ == "__main__":
    sev = simple_server.make_server("127.0.0.1", 5000, app)
    sev.serve_forever()

运行起来,浏览器里输入http://127.0.0.1:5000 查看效果:页面出现 Hello World

解释一下代码

class RootController(object):

    @pecan.expose()
    def index(self):
        return "Hello World"

这定义了一个控制RootController,定义一个Pecan的app需要一个RootController。
这里的index方法名是不能改的,routing的时候会,查找有没有这个方法,而且必须使用装饰器 @pecan.expose()
注意!!!
有些小朋友,会把@pecan.expose**()**写成@pecan.expose,没有带括号。一运行起,界面上就该报:“A server error occurred. Please contact the administrator.”,控制台会打出“AttributeError: ‘instancemethod’ object has no attribute ‘_pecan’”。这就是没用到装饰器,一些属性没有加上。装饰器使用时带不带括号是有区别的。当然也可以写一个兼容的。我们会在最后放一个兼容的,防止一些粗心的小朋友,因为这苦恼。

app = pecan.Pecan(RootController())

这段就是生成一个Pecan的WSGI对象。第一个参数需要传递一个RootController对象,记住是一个object不是类。有的小朋友会写成app = pecan.Pecan(RootController),这样控制台又该报错了“TypeError: unbound method index() must be called with RootController instance as first argument (got nothing instead)

if __name__ == "__main__":
    sev = simple_server.make_server("127.0.0.1", 5000, app)
    sev.serve_forever()

这个使用python自带的模块wsgiref启动一个WSGI app,这个测试用,生产环境不要用。

一个带不带括号都行的expose

# /usr/bin/env python
# coding=utf-8
import pecan
from functools import wraps

def happy_expose(f=None, **kw):
    if f is None:
        def inner_expose(func):
            return happy_expose(func, **kw)
        return inner_expose
    else:
        @wraps(f)
        @pecan.expose(**kw)
        def _expose(*args, **kwargs):
            return f(*args, **kwargs)
        return _expose

2 使用_default 实现路由返回结果

建个文件 root.py

# /usr/bin/env python
# coding=utf-8

from utils import happy_expose


class RootController(object):
    
    @happy_expose
    def index(self):
        return "Hello World"

    @happy_expose
    def _default(self, *remainder):
        return 'Hello World from root default'

这里我们比上一章换了装饰器,可以不用加括号了!,加了个_default的方法。

在建个文件 startup.py

# /usr/bin/env python
# coding=utf-8

from wsgiref import simple_server
import pecan
from root import RootController


app = pecan.Pecan(RootController())


if __name__ == "__main__":
    sev = simple_server.make_server("127.0.0.1", 5000, app)
    sev.serve_forever()

在界面输入 http://127.0.0.1:5000 后,会显示 Hello World

在界面输入 访问http://127.0.0.1:5000/a 后, 会显示 Hello World from root default

我们发现随便输url(比如 http://127.0.0.1:5000/b http://127.0.0.1:5000/b/c/d/e ),除了(http://127.0.0.1:5000)都是打出“Hello World from root default”,都走了默认的处理方法。
这里需要注意的是_default接收的参数为remainder,有的小朋友粗心,写成不带的,那效果就不一样。你会发现 http://127.0.0.1:5000/b http://127.0.0.1:5000/a 这种还能看到Hellow World,而 http://127.0.0.1:5000/b/c/d/e 就是404 Not Found了。
因为Pecan在选择路由时,会把请求路径path,按照/分割成数组,例如[‘b’, ‘c’, ‘d’, ‘e’]。当然去匹配你的_default(remainder),发现这个方法只需要一个参数,而这有4个参数,不匹配。所以就报了404。当然有些具体的应用,需要这种效果,也可以这么写。

3 注册路由方法一—Controller 增加方法处理路由

经过上一章
我们可以让所有路由返回都是一样的。但是这可不是我们开发网站要用的,我们需要不同的路由返回不同的内容。这里我们介绍一种Pecan注册路由的方法。
我们给root.py RootController加一个方法叫做diff。
更改以后

# /usr/bin/env python
# coding=utf-8

from utils import happy_expose


class RootController(object):

    @happy_expose
    def index(self):
        return "Hello World"

    @happy_expose
    def _default(self, *remainder):
        return 'Hello World from root default'

    @happy_expose
    def diff(self):
        return 'You find different worlds'

增加了diff方法,装饰器不要忘了,怎么访问这个不同的路径呢,很简单:
http://127.0.0.1:5000/diff

页面显示 You find different worlds

我们可以通过添加不同的方法名,来处理不同的路由,返回不同的结果。Pecan会根据路由查看,你这个控制器有没有对应的属性,有的话就交给这个属性方法处理。

4 注册路由方法二—Controller 增加方法处理路由

我们已经知道了一种注册路由的方法了,现在介绍第二种。

更改代码,这次我们原有的文件结构不变,给root.py增加点东西,增加后变成下面这样

# /usr/bin/env python
# coding=utf-8

from utils import happy_expose


class BookController(object):

    @happy_expose
    def index(self):
        return "Welcome to the Sea of Books"


class RootController(object):

    book = BookController()

    @happy_expose
    def index(self):
        return "Hello World"

    @happy_expose
    def _default(self, *remainder):
        return 'Hello World from root default'

    @happy_expose
    def diff(self):
        return 'You find different worlds'

两点改变:
一、增加BookController这个类的定义
二、RootController增加一个属性 book = BookController()

检查结果

浏览器输入http://127.0.0.1:5000/book/

页面显示 Welcome to the Sea of Books

我们如愿看到,“Welcome to the Sea of Books”

如果你在控制台看到了“TypeError: unbound method index() must be called with BookController instance as first argument (got nothing instead)”。恭喜你,你是一个粗心的小朋友,把book = BookController,正确的是book = BookController()。

这次的原理,其实是个上一个添加路由的方法是一样的,Pecan会根据路由查看,你这个控制器有没有对应的属性,有的话就交给这个属性方法处理。上一个是添加了一个成员方法,这次是一个成员属性而已。

如果我们访问
http://127.0.0.1:5000/book/sea 会是什么样呢?

页面显示 Hello World from root default

咦,怎么进了RootController的_default了呢?!
其实原理是这么回事的:
Pecan会把路由分成[“book”, “sea”]
第一步:从RootController去发现有没有能处理book的Controller。发现RooController有一个book属性值为BookController的对象。

第二步:Pecan从BookController的对象去发现有没有能处理sea的Controller。找了一圈发现没有,这就尴尬了处理不了!但是为啥没有报404呢?

因为在第一步的时候,Pecan不仅会去发现有没有能处理book的Controller,同时还会去检查RootController有没有一些应对没找到Controller时处理的方法。这些方法可以是_defalut(这个我们已经用过了),_lookup(这个后面在讲)。发现有_default,会将_default放入一个列表,待用。第二步以后发现,没有能处理的Controller,所以调用了RootController对象的_default的方法,我们就看到了“Hello World from root default”

有的好奇的小朋友说,我能不能在BookController里也定义了_default呢?答案是肯定的!如果像:

class BookController(object):

    @happy_expose
    def index(self):
        return "Welcome to the Sea of Books"

    @happy_expose
    def _default(self, *remainder):
        return "This is Book default"

再访问http://127.0.0.1:5000/book/sea 会是什么样呢?

页面显示 This is Book default

为啥是这个结果呢?

第一步的时候,会把RooController()._default,放入一个待用队列,源码里叫做notfound_handlers=[RooController()._default]

第二步的时候,虽然没有发现能处理sea的控制器,也会去查找一个有没有_default,_look_up。发现有_default,也会放入队列。变成notfound_handlers=[RooController()._default, BookController()._default]。

当最终没有能处理[‘book’, ‘sea’]时,开始notfound_handlers上场。他会倒着拿里面的值,就是notfound_handlers.pop()。所以会BookController()._default处理。

5 使用 _lookup 实现路由处理

我们已经有了两种方法去注册一个路由,现在我们第三种方法。OpenStack Neutron组件就是用这种方法实现的。

增加新的 ITController 这个控制器我们用来处理,所有IT类型的书籍,也就是通过/book/it为前缀访问的

class ITController(object):

    @happy_expose
    def _default(self, *remainder):
        return "All the books here are about IT."

使用_lookup 进行路由转发

我们给BookController增加方法_lookup

@happy_expose
    def _lookup(self, *remainder):
        if remainder[0] == "it":
            return ITController(), remainder[1:]

remainder这是一个数组,是去掉book以后的,我们判断紧接着是it的话我们就处理,不是的话就不管他。这个方法有效的返回是第一个是一个控制器,第二个是一个剩余路由的数组。其他返回无效!

访问http://127.0.0.1:500/book/it

页面显示 All the books here are about IT.

我们访问http://127.0.0.1:500/book/it/network 等以/book/it开头的都是这个返回。

我们访问以下httphttp://127.0.0.1:500/book/food

页面显示 This is Book default

我们看到,返回了BookController的_default的处理结果。那么_default和_lookup的处理顺序是啥呢?下面介绍

处理步骤:

我们访问/book/it和/book/food Pecan是如何处理的呢?

第一步

我们知道这两个路由以book开头,会交给BookController去处理

第二步

BookController开始处理,查找有没有可用_default方法,加入notfound_handlers,发现有,我们的notfound_handlers=[BookController()._default]

第三步

查找有没有可用的_lookup方法,加入notfound_handlers,发现有,我们的notfound_handlers=[BookController()._default, [BookController()._lookup]

第四步

查找BookController同名的属性处理,发现没有,抛出PecanNotFound

第五步

接收到PecanNotFound查看一下notfound_handlers里有没有对象,发现有。接着处理

第六步

前面也提到过,会从notfound_handlers队尾获取一个处理方法,也就是BookController()._lookup(后入先出)。
处理/book/it,也就是BookController()._lookup(“it”)
处理/book/food,也就是BookController()._lookup(“food”)
当是it时,发现_lookup返回了一个两个元素的数组,校验,符合返回要求,把返回的第一个元素作为Controller继续处理。再从第一步开始走。不再赘述。
当food时,发现_lookup没有返回,不符合返回要求,交给notfound_handlers继续处理。注意:只有_lookup返回不合法时,会继续交给notfound_handlers,如果是_default返回不合法将直接报404!

第七步

当是food时,_lookup没处理成功,发现notfound_handlers,还能pop出处理对象,就接着交给_default处理,所以页面显示“This is Book default”。

猜你喜欢

转载自blog.csdn.net/weixin_43700106/article/details/107734216