Openstack Restful API 开发框架 Paste + PasteDeploy + Routes + WebOb

简介: 目录目录Paste PasteDeploy Routes WebOb 简介WSGI入口Paste和PasteDeploy配置文件 pasteini中间件的实现RoutesWebOb参考资料Paste + PasteDeploy + Routes + WebOb 简介Paste + PasteDeploy + Routes + WebOb 这几个模块组合构成了 Openstack Restful API 的开发框架。

目录

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 设计与实现》

相关文章
|
9天前
|
安全 Java API
RESTful API设计与实现:Java后台开发指南
【4月更文挑战第15天】本文介绍了如何使用Java开发RESTful API,重点是Spring Boot框架和Spring MVC。遵循无状态、统一接口、资源标识和JSON数据格式的设计原则,通过创建控制器处理HTTP请求,如示例中的用户管理操作。此外,文章还提及数据绑定、验证、异常处理和跨域支持。最后,提出了版本控制、安全性、文档测试以及限流和缓存的最佳实践,以确保API的稳定、安全和高效。
|
12天前
|
小程序 前端开发 API
小程序全栈开发中的RESTful API设计
【4月更文挑战第12天】本文探讨了小程序全栈开发中的RESTful API设计,旨在帮助开发者理解和掌握相关技术。RESTful API基于REST架构风格,利用HTTP协议进行数据交互,遵循URI、客户端-服务器架构、无状态通信、标准HTTP方法和资源表述等原则。在小程序开发中,通过资源建模、设计API接口、定义资源表述及实现接口,实现前后端高效分离,提升开发效率和代码质量。小程序前端利用微信API与后端交互,确保数据流通。掌握这些实践将优化小程序全栈开发。
|
21天前
|
前端开发 Java API
构建RESTful API:Java中的RESTful服务开发
【4月更文挑战第3天】本文介绍了在Java环境中构建RESTful API的重要性及方法。遵循REST原则,利用HTTP方法处理资源,实现CRUD操作。在Java中,常用框架如Spring MVC简化了RESTful服务开发,包括定义资源、设计表示层、实现CRUD、考虑安全性、文档和测试。通过Spring MVC示例展示了创建RESTful服务的步骤,强调了其在现代Web服务开发中的关键角色,有助于提升互操作性和用户体验。
构建RESTful API:Java中的RESTful服务开发
|
JavaScript Java Serverless
入门 | 云开发平台1分钟开发一个API
云开发系列课程主要介绍了从入门到精通快速上手Serverless和云开发技术。学习内容涵盖云开发协同、云函数、云数据库、多媒体托管、前后端一体化框架等Serverless Web开发必备知识。希望通过云开发系列课程的学习与实际操作,让大家深入了解Serverless和云开发技术,并加深对阿里云云开发平台和阿里云Serverless产品的理解与认识。 本篇内容作为入门知识,让你在一分钟之内运行起一个Java/NodeJS/Python/PHP任何一门语言的Serverless API ,让你可以在后续的课程中向API添加各种有意思的功能。
入门 | 云开发平台1分钟开发一个API
|
15天前
|
缓存 前端开发 API
API接口封装系列
API(Application Programming Interface)接口封装是将系统内部的功能封装成可复用的程序接口并向外部提供,以便其他系统调用和使用这些功能,通过这种方式实现系统之间的通信和协作。下面将介绍API接口封装的一些关键步骤和注意事项。
|
22天前
|
监控 前端开发 JavaScript
实战篇:商品API接口在跨平台销售中的有效运用与案例解析
随着电子商务的蓬勃发展,企业为了扩大市场覆盖面,经常需要在多个在线平台上展示和销售产品。然而,手工管理多个平台的库存、价格、商品描述等信息既耗时又容易出错。商品API接口在这一背景下显得尤为重要,它能够帮助企业在不同的销售平台之间实现商品信息的高效同步和管理。本文将通过具体的淘宝API接口使用案例,展示如何在跨平台销售中有效利用商品API接口,以及如何通过代码实现数据的统一管理。
|
1月前
|
安全 算法 API
产品经理必备知识——API接口
前言 在古代,我们的传输信息的方式有很多,比如写信、飞鸽传书,以及在战争中使用的烽烟,才有了著名的烽火戏诸侯,但这些方式传输信息的效率终究还是无法满足高速发展的社会需要。如今万物互联的时代,我通过一部手机就可以实现衣食住行的方方面面,比如:在家购物、远程控制家电、自动驾驶等等,背后都离不开我们今天要聊的API接口。
|
1月前
|
数据采集 JSON API
如何实现高效率超简洁的实时数据采集?——Python实战电商数据采集API接口
你是否曾为获取重要数据而感到困扰?是否因为数据封锁而无法获取所需信息?是否因为数据格式混乱而头疼?现在,所有这些问题都可以迎刃而解。让我为大家介绍一款强大的数据采集API接口。
|
2天前
|
Java API Android开发
[NDK/JNI系列04] JNI接口方法表、基础API与异常API
[NDK/JNI系列04] JNI接口方法表、基础API与异常API
10 0
|
5天前
|
XML JSON API
快速淘宝商品详情页面API接口传输 php
PI(Application Programming Interface,应用程序接口)是一组预定义的函数、协议和工具,用于构建软件应用程序之间的交互。它允许不同的软件系统和应用通过统一的接口进行数据交换和通信