来杯咖啡看Pecan

Pecan的介绍及安装

文章内容来自官方文档: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:存放测试用例。

代码变少了:application的配置

Pecan的配置很容易,通过 一个python的源码式的配置文件就可以完成基本的配置,这个配置的主要目的是指定应用程序的root,然后用于生成WSGI application。我们来看Magnum项目的列子,Magnum项目有个API服务是 用Pecan实现的,在magnum/api/config.py文件中可以找到这个文件,主要内容如下:

 1 app = {
 2     'root': 'magnum.api.controllers.root.RootController',
 3     'modules': ['magnum.api'],
 4     'debug': False,
 5     'hooks': [
 6         hooks.ContextHook(),
 7         hooks.RPCHook(),
 8         hooks.NoExceptionTracebackHook(),
 9     ],
10     'acl_public_routes': [
11         '/'
12     ],
13 }

上面这个app对象就是Pecan的配置,每个Pecan应用都需要有这么一 个名为app的配置。app配置中最重要的就是root的值,这个值表示了应用程序的入口,也就是从哪个地方开始解析HTTP的根path:/。 hooks对应的配置是一些pecan的hook,作用类似于WSGI Middleware。有了app配置后,就可以让Pecan生成一个WSGI application。在Magnum项目中,magnum/api/app.py文件就是生成WSGI application的地方,我们来看一下这个主要的内容:

 1 def get_pecan_config():
 2     # Set up the pecan configuration
 3     filename = api_config.__file__.replace('.pyc', '.py')
 4     return pecan.configuration.conf_from_file(filename)
 5 
 6 
 7 def setup_app(config=None):
 8     if not config:
 9         config = get_pecan_config()
10 
11     app_conf = dict(config.app)
12 
13     app = pecan.make_app(
14         app_conf.pop('root'),
15         logging=getattr(config, 'logging', {}),
16         wrap_app=middleware.ParsableErrorMiddleware,
17         **app_conf
18     )
19 
20     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的是一个类,我们来看代码:

 1 class RootController(rest.RestController):
 2 
 3     _versions = ['v1']
 4     """All supported API versions"""
 5 
 6     _default_version = 'v1'
 7     """The default API version"""
 8 
 9     v1 = v1.Controller()
10 
11     @expose.expose(Root)
12     def get(self):
13         # NOTE: The reason why convert() it's being called for every
14         #       request is because we need to get the host url from
15         #       the request object to make the links.
16         return Root.convert()
17 
18     @pecan.expose()
19     def _route(self, args):
20         """Overrides the default routing behavior.
21 
22         It redirects the request to the default version of the magnum API
23         if the version number is not specified in the url.
24         """
25 
26         if args[0] and args[0] not in self._versions:
27             args = [self._default_version] + args
28         return super(RootController, self)._route(args)

别看这个类这么长,我来解释下你就懂了,首先你可以忽略掉_route()函数,这个函数使用来覆盖Pecan的默认路由实现的,在这里去掉它不妨碍我们理解Pecan(这里的_route()函数的作用把所有请求重定向到默认的API版本去),去掉_route()和其他的东西后,整个类就是变成这么短:

1 class RootController(rest.RestController):
2     v1 = v1.Controller()
3 
4     @expose.expose(Root)
5     def get(self):
6         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的实现:

 1 class Controller(rest.RestController):
 2     """Version 1 API controller root."""
 3 
 4     bays = bay.BaysController()
 5     baymodels = baymodel.BayModelsController()
 6     containers = container.ContainersController()
 7     nodes = node.NodesController()
 8     pods = pod.PodsController()
 9     rcs = rc.ReplicationControllersController()
10     services = service.ServicesController()
11     x509keypairs = x509keypair.X509KeyPairController()
12     certificates = certificate.CertificateController()
13 
14     @expose.expose(V1)
15     def get(self):
16         return V1.convert()
17 
18     ...

上面这个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的路由还是要完整的看一下官方文档。

内置RESTful支持

我们上面举例的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属性来指定其能处理的方法。

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose()
7     def test(self):
8         return 'hello'

上面这个控制器是一个根控制器,指定了/test路径支持GET方法,效果如下:

$ curl http://localhost:8080/test

hello%

那么HTTP请求和HTTP响应呢?

wsme

Pecan对请求和响应的处理

在开始提到WSME之前,我们吸纳来看下Pecan自己对HTTP请求和响应的处理。这样你能更好的理解为什么会引入WSME库。

Pecan框架为每个线程维护了单独的请求和响应的对象,你可以直接在处理函数中访问。

pecan.requesr和pecan.response分别代表当前需要处理的请求和响应对象,你可以直接操作这两个对象,比如指定响应的状态码,就像下面这个列子一样:

1 @pecan.expose()
2 def login(self):
3     assert pecan.request.path == '/login'
4     username = pecan.request.POST.get('username')
5     password = pecan.request.POST.get('password')
6 
7     pecan.response.status = 403
8     pecan.response.text = 'Bad Login!'

这个列子演示了访问POST请求的参数以及返回403,你也可以重新构造一个pecan.Response对象作为返回值:

1 from pecan import expose, Response
2 
3 class RootController(object):
4 
5     @expose()
6     def hello(self):
7         return Response('Hello, World!', 202)

另外,HTTP请求参数的参数也会可以作为控制器方法的参数,还是来看几个官方文档的列子:

1 class RootController(object):
2     @expose()
3     def index(self, arg):
4         return arg
5 
6     @expose()
7     def kwargs(self, **kwargs):
8         return str(kwargs)

这个控制器中的方法直接返回了参数,演示了对GET请求参数的处理,效果是这样的:

1 $ curl http://localhost:8080/?arg=foo
2 foo
3 $ curl http://localhost:8080/kwargs?a=1&b=2&c=3
4 {u'a': u'1', u'c': u'3', u'b': u'2'}

有时候,参数也可能是URL的一部分,比如最后一段path作为参数,就像下面这样:

1 class RootController(object):
2     @expose()
3     def args(self, *args):
4         return ','.join(args)

效果是这样的:

1 $ curl http://localhost:8080/args/one/two/three
2 one,two,three

另外,我们还要看一下POST方法的参数 如何处理:

1 class RootController(object):
2     @expose()
3     def index(self, arg):
4         return arg

效果如下,就是把HTTP body解析成了控制器方法的参数:

1 $ curl -X POST "http://localhost:8080/" -H "Content-Type: 
2 application/x-www-form-urlencoded" -d "arg=foo" foo

返回JSON还是HTML?

如果你不是明确的返回一个Response对象,那么Pecan中方法的返回内容类型就是由expose()装饰器决定的,默认情况下,控制器的方法返回的content-type是HTML。

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose()
7     def test(self):
8         return 'hello'

效果如下:

 1 $ curl -v http://localhost:8080/test
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Tue, 15 Sep 2015 14:31:28 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 5
15 < Content-Type: text/html; charset=UTF-8
16 <
17 * Closing connection 0
18 hello% 

也可以让他返回JSON:

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose('json')
7     def test(self):
8         return 'hello'

效果如下:

 1 curl -v http://localhost:8080/test
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Tue, 15 Sep 2015 14:33:27 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 18
15 < Content-Type: application/json; charset=UTF-8
16 <
17 * Closing connection 0
18 {"hello": "world"}% 

甚至,你可以让一个控制器方法根据URL path的来决定是返回HTML还是JSON:

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose()
7     @expose('json')
8     def test(self):
9         return json.dumps({'hello': 'world'})

返回JSON:

 1 $ curl -v http://localhost:8080/test.json
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test.json HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Wed, 16 Sep 2015 14:26:27 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 24
15 < Content-Type: application/json; charset=UTF-8
16 <
17 * Closing connection 0
18 "{\"hello\": \"world\"}"% 

返回HTML:

 1 $ curl -v http://localhost:8080/test.html
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test.html HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Wed, 16 Sep 2015 14:26:24 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 18
15 < Content-Type: text/html; charset=UTF-8
16 <
17 * Closing connection 0
18 {"hello": "world"}% 

这里要注意一下;

1、同一个字符串作为JSON返回和作为HTML返回是不一样的,仔细看一下HTTP响应的内容。

2、我们的列子中在URL的最后加上了.html后缀或者.json后缀,请尝试一下不加后缀的变化是返回什么?然后,调换一下两个expose()的顺序再试一下。

从上面的列子可以看出,决定响应类型的主要是传递给expose()函数的参数,我们看下expose()函数的完整声明:

1 pecan.decorators.expose(template=None,
2                         content_type='text/html',
3                         generic=False)

template参数用来指定返回值得末班,如果是json就会返回json内容,这里可以指定一个

 HTML文件,或者指定一个mako模板。

content_type指定响应的content-type,默认值是"text/html"

generic参数表明该方法是一个"泛型"方法,可以指定多个不同的函数对应同一个路径的不同的HTTP方法。

看过参数的解释后,你应该能大概了解expose()函数是如何控制HTTP响应的内容和类型的。

 

posted @ 2017-08-24 10:50  WrYcF  阅读(1263)  评论(0编辑  收藏  举报
Live2D