文章内容来自官方文档:http://pecan.readthedocs.io/en/latest/quick_start.html
Pecan 的介绍:
Pecan 是一个路由对象分发的 oython web 框架。本质上可以将 url 通过分割为每一部分,然后对每一部分查找对应处理该 URL 部分的处理类,处理后,继续交给后面部分的 URL 处理,直到所有 URL 部分都被处理后,调用最后分割的 URL 对应的处理函数处理。
本文以 Xshall 为主在其进行操作
Pecan 的安装
创建项目
项目创建好之后目录结构如下
app.py:一般包含了 Pecan 应用的入口,包含初始化代码。
Config.py : 包含 Pecan 的应用配置,会被 app.py 使用。
Controllersl : 这个目录包含所有的控制器,也就是 API 具体逻辑的地方 。
Cotrollers/root.py : 这个包含根路径对应的控制器。
Controllers/v1/ 这个目录放的是版本的 API 的。
Public : 文件夹存放一些 Web 应用所需的 Image,CSS 或者 JavaScript。
setup.py 和 setup.cfg 用于 Web 应用的安装部署。
templates:存储 Html 或者 Json 的末班文件。
tests:存放测试用例。
Pecan 的配置很容易,通过 一个 python 的源码式的配置文件就可以完成基本的配置,这个配置的主要目的是指定应用程序的 root,然后用于生成 WSGI application。我们来看 Magnum 项目的列子,Magnum 项目有个 API 服务是 用 Pecan 实现的,在 magnum/api/config.py 文件中可以找到这个文件,主要内容如下:
- app = {
- 'root': 'magnum.api.controllers.root.RootController',
- 'modules': ['magnum.api'],
- 'debug': False,
- 'hooks': [
- hooks.ContextHook(),
- hooks.RPCHook(),
- hooks.NoExceptionTracebackHook(),
- ],
- 'acl_public_routes': [
- '/'
- ],
- }
上面这个 app 对象就是 Pecan 的配置,每个 Pecan 应用都需要有这么一 个名为 app 的配置。app 配置中最重要的就是 root 的值,这个值表示了应用程序的入口,也就是从哪个地方开始解析 HTTP 的根 path:/。 hooks 对应的配置是一些 pecan 的 hook,作用类似于 WSGI Middleware。有了 app 配置后,就可以让 Pecan 生成一个 WSGI application。在 Magnum 项目中,magnum/api/app.py 文件就是生成 WSGI application 的地方,我们来看一下这个主要的内容:
- def get_pecan_config():
- # Set up the pecan configuration
- filename = api_config.__file__.replace('.pyc', '.py')
- return pecan.configuration.conf_from_file(filename)
- def setup_app(config=None):
- if not config:
- config = get_pecan_config()
- app_conf = dict(config.app)
- app = pecan.make_app(
- app_conf.pop('root'),
- logging=getattr(config, 'logging', {}),
- wrap_app=middleware.ParsableErrorMiddleware,
- **app_conf
- )
- return auth.install(app, CONF, config.app.acl_public_routes)
get_pecan_config()方法读取我们上面提到的 config.py 文件,然后返回一个 pecan.configuration.Config 对象,setup_app()函数首先调用 get_pecan_config()函数获取 application 的配置,然后调用 pecan.make_app()函数创建了一个 WSGI application,调用了 auth.install()函数 (也就是 magnum.api.auth.install() 函数)为刚刚生成的 WSGI application 加上 keystone 的认证中间件(确保所有的请求都会通过 keystone 认证)。
到这边为止,一个 pecan 的 WSGI application 就已经准备好了,只要调用这个 setup_app()函数就获得,至于如何部署这个 WSGI application 请参考 WSGI 简介这篇文章 (https://segmentfault.com/a/1190000003069785) 从 Magnum 这个实际的列子可以看出,使用了 pecan 之后 ,我们不再需要自己写那些 WSGI application 代码了,直接调用 pecan 的 make_app()函数就能完成 这些工作,另外,对于之前使用 pasteDeploy 时用到的很多 WSGI 中间件,可以选择使用 pecan 的 hooks 机制来实现,也选择使用 WSGI 中间件的方式来实现,在 Magnum 的 API 服务就同时使用了这两种方式,其实 pecan 还可以和 pastedeploy 一起使用,ceilometer 项目就是这么做的,大家可以看看。
Pecan 不仅缩减了生成 WSGI application 的代码,而且也让开发人员更容易的指定一个 application 的路由,Pecan 采用了一种对象分发风格 (object-dispatch style) 的路由模式,我们直接通过列子来解释这种路由模式,还是以 Magnum 项目为例。
上面提到了,Magnum 的 API 服务的 root 是 magnum.api.controllers.root.RootController。这里的 RootController 的是一个类,我们来看代码:
- class RootController(rest.RestController):
- _versions = ['v1']
- """All supported API versions"""
- _default_version = 'v1'
- """The default API version"""
- v1 = v1.Controller()
- @expose.expose(Root)
- def get(self):
- # NOTE: The reason why convert() it's being called for every
- # request is because we need to get the host url from
- # the request object to make the links.
- return Root.convert()
- @pecan.expose()
- def _route(self, args):
- """Overrides the default routing behavior.
- It redirects the request to the default version of the magnum API
- if the version number is not specified in the url.
- """
- if args[0] and args[0] not in self._versions:
- args = [self._default_version] + args
- return super(RootController, self)._route(args)
别看这个类这么长,我来解释下你就懂了,首先你可以忽略掉_route() 函数,这个函数使用来覆盖 Pecan 的默认路由实现的,在这里去掉它不妨碍我们理解 Pecan(这里的_route() 函数的作用把所有请求重定向到默认的 API 版本去),去掉_route() 和其他的东西后,整个类就是变成这么短:
- class RootController(rest.RestController):
- v1 = v1.Controller()
- @expose.expose(Root)
- def get(self):
- return Root.convert()
首先,你要记住,这个 RootController 对应的是 URL 中根路径,也就是 path 中最左边的 /。
RootController 继承自 rest.RestController,是 Pecan 实现的 RESTful 控制器,这里 get() 函数表示,当访问的是 GET / 时,由该函数处理,get() 函数会返回一个 WSME 对象,表示已个形式的 HTTP Response,这个下面再讲。get() 函数上面的 expose 装饰器是 Pecan 实现路由控制的一个方式,被 expose 的函数才会被路由处理。
这里的 v1 = v1.Controller() 表示,当访问的是 GET/v1 或者 GET/v1/.... 时,请求由一个 v1.Controller 实例来处理。
为了加深大家的理解,我们再来看下 v1.Controller 的实现:
- class Controller(rest.RestController):
- """Version 1 API controller root."""
- bays = bay.BaysController()
- baymodels = baymodel.BayModelsController()
- containers = container.ContainersController()
- nodes = node.NodesController()
- pods = pod.PodsController()
- rcs = rc.ReplicationControllersController()
- services = service.ServicesController()
- x509keypairs = x509keypair.X509KeyPairController()
- certificates = certificate.CertificateController()
- @expose.expose(V1)
- def get(self):
- return V1.convert()
- ...
上面这个 Controoler 也是继承自 restRestController。所以它的 get 函数表示,当访问的是 GET/v1 的时候,要做的处理。然后它还有很多类属性,这些属性分别表示不同的 URL 路径的控制器:
1、/vq/bays 由 bays 处理
2、/v1baymodels 由 baymodels 处理
3、/v1/containers 由 containers 处理
其他的都是类似的。我们在继续看 bay.B 安阳市 Controller 的代码:
- class BaysController(rest.RestController):
- """REST controller for Bays."""
- def __init__(self):
- super(BaysController, self).__init__()
- _custom_actions = {
- 'detail': ['GET'],
- }
- def get_all(...):
- def detail(...):
- def get_one(...):
- def post(...):
- def patch(...):
- def delete(...):
这个 Controller 中只有函数,没有任何类属性,而且没有实现任何特殊方法,所以 / v1/bays 开头的 URL 处理都在这个 controller 中终结,这个类会处理如下请求:
1、GET /v1/bays
2、GET /v1/bays/{UUID}
3、POST /v1/bays
4、PATCH /v1/bays/{UUID}
5、DELETE /v1/bays/{UUID}
6、GET / v1/bays/detail/{UUID}
看到上面的 3 个 controller 之后,你应该能大概明白 Pecan 是如何对 URL 进行路由的,这种路由方式就是对象分发:(根据类属性)、(包括数据属性) 和方法属性来决定如何路由一个 HTTP 请求,Pecan 的文档中请求额路由有专门的描述,要想掌握 Pecan 的路由还是要完整的看一下官方文档。
我们上面举例的 controller 都是继承自 pecan.rest.RestController,这种 controller 称为 RESTful controller,专门用于实现 RESTful API 的,因此在 Openstack 中使用特别多,Pecan 还支持普通的 controller,称为 Generic controller。Generic controller 继承自 object 对象,默认没有实现对 RESTful 请求的方法。简单的说,RESTful controller 帮我们规定好了 get_one(),get_all(),get(),post() 等方法对应的 HTTP 请求,而 Generic controller 则没有,关于这两种 controller 的区别 ,可以看官方文档, 有很清楚的示例。
对于 RestController 中没有预先定义好的方法,我们可以通过控制器的_custom_actions 属性来指定其能处理的方法。
- class RootController(rest.RestController):
- _custom_actions = {
- 'test': ['GET'],
- }
- @expose()
- def test(self):
- return 'hello'
上面这个控制器是一个根控制器,指定了 / test 路径支持 GET 方法,效果如下:
- $ curl http://localhost:8080/test
hello%
wsme
Pecan 对请求和响应的处理
在开始提到 WSME 之前,我们吸纳来看下 Pecan 自己对 HTTP 请求和响应的处理。这样你能更好的理解为什么会引入 WSME 库。
Pecan 框架为每个线程维护了单独的请求和响应的对象,你可以直接在处理函数中访问。
pecan.requesr 和 pecan.response 分别代表当前需要处理的请求和响应对象,你可以直接操作这两个对象,比如指定响应的状态码,就像下面这个列子一样:
- @pecan.expose()
- def login(self):
- assert pecan.request.path == '/login'
- username = pecan.request.POST.get('username')
- password = pecan.request.POST.get('password')
- pecan.response.status = 403
- pecan.response.text = 'Bad Login!'
这个列子演示了访问 POST 请求的参数以及返回 403,你也可以重新构造一个 pecan.Response 对象作为返回值:
- from pecan import expose, Response
- class RootController(object):
- @expose()
- def hello(self):
- return Response('Hello, World!', 202)
另外,HTTP 请求参数的参数也会可以作为控制器方法的参数,还是来看几个官方文档的列子:
- class RootController(object):
- @expose()
- def index(self, arg):
- return arg
- @expose()
- def kwargs(self, **kwargs):
- return str(kwargs)
这个控制器中的方法直接返回了参数,演示了对 GET 请求参数的处理,效果是这样的:
- $ curl http://localhost:8080/?arg=foo
- foo
- $ curl http://localhost:8080/kwargs?a=1&b=2&c=3
- {u'a': u'1', u'c': u'3', u'b': u'2'}
有时候,参数也可能是 URL 的一部分,比如最后一段 path 作为参数,就像下面这样:
- class RootController(object):
- @expose()
- def args(self, *args):
- return ','.join(args)
效果是这样的:
- $ curl http://localhost:8080/args/one/two/three
- one,two,three
另外,我们还要看一下 POST 方法的参数 如何处理:
- class RootController(object):
- @expose()
- def index(self, arg):
- return arg
效果如下,就是把 HTTP body 解析成了控制器方法的参数:
- $ curl - X POST "http://localhost:8080/" - H "Content-Type:
- application/x-www-form-urlencoded" - d "arg=foo"foo
如果你不是明确的返回一个 Response 对象,那么 Pecan 中方法的返回内容类型就是由 expose() 装饰器决定的,默认情况下,控制器的方法返回的 content-type 是 HTML。
- class RootController(rest.RestController):
- _custom_actions = {
- 'test': ['GET'],
- }
- @expose()
- def test(self):
- return 'hello'
效果如下:
- $ curl -v http://localhost:8080/test
- * Hostname was NOT found in DNS cache
- * Trying 127.0.0.1...
- * Connected to localhost (127.0.0.1) port 8080 (#0)
- > GET /test HTTP/1.1
- > User-Agent: curl/7.38.0
- > Host: localhost:8080
- > Accept: */*
- >
- * HTTP 1.0, assume close after body
- < HTTP/1.0 200 OK
- < Date: Tue, 15 Sep 2015 14:31:28 GMT
- < Server: WSGIServer/0.1 Python/2.7.9
- < Content-Length: 5
- < Content-Type: text/html; charset=UTF-8
- <
- * Closing connection 0
- hello%
也可以让他返回 JSON:
- class RootController(rest.RestController):
- _custom_actions = {
- 'test': ['GET'],
- }
- @expose('json')
- def test(self):
- return 'hello'
效果如下:
- curl -v http://localhost:8080/test
- * Hostname was NOT found in DNS cache
- * Trying 127.0.0.1...
- * Connected to localhost (127.0.0.1) port 8080 (#0)
- > GET /test HTTP/1.1
- > User-Agent: curl/7.38.0
- > Host: localhost:8080
- > Accept: */*
- >
- * HTTP 1.0, assume close after body
- < HTTP/1.0 200 OK
- < Date: Tue, 15 Sep 2015 14:33:27 GMT
- < Server: WSGIServer/0.1 Python/2.7.9
- < Content-Length: 18
- < Content-Type: application/json; charset=UTF-8
- <
- * Closing connection 0
- {"hello": "world"}%
甚至,你可以让一个控制器方法根据 URL path 的来决定是返回 HTML 还是 JSON:
- class RootController(rest.RestController):
- _custom_actions = {
- 'test': ['GET'],
- }
- @expose()
- @expose('json')
- def test(self):
- return json.dumps({'hello': 'world'})
返回 JSON:
- $ curl -v http://localhost:8080/test.json
- * Hostname was NOT found in DNS cache
- * Trying 127.0.0.1...
- * Connected to localhost (127.0.0.1) port 8080 (#0)
- > GET /test.json HTTP/1.1
- > User-Agent: curl/7.38.0
- > Host: localhost:8080
- > Accept: */*
- >
- * HTTP 1.0, assume close after body
- < HTTP/1.0 200 OK
- < Date: Wed, 16 Sep 2015 14:26:27 GMT
- < Server: WSGIServer/0.1 Python/2.7.9
- < Content-Length: 24
- < Content-Type: application/json; charset=UTF-8
- <
- * Closing connection 0
- "{\"hello\": \"world\"}"%
返回 HTML:
- $ curl -v http://localhost:8080/test.html
- * Hostname was NOT found in DNS cache
- * Trying 127.0.0.1...
- * Connected to localhost (127.0.0.1) port 8080 (#0)
- > GET /test.html HTTP/1.1
- > User-Agent: curl/7.38.0
- > Host: localhost:8080
- > Accept: */*
- >
- * HTTP 1.0, assume close after body
- < HTTP/1.0 200 OK
- < Date: Wed, 16 Sep 2015 14:26:24 GMT
- < Server: WSGIServer/0.1 Python/2.7.9
- < Content-Length: 18
- < Content-Type: text/html; charset=UTF-8
- <
- * Closing connection 0
- {"hello": "world"}%
这里要注意一下;
1、同一个字符串作为 JSON 返回和作为 HTML 返回是不一样的,仔细看一下 HTTP 响应的内容。
2、我们的列子中在 URL 的最后加上了. html 后缀或者. json 后缀,请尝试一下不加后缀的变化是返回什么?然后,调换一下两个 expose() 的顺序再试一下。
从上面的列子可以看出,决定响应类型的主要是传递给 expose() 函数的参数,我们看下 expose() 函数的完整声明:
- pecan.decorators.expose(template=None,
- content_type='text/html',
- generic=False)
template 参数用来指定返回值得末班,如果是 json 就会返回 json 内容,这里可以指定一个
HTML 文件,或者指定一个 mako 模板。
content_type 指定响应的 content-type,默认值是 "text/html"
generic 参数表明该方法是一个 "泛型" 方法,可以指定多个不同的函数对应同一个路径的不同的 HTTP 方法。
看过参数的解释后,你应该能大概了解 expose() 函数是如何控制 HTTP 响应的内容和类型的。
来源: http://www.cnblogs.com/wuyongcong/p/7417843.html