openstack...dome。。。

巨啰嗦的框架:Paste + PasteDeploy + Routes + WebOb。后来OpenStack社区的人受不了这么啰嗦的代码了,决定换一个框架,他们最终选中了Pecan。Pecan框架相比上一篇文章的啰嗦框架有如下好处:

不用自己写WSGI application了

请求路由很容易就可以实现了

总的来说,用上Pecan框架以后,很多重复的代码不用写了,开发人员可以专注于业务,也就是实现每个API的功能。

Pecan

Pecan框架的目标是实现一个采用对象分发方式进行URL路由的轻量级Web框架。它非常专注于自己的目标,它的大部分功能都和URL路由以及请求和响应的处理相关,而不去实现模板、安全以及数据库层,这些东西都可以通过其他的库来实现。关于Pecan的更多信息,可以查看文档:https://pecan.readthedocs.org/en/latest/index.html。本文以OpenStack的magnum项目为例来说明Pecan项目在实际中的应用,但是本文不会详细讲解Pecan的各个方面,一些细节请读者阅读Pecan的文档。

项目中的代码结构

使用Pecan框架时,OpenStack项目一般会把API服务的实现都放在一个api目录下,比如magnum项目是这样的:

➜ ~/openstack/env/p/magnum git:(master) $ tree magnum/apimagnum/api├── app.py├── auth.py├── config.py├── controllers│  ├── base.py│  ├── __init__.py│  ├── link.py│  ├── root.py│  └── v1│      ├── base.py│      ├── baymodel.py│      ├── bay.py│      ├── certificate.py│      ├── collection.py│      ├── container.py│      ├── __init__.py│      ├── magnum_services.py│      ├── node.py│      ├── pod.py│      ├── replicationcontroller.py│      ├── service.py│      ├── types.py│      ├── utils.py│      └── x509keypair.py├── expose.py├── hooks.py├── __init__.py├── middleware│  ├── auth_token.py│  ├── __init__.py│  └── parsable_error.py├── servicegroup.py└── validation.py

你也可以在Ceilometer项目中看到类似的结构。介绍一下几个主要的文件,这样你以后看到一个使用Pecan的OpenStack项目时就会比较容易找到入口。

app.py 一般包含了Pecan应用的入口,包含应用初始化代码

config.py 包含Pecan的应用配置,会被app.py使用

controllers/ 这个目录会包含所有的控制器,也就是API具体逻辑的地方

controllers/root.py 这个包含根路径对应的控制器

controllers/v1/ 这个目录对应v1版本的API的控制器。如果有多个版本的API,你一般能看到v2等目录。

代码变少了:application的配置

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的地方,我们来看一下这个的主要内容:

defget_pecan_config():# Set up the pecan configurationfilename = api_config.__file__.replace('.pyc','.py')returnpecan.configuration.conf_from_file(filename)defsetup_app(config=None):ifnotconfig: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)returnauth.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简介这篇文章。

从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的是一个类,我们来看它的代码:

classRootController(rest.RestController):_versions = ['v1']"""All supported API versions"""_default_version ='v1'"""The default API version"""v1 = v1.Controller()@expose.expose(Root)defget(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.returnRoot.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.

"""ifargs[0]andargs[0]notinself._versions:args = [self._default_version] + argsreturnsuper(RootController, self)._route(args)

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

classRootController(rest.RestController):v1 = v1.Controller()@expose.expose(Root)defget(self):returnRoot.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的实现:

classController(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)defget(self):returnV1.convert()    ...

上面这个Controller也是继承自rest.RestController。所以它的get函数表示,当访问的是GET /v1的时候,要做的处理。然后,它还有很多类属性,这些属性分别表示不同URL路径的控制器:

/v1/bays 由bays处理

/v1/baymodels 由baymodels处理

/v1/containers 由containers处理

其他的都是类似的。我们再继续看bay.BaysController的代码:

classBaysController(rest.RestController):"""REST controller for Bays."""def__init__(self):super(BaysController, self).__init__()_custom_actions = {'detail': ['GET'],}defget_all(...):defdetail(...):defget_one(...):defpost(...):defpatch(...):defdelete(...):

这个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的区别,可以看官方文档Writing RESTful Web Services with Generic Controllers,有很清楚的示例。

对于RestController中没有预先定义好的方法,我们可以通过控制器的_custom_actions属性来指定其能处理的方法。

classRootController(rest.RestController):_custom_actions = {'test': ['GET'],}@expose()deftest(self):return'hello'

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

$ curlhttp://localhost:8080/testhello%

那么HTTP请求和HTTP响应呢?

上面讲了这么多,我们都没有说明在Pecan中如何处理请求和如何返回响应。这个将在下一章中说明,同时我们会引入一个新的库WSME

WSME

Pecan对请求和响应的处理

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

Pecan框架为每个线程维护了单独的请求和响应对象,你可以直接在请求处理函数中访问。pecan.requestpecan.response分别代表当前需要处理的请求和响应对象。你可以直接操作这两个对象,比如指定响应的状态码,就像下面这个例子一样(例子来自官方文档):

@pecan.expose()deflogin(self):assertpecan.request.path =='/login'username = pecan.request.POST.get('username')password = pecan.request.POST.get('password')pecan.response.status =403pecan.response.text ='Bad Login!'

这个例子演示了访问POST请求的参数以及返回403。你也可以重新构造一个pecan.Response对象作为返回值(例子来自官方文档):

frompecanimportexpose, ResponseclassRootController(object):@expose()defhello(self):returnResponse('Hello, World!',202)

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

classRootController(object):@expose()defindex(self, arg):returnarg@expose()defkwargs(self, **kwargs):returnstr(kwargs)

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

$ curlhttp://localhost:8080/?arg=foofoo$ curlhttp://localhost:8080/kwargs?a=1&b=2&c=3{u'a': u'1', u'c': u'3', u'b': u'2'}

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

classRootController(object):@expose()defargs(self, *args):return','.join(args)

效果是这样的:

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

另外,我们还要看一下POST方法的参数如何处理(例子来自官方文档):

classRootController(object):@expose()defindex(self, arg):returnarg

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

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

返回JSON还是HTML?

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

classRootController(rest.RestController):_custom_actions = {'test': ['GET'],}@expose()deftest(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

也可以让它返回JSON:

classRootController(rest.RestController):_custom_actions = {'test': ['GET'],}@expose('json')deftest(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

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

classRootController(rest.RestController):_custom_actions = {'test': ['GET'],}@expose()@expose('json')deftest(self):returnjson.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

返回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

这里要注意一下:

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

我们的例子中在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响应的内容和类型的。

用WSME来做什么?

上面两节已经说明了Pecan可以比较好的处理HTTP请求中的参数以及控制HTTP返回值。那么为什么我们还需要WSME呢?因为Pecan在做下面这个事情的时候比较麻烦:请求参数和响应内容的类型检查(英文简称就是typing)。当然,做是可以做的,不过你需要自己访问pecan.request和pecan.response,然后检查指定的值的类型。WSME就是为解决这个问题而生的,而且适用场景就是RESTful API。

WSME简介

WSME的全称是Web Service Made Easy,是专门用于实现REST服务的typing库,让你不需要直接操作请求和响应,而且刚好和Pecan结合得非常好,所以OpenStack的很多项目都使用了Pecan + WSME的组合来实现API(好吧,我看过的项目,用了Pecan的都用了WSME)。WSME的理念是:在大部分情况下,Web服务的输入和输出对数据类型的要求都是严格的。所以它就专门解决了这个事情,然后把其他事情都交给其他框架去实现。因此,一般WSME都是和其他框架配合使用的,支持Pecan、Flask等。WSME的文档地址是http://wsme.readthedocs.org/en/latest/index.html

WSME的使用

用了WSME后的好处是什么呢?WSME会自动帮你检查HTTP请求和响应中的数据是否符合预先设定好的要求。WSME的主要方式是通过装饰器来控制controller方法的输入和输出。WSME中主要使用两个控制器:

@signature: 这个装饰器用来描述一个函数的输入和输出。

@wsexpose: 这个装饰器包含@signature的功能,同时会把函数的路由信息暴露给Web框架,效果就像Pecan的expose装饰器。

这里我们结合Pecan来讲解WSME的使用。先来看一个原始类型的例子:

fromwsmeext.pecanimportwsexposeclassRootController(rest.RestController):_custom_actions = {'test': ['GET'],}@wsexpose(int, int)deftest(self, number):returnnumber

如果不提供参数,访问会失败:

$ curlhttp://localhost:8080/test{"debuginfo":null,"faultcode":"Client","faultstring":"Missing argument: \"number\""}%

如果提供的参数不是整型,访问也会失败:

$ curlhttp://localhost:8080/test\?number\=a{"debuginfo":null,"faultcode":"Client","faultstring":"Invalid input for field/attribute number. Value: 'a'. unable to convert to int"}%

上面这些错误信息都是由WSME框架直接返回的,还没有执行到你写的方法。如果请求正确,那么会是这样的:

$ curl -v http://localhost:8080/test\?number\=1*Hostname was NOT found in DNS cache*Trying 127.0.0.1...*Connected to localhost (127.0.0.1) port 8080 (#0)> GET /test?number=1 HTTP/1.1> User-Agent: curl/7.38.0> Host: localhost:8080> Accept: */*>

* HTTP 1.0, assume close after body

请注意返回的content-type,这里返回JSON是因为我们使用的wsexpose设置的返回类型是XML和JSON,并且JSON是默认值。上面这个例子就是WSME最简单的应用了。

那么现在有下面这些问题需要思考一下:

如果想用POST的方式来传递参数,要怎么做呢?提示:要阅读WSME中@signature装饰器的文档。

如果我希望使用/test/1这种方式来传递参数要怎么做呢?提示:要阅读Pecan文档中关于路由的部分。

WSME中支持对哪些类型的检查呢?WSME支持整型、浮点型、字符串、布尔型、日期时间等,甚至还支持用户自定义类型。提示:要阅读WSME文档中关于类型的部分。

WSME支持数组类型么?支持。

上面的问题其实也是很多人使用WSME的时候经常问的问题。我们将在下一篇文章中使用Pecan + WSME来继续开发我们的demo,并且用代码来回答上面所有的问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,748评论 6 342
  • 一、Web开发 Browser/Server模式目前最流行,简称BS架构。在BS架构下,客户端只需要浏览器,应用程...
    时间之友阅读 848评论 0 0
  • 只要你还活着, 每天就必须吃喝, 必须拉撒! 必须穿着衣服走出去, 人模狗样。 必须向活着的人打招呼, 应酬八方。...
    简村小吹阅读 206评论 31 8
  • 当我每天忙着挣点买房的首付,努力日晚贪黑,房价蹭蹭的上涨让我紧衣缩食心声无望。身边的人也是拼命挣钱为了家庭孩子像老...
    阿秀仔阅读 218评论 0 0