目录

Paste + PasteDeploy + Routes + WebOb 简介

Paste + PasteDeploy + Routes + WebOb 这几个模块组合构成了 Openstack Restful API 的开发框架。

  • 由 Paste + PasteDeploy 完成 Application 的 WSGI 化,其中 PasteDeploy 完成 WSGI Server 和 Application 的构建;
  • Routes 负责 URL 路由转发;
  • WebOb 完成了 WSGI 请求和响应的封装 。

使用该框架开发出来的 Restful API 能够满足 WSGI 规范的要求,但是弊端在于该框架比较复杂,代码量大。只有最初的几个 Openstack 核心项目在使用,后来的新生项目使用了一个相对而言更加简单便捷的 Pecan 框架。

RESTful API 程序的主要特点就是 URL_Path 会和功能对应起来。比如用户管理的功能一般都放在 http://hostname:post/version/<project_id>/user 这个路径下。因此,看一个 RESTful API 程序,一般都是看它实现了哪些 UR_Path,以及每个 URL_Path 对应了什么功能,这个一般都是由 Routes 的 URL 路由功能负责的。所以,熟悉一个RESTful API程序的重点在于确定URL路由。

WSGI入口

WSGI 可以使用 Apache 充当 Web Server,也可以使用 eventlet 进行部署,Keystone Project 都提供了这两种方案的代码实现(也就是 WSGI 的入口)。Keystone Project 在 keystone/httpd/ 目录下,存放了 Apache Web Server 用于部署 WSGI 的相关文件:

  • wsgi-keystone.confmod_wsgi 功能模块的示例配置文件,其中定义了 Post :5000 和 Post :35357 两个虚拟主机,可以通过 Request URL_Path 中 Post 的不同来指定使用哪一个虚拟主机的 WSGI Application 来处理这一个请求。

  • keystone.py 则是 WSGI Application 的入口文件,Web Server 会根据配置路径来加载这个入口文件,在该文件中包含了由 wsgi Module(wsgi.py) 提供的 application 可调用对象,该对象就是 WSGI Application 真正的入口。

Apache 部署方式的 WSGI 入口

#keystone/httpd/keystone.py

import os

from keystone.server import wsgi as wsgi_server

name = os.path.basename(__file__)

# NOTE(ldbragst): 'application' is required in this context by WSGI spec.
# Web Server 只能通过 application 可调用对象来与 Application 进行交互
application = wsgi_server.initialize_application(name)

keystone/httpd/keystone.py 调用了 keystone/keystone/server/wsgi.py 模块中的 initialize_application(name) 函数来载入 WSGI Application,这里主要用到了 Paste + PasteDeploy 库。

#keystone/keystone/server/wsgi.py

def initialize_application(name):

    def loadapp():
return keystone_service.loadapp(
'config:%s' % config.find_paste_config(), name)
#keystone_service.loadapp() 函数内部调用了 paste.deploy.loadapp() 函数来加载 WSGI Application,至于如何加载则由 Paste 的配置文件 keystone-paste.ini 来决定,这个文件也是看懂整个程序的关键。
#config.find_paste_config() 用来查找并加载需要用到的 Paste 配置文件,这个文件在源码中的路径是 keystone/etc/keystone-paste.ini 文件。 _unused, application = common.setup_backends(
startup_application_fn=loadapp) ...
return application #返回一个可调用的 application 对象

name很关键

name = os.path.basename(__file__) 这个变量从 keystone/httpd/keystone.py 文件传递到 initialize_application() 函数,再被传递到 keystone_service.loadapp() 函数,最终被传递到 paste.deploy.loadapp() 函数。name 是用来确定Application 入口的,表示了配置文件 paste.ini 中一个 composite Section 的名字,指定这个 Section 作为处理 HTTP Request 的第一站。在 Keystone 的 paste.ini 中,请求必须先由 [composite:main] 或者 [composite:admin] 处理,所以在 keystone 项目中,name 的值必须是 main 或者 admin 。

上面提到的 keystone/httpd/keystone.py 文件中,name 等于文件名的 basename,所以实际部署中,必须把 keystone.py 重命名为 main.py 或者 admin.py ,这样os.path.basename(__file__)的值才能为 main 或 admin 。

eventlet 部署方式的入口

keystone-all 指令则是采用 eventlet 来进行部署时,可以从 setup.cfg 文件中确定 keystone-all 指令的入口。

#keystone/setup.cfg

[entry_points]
console_scripts
keystone-all = keystone.cmd.all:main #表示 keystone-all 的入口是 all.py 文件中的 main() 函数
keystone-manage = keystone.cmd.manage:main

main() 函数的内容:

#keystone/keystone/cmd/all.py

def main():
eventlet_server.run(possible_topdir)
#main() 函数的主要作用就是运行 eventlet_server ,配置文件从 possible_topdir 中给出。

Paste和PasteDeploy

配置文件 paste.ini

使用Paste和PasteDeploy模块来实现 WSGI Services 时,都需要加载一个 paste.ini 文件。这个文件也是 Paste 模块的精髓,那么这个文件如何阅读呢 ?

paste.ini 文件的格式类似于INI格式,每个 Section 的格式均为 [type:name]

这里重要的是理解几种不同 type 的 Section 的作用:

  • composite : 这种 Section 用于将 HTTP URL Request 分发到指定的 Application 。

    Keystone 的 paste.ini 文件中有两个 composite 的 Section:
#keystone/etc/keystone-paste.ini

[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api
/v3 = api_v3
/ = public_version_api [composite:admin]
use = egg:Paste#urlmap
/v2.0 = admin_api
/v3 = api_v3
/ = admin_version_api #use 是一个关键字,指定处理请求的代码,表明了具体处理请求的分发方式。
#egg:Paste#urlmap 表示使用 Paste 包中的 urlmap 模块来分发请求。
#/v2.0 /v3 / , 是 urlmap 进行分发时,需要使用到的参数。

注意:在 virtualenv 环境下,是到文件 /lib/python2.7/site-packages/Paste-2.0.2.dist-info/metadata.json 下去寻找 urlmap 关键字所对应的函数,而非 egg-info :

{
...
"extensions": {
...
"python.exports": {
"paste.composite_factory": {
"cascade": "paste.cascade:make_cascade",
"urlmap": "paste.urlmap:urlmap_factory"
},
...
}
# 在这个 JSON 文件中,你可以找到 urlmap 关键字对应的 paste.urlmap:urlmap_factory 函数(也就是 paste/urlmap.py 文件中的urlmap_factory()函数)。
# composite 中其他的关键字(/v2.0、 /v3、 /)则是 urlmap_factory() 函数的参数,用于表示不同的URL_Path前缀。
# urlmap_factory() 函数会返回一个 WSGI Application, 其功能是根据不同的 URL_Path 前缀,把请求路由给不同的 Application 。

注意:在不同的版本中可能会有不同的 composite section 实现,EXAMPLE-M版:

[composite:rootapp]
paste.composite_factory = glance.api:root_app_factory
/: apiversions
/v1: apiv1app
/v2: apiv2app
/v3: apiv3app

以[composite:main]为例,看看其他关键字的作用

[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api # /v2.0 开头的请求会路由给 public_api 处理
/v3 = api_v3 # /v3 开头的请求会路由给 api_v3 处理
/ = public_version_api # / 开头的请求会路由给 public_version_api 处理
# public_api/api_v3/public_version_api 这些路由的对象就是 paste.ini 中的其他 Secion Name,而且Section Type 必须是 app 或者 pipeline。
  • pipeline : 用来把一系列的 filter 过滤器和 app 串起来。

    它只有一个关键字就是 pipeline。EXAMPLE:
[pipeline:public_api]
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension user_crud_extension public_service

我们以api_v3这个pipeline为例:

[pipeline:api_v3]
# The last item in this pipeline must be service_v3 or an equivalent
# application. It cannot be a filter.
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension simple_cert_extension revoke_extension federation_extension oauth1_extension endpoint_filter_extension service_v3 # pipeline 关键字指定了很多个名字,这些名字也是 paste.ini 文件中其他的 section Name。
# HTTP Request 从 Sectin composite 被转发到 Section pipeline 之后,该请求会从第一个的 section 开始处理,一直向后传递知道结束。
# pipeline 指定的 section 有如下要求:
# 1. 最后一个名字对应的 section 一定要是一个 app 类型的 Section。
# 2. 非最后一个名字对应的 section 一定要是一个 filter 类型的 Section。
  • filter: 实现一个过滤器中间件。

    filter(以WSGI中间件的方式实现)是用来过滤请求和响应的,应该不同的中间件的过滤,该请求就具有了不同的功能。EXAMPLE:
[filter:sizelimit]
paste.filter_factory = oslo_middleware.sizelimit:RequestBodySizeLimiter.factory
#paste.filter_factory 表示调用哪个函数来获得这个 filter 中间件。
#这个是 [pipeline:api_v3] 这个 Section 指定的第一个 filter ,作用是限制请求的大小。
  • app: 一个 app 就是一个实现主要功能的具体的 WSGI Application 。
[app:service_v3] #是 [pipeline:api_v3] 这个 pipeline Section 的最后一个元素,必须是一个 app Type。
paste.app_factory = keystone.service:v3_app_factory #keystone.service:v3_app_factory 表示获取哪一个 Application
#paste.app_factory 表示调用哪个函数来获得 Application

paste.ini 配置文件中这一大堆配置的作用就是把我们用 Python 写的 WSGI Application 和 Middleware 串起来,规定好 HTTP Request 处理的路径。当 Paste + PasteDeploy 模块提供的 WSGI Server(Web Server) 接受到 URL_Path 形式的 HTTP Request 时,这些 Request 首先会被 Paste 按照配置文件 paste.ini 进行处理,处理的过程为:composite(将Request转发到pipeline或app) ==> pipeline(包含了filter和app) ==> filter(调用Middleware对Request进行过滤) ==> app(具体的Application来实现Request的操作) 。这个过程就是将 Application 和 Middleware 串起来的过程 。

举个例子:

一般情况下,如果希望从 Keystone service 获取一个 token 时,会使到 http://hostname:35357/v3/auth/tokens 这个 API。

我们根据 Keystone 的 paste.ini 配置文件来说明这个 API 是如何被处理的:

  • Step1. (hostname:35357): 这一部分是由 Apache Web Server 来获取并处理的。然后,请求会被 Web Server 转到 WSGI Application 的入口,也就是 keystone/httpd/keystone.py 中的 application 对象取处理。

  • Step2. (/v3/auth/tokens): application 对象根据 paste.ini 中的配置来对剩下的(/v3/auth/tokens)部分进行处理。首先请求的 Post=35357 决定了会经过 [composite:admin] section 。(一般是admin监听35357端口,main监听5000端口,也会受到 name 变量的影响)

  • Step3. (/v3): 决定将请求路由到哪一个 pipeline secion,[composite:admin] 发现请求的 URL_Path 是 /v3 开头的,于是就把请求转发给[pipeline:api_v3]处理,转发之前,会把 /v3 这个部分去掉。
  • Step4. (/auth/tokens) : [pipeline:api_v3]收到请求,URL_Path是 (/auth/tokens),然后开始调用各个 filter(中间件) 来处理请求。最后会把请求交给[app:service_v3]进行处理。
  • Step5. (/auth/tokens): [app:service_v3] 收到请求,URL_Path是 (/auth/tokens),最后交由的 WSGI Application 去处理。

到此为止,paste.ini 中的配置的所有工作都已经做完了。下面请求就要转移到最终的 Application 内部去处理了。

注意:那么通过 paste.ini 处理过后,剩下的一部分URL_Path(/auth/tokens)的路由还没确定,它又是怎么被处理的呢?

中间件的实现

Middleware 中间件在 paste.ini 配置文件中以 filter section 的形式被表示,EXAMPLE

[filter:build_auth_context]
paste.filter_factory = keystone.middleware:AuthContextMiddleware.factory

build_auth_context 这个 filter (中间件)的作用是在 WSGI Application 需要接受的 environ 环境变量参数中添加 KEYSTONE_AUTH_CONTEXT 这个成员Key,包含的内容是认证信息的上下文。这个 filter 的类继承关系为:

keystone.middleware.core.AuthContextMiddleware
-> keystone.common.wsgi.Middleware
-> keystone.common.wsgi.Application
-> keystone.common.wsgi.BaseApplication
  • class keystone.common.wsgi.Middleware 实现了__call__()方法,是 application 对象被调用时运行的方法。
class Middleware(Application):
...
@webob.dec.wsgify()
def __call__(self, request):
try:
response = self.process_request(request)
if response:
return response
response = request.get_response(self.application)
return self.process_response(request, response)
except exceptin.Error as e:
...
...
#__call__() 的实现为接收一个 request 对象,返回一个 response 对象,使用 WebOB Module 的装饰器 `webob.dec.wsgify()` 将它变成标准的 WSGI Application 接口。这里的 request 和 response 对象分别是 webob.Request 和 webob.Response。
#

__call__()内部调用的 self.process_request()keystone.middleware.core.AuthContextMiddleware 中实现:

class AuthContextMiddleware(wsgi.Middleware):
...
def process_request(self, request):
...
request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context #process_request() 会根据功能设计创建 auth_context, 然后赋值给 request.environ[authorization.AUTH_CONTEXT_ENV], 这样就能通过 application 对象的 environ 参数传递到WSGI Application中去了。

Routes

对于URL_Path是以 /v3 开头的请求,在 paste.ini 中会被路由到 [app:service_v3] 这个 app section,并且交给 keystone.service:v3_app_factory 这个函数生成的 application 处理。最后这个 application 需要根据 URL_Path 中剩下的部分(/auth/tokens),来实现 URL 路由。这需要使用 Routes 模块

Routes Module:是用 Python 实现的类似 Rails 的 URL 路由系统,它的主要功能就是把接收到的 URL_Path 映射到对应的操作。Routes 的一般用法是创建一个 Mapper 对象,然后调用该 Mapper 对象的 connect() 方法把 URL_Path 和 HTTP 内建方法映射到一个 Controller 的某个 Action 上。

这里 Controller 是一个自定义的类实例,每一个资源都对应一个 Controller,是对资源操作方法的集合。Action 表示 Controller 对象提供的操作方法(EG. index/show/delect/update/create )。一般调用这些 Action 的时候会把这个 Action 映射到 HTTP 的内置方法中去。EG. GET/POST/DELETE/PUT 。

EXAMPLE

#keystone/auth/routers.py
class Routers(wsgi.RoutersBase): def append_v3_routers(self, mapper, routers):
auth_controller = controllers.Auth() self._add_resource( # 2.
mapper, auth_controller, # 3.
path='/auth/tokens', # 1.
get_action='validate_token',
head_action='check_token',
post_action='authenticate_for_token',
delete_action='revoke_token',
rel=json_home.build_v3_resource_relation('auth_tokens')) ...
#1. ._add_resource() 是专为了路由 URL_Path (/auth/tokens) 而定义的。 其他不同的 URL_Path 也会有对应的的方法定义。 方法名是一致的,但参数不同,尤其是参数 path 的值。
#2. _add_resource() 批量为 /auth/tokens 这个 URL_Path 添加多个 HTTP 内建方法的处理函数。
#3. _add_resource() 的其他参数(get_action/head_action/...) ,可以从名字看出这些参数的作用是指定 HTTP 内建方法对应的处理函数,
# EG. HTTP:GET() ==> Action:get_action ==> 处理函数:validate_token

_add_resource的实现:

def _add_resource(self, mapper, controller, path, rel,
get_action=None, head_action=None, get_head_action=None,
put_action=None, post_action=None, patch_action=None,
delete_action=None, get_post_action=None,
path_vars=None, status=json_home.Status.STABLE):
...
if get_action: #如果传递了 get_action 参数,则执行代码块
getattr(controller, get_action) # ensure the attribute exists
mapper.connect(path, controller=controller, action=get_action,
conditions=dict(method=['GET']))
#调用 mapper 对象的 connect 方法指定一个 URL_Path 的 Action:get_action 映射到 HTTP:GET()
...

总结:URL_Path(/auth/tokens) 和 HTTP 内建的方法,在 Routes Module 中被 Mapper 对象的 connect 方法映射到某一个 Controller 的 Action 操作函数中。实现了调用 URL_Path 即调用 Action 操作函数的效果,而且因为 HTTP 的内建方法也被映射在其中,所以可以很容易的使用 HTTP 协议来实现操作。

WebOb

WebOb 有两个重要的对象:

  • 一个是 Webob.Request,对 WSGI Request 的 environ 参数进行封装。
  • 一个是 webob.Response ,包含了标准 WSGI Response 的所有元素。
  • 此外,还有一个 webob.exc,针对 HTTP 错误代码进行封装。

除了这三种对象,WebOb还提供了一个装饰器webob.dec.wsgify,以便我们可以不使用原始的 WSGI 参数传递和返回格式,而全部使用 WebOb 替代。

EXAMPLE:

@wsgify
def myfunc(req):
return webob.Response('Hey!')

原始方式调用

app_iter = myfunc(environ, start_response)

WebOb调用方式

resp = myfunc(req)

参考资料

Web 开发规范 — WSGI

通过demo学习OpenStack开发

《Openstack 设计与实现》

最新文章

  1. jBPM4.4 no jBPM DB schema: no JBPM4_EXECUTION table. Run the create.jbpm.schema target first in the install tool.
  2. CSS样式之优先级
  3. Windows 保存BMP图片
  4. sequential minimal optimization,SMO for SVM, (MATLAB code)
  5. Android基础之项目结构分析
  6. 317. Shortest Distance from All Buildings
  7. [zz] 安装PostGIS(Linux篇)
  8. 1042: [HAOI2008]硬币购物 - BZOJ
  9. windows下配置lamp环境(2)---配置Apache服务器2.2.25
  10. #include &lt;list&gt;
  11. 高晓松脱口秀--晓说(第一季&amp;第二季)mp3下载
  12. 一种新的隐藏-显示模式诞生——css3的scale(0)到scale(1)
  13. 原生JS获取HTML样式并修改
  14. Linux权限分析
  15. 编写一篇博文介绍COOKIE和Session的原理及异同
  16. D3比例尺
  17. form表单以及内嵌框架标签
  18. 【转】背后的故事之 - 快乐的Lambda表达式(一)
  19. FTP服务器搭建(Centos7)
  20. 拥抱 Android Studio 之一:从 ADT 到 Android Studio

热门文章

  1. phpcms列表分页ajax加载更多
  2. Socket编程半双工
  3. Python入门习题9.数码管时间
  4. 【Vue 2.X】基于input[type=&#39;number&#39;]封装parseFloat、parseInt-自定义指令系列(一)
  5. mitdump爬取当当网APP图书目录
  6. httpclient请求接口,上传文件附加参数(.net core)
  7. js本地时间格式化
  8. python车牌精确定位
  9. java写文件UTF-8格式
  10. springboot创建一个服务,向eureka中注册,使用swagger2进行服务管理