来杯咖啡看Pecan

Pecan的介绍及安装

文章内容来自官方文档:http://pecan.readthedocs.io/en/latest/quick_start.htmlhtml

Pecan的介绍:node

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

本文以Xshall为主在其进行操做web

Pecan的安装json

建立项目segmentfault

项目建立好以后目录结构以下api

app.py:通常包含了Pecan应用的入口,包含初始化代码。app

Config.py : 包含Pecan的应用配置,会被 app.py使用。框架

Controllersl : 这个目录包含全部的控制器,也就是API具体逻辑的地方 。curl

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路径的控制器:

一、/vq/bays   由bays处理

二、/v1baymodels  由baymodels处理

三、/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中终结,这个类会处理以下请求:

一、GET /v1/bays

二、GET /v1/bays/{UUID}

三、POST /v1/bays

四、PATCH /v1/bays/{UUID}

五、DELETE /v1/bays/{UUID}

六、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"}% 

这里要注意一下;

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

二、咱们的列子中在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响应的内容和类型的。

相关文章
相关标签/搜索