【编排】基于senlin接口的wsgi app解读

Openstack项目的REST接口普遍使用wsgi app,作者在这里通过对senlin项目接口的解读,来帮助大家理解wsgi app,其中也存在很多不是很理解的地方,还需要读者一起交流探讨。

一、wsgi app介绍:

1.背景

Python Web开发中,服务端程序可以分为两个部分:服务器程序和应用程序。前者负责接收、整理客户端请求,后者负责具体的逻辑处理。为了方便应用程序的开发,我们把常用的功能封装起来,成为各种Web开发框架,例如Django,Flask,Tornado等。
不同的框架有不同的开发方式,但是不管怎样,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样服务器程序就需要为不同的框架提供不同的支持,这样混乱的局面无论对服务器还是框架都是不好的。
这时候标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确立,双方就可以各自实现,这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

2.WSGI是什么

WSGI是服务器程序与应用程序的一个规定,它规定了双方各自需要实现什么接口,提供什么功能,以便二者能够配合使用。
WSGI不能规定的太复杂,否则对已有的服务器实现起来会困难,不利于WSGI的普及。同时,WSGI不能规定的太多,例如cookie处理就没有在WSGI中规定,这是为了给框架最大的灵活性。另一方面,WSGI需要middleware易于实现。
middleware处于服务器程序与应用程序之间,对服务器程序来说,它相当于应用程序,对应用程序来说,它相当于服务器程序。对用户请求的处理,可以变成多个middleware的叠加处理,每个middleware实现不同的功能。请求从服务器程序来的时候,依次通过middleware,相应从应用程序返回的时候,反向通过层层middleware。我们可以方便地添加、替换middleware,以便对用户请求作出不同的处理。

二、代码解读:

下面结合senlin代码的解读,来更形象的解释WSGI框架:

  • 服务器程序启动:

服务器程序是随着senlin-api服务启动而启动的:

/senlin/bin/senlin-api #类似的还有/senlin/bin/senlin-engine和/senlin/bin/senlin-manage服务的启动
app = config.load_paste_app()
host = cfg.CONF.senlin_api.bind_host #服务绑定的主机ip
port = cfg.CONF.senlin_api.bind_port #服务绑定的端口号
LOG.info(_LI('Starting Senlin API on %(host)s:%(port)s'),
        {'host': host, 'port': port})
server = wsgi.Server('senlin-api', cfg.CONF.senlin_api)
server.start(app, default_port=port)
  • 加载api-paste文件:

从上述代码中看到,服务启动之前有app = config.load_paste_app()代码,主要就是实现api-paste里面filter_factory类和app_factory类的加载的:

senlin/common/config.py:
app = wsgi.paste_deploy_app(conf_file, app_name, cfg.CONF)
# Log the options used when starting if we're in debug mode...
if cfg.CONF.debug:
    cfg.CONF.log_opt_values(logging.getLogger(app_name),
                            sys_logging.DEBUG)
return app
senlin/common/wsgi.py
setup_paste_factories(conf)
try:
    return deploy.loadapp("config:%s" % paste_config_file, name=app_name)
finally:
    teardown_paste_factories()
  • paste文件详解:

pipeline表示一个请求过来,需要走过的组件;app表示接口处理类;filter表示过滤类;

# senlin-api pipeline
[pipeline:senlin-api]
pipeline = request_id faultwrap ssl versionnegotiation webhook authtoken context trust apiv1app
[app:apiv1app]
paste.app_factory = senlin.common.wsgi:app_factory #senlin.app_factory对应的类
senlin.app_factory = senlin.api.openstack.v1:API
# Middleware to set x-openstack-request-id in http response header
[filter:request_id]
paste.filter_factory = oslo_middleware.request_id:RequestId.factory
[filter:faultwrap]
paste.filter_factory = senlin.common.wsgi:filter_factory
senlin.filter_factory = senlin.api.openstack:faultwrap_filter
[filter:context]
paste.filter_factory = senlin.common.wsgi:filter_factory
senlin.filter_factory = senlin.api.openstack:contextmiddleware_filter
[filter:ssl]
paste.filter_factory = oslo_middleware.ssl:SSLMiddleware.factory
[filter:versionnegotiation]
paste.filter_factory = senlin.common.wsgi:filter_factory
senlin.filter_factory = senlin.api.openstack:version_negotiation_filter
[filter:trust]
paste.filter_factory = senlin.common.wsgi:filter_factory
senlin.filter_factory = senlin.api.openstack:trustmiddleware_filter
[filter:webhook]
paste.filter_factory = senlin.common.wsgi:filter_factory
senlin.filter_factory = senlin.api.openstack:webhookmiddleware_filter
# Auth middleware that validates token against keystone
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
  • 加载应用程序:

应用程序在WSGI中是这样规定的:

  • 1.需要时一个可调用的对象

1.可以是函数
2.可以是实例,它的类实现了call方法
3.可以是类,用这个类生产实例的过程就相当于调用这个类

  • 2.接受两个参数
  • 3.可调用对象要返回一个可迭代的值
    代码示例如下:
HELLO_WORLD = b"Hello world!\n"
# callable function
def application(environ, start_response):
    return [HELLO_WORLD]
# callable class
class Application:
    def __init__(self, environ, start_response):
        pass
    def __iter__(self):
        yield HELLO_WORLD
# callable object
class ApplicationObj:
    def __call__(self, environ, start_response):
        return [HELLO_WORLD]

senlin中用Router类封装了程序:

senlin/common/wsgi.py
app = match['controller']
return app
senlin/api/openstack/v1/__init__.py中API继承了Router类
最终通过api-paste.ini中senlin.app_factory = senlin.api.openstack.v1:API配置项被加载
例如:
uri: GET http://host:port/profile-types/remove
func: senlin.api.openstack.v1.profile_types:ProfileTypeController:get(req, 'remove')
  • 启动多个服务线程:

signal.signal(signal.SIGTERM, self.kill_children)
signal.signal(signal.SIGINT, self.kill_children)
signal.signal(signal.SIGHUP, self.hup)
while len(self.children) < self.conf.workers:
            self.run_child()

到这里整个WSGI服务就启动成功了,API中的mapper中包含了多个接口的实现。

三、数据结构介绍:

熟悉WSGI架构中一些常见的数据结构,可以加深我们对整个流程的了解,下面就介绍几个相关数据结构,以及它们包含的属性。

  • 1.environ变量

environ变量需要包含CGI变量,它们在The Common Gateway Interface Specification中有定义,environ中包含下列变量:

变量名 变量含义
REQUEST_METHOD http请求方法,例如GETPOST
SCRIPT_NAME URL起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,则可以为空字符串
PATH_INFO URL除了起始部分后的剩余部分,用于找到相应的应用程序对象
QUERY_STRING URL中?后面的部分
CONTENT_TYPE http请求中Content-Type部分
CONTENT_LENGTH http请求中Content-Length部分
SERVER_NAME/SERVER_PORT SCRIPT_NAMEPATH_INFO共同构成完整的URL,如果HTTP_HOST存在,则HTTP_HOST优先级高于SERVER_NAME
SERVER_PROTOCOL 客户端使用的协议,例如HTTP/1.0HTTP/1.1,它决定了如何处理http请求的头部。
HTTP_Variables 一系列的变量名,都是以HTTP开头,对应客户端支持的http请求的头部信息

示例:

REQUEST_METHOD = 'GET'
SCRIPT_NAME = ''
PATH_INFO = '/xyz'
QUERY_STRING = 'abc'
CONTENT_TYPE = 'text/plain'
CONTENT_LENGTH = ''
SERVER_NAME = 'minix-ubuntu-desktop'
SERVER_PORT = '8000'
SERVER_PROTOCOL = 'HTTP/1.1'

HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
HTTP_CONNECTION = 'keep-alive'
HTTP_HOST = 'localhost:8000'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'
  • wsgi变量

服务器程序可以包含某些操作系统相关的环境变量,但是非必须,但是下列wsgi相关变量必须要包含:

变量名 变量含义
wsgi.version WSGI版本,值的形式为(1,0),表示1.0版本
wsgi.url_scheme url模式,https还是http
wsgi.input 输入流,HTTP请求的body部分可以从中读取
wsgi.errors 输出流,如出现错误可以从中读取
wsgi.multithread 如果应用程序对象可以被同一进程中的另一线程同时调用,则为True
wsgi.multiprocess 如果应用程序对象可以同时被另一个进程调用,则为True
wsgi.run_once 如果服务器希望应用程序对象在包含它的进程中只被调用一次,则为True

示例:

wsgi.errors = <open file '<stderr>', mode 'w' at 0xb735f0d0>
wsgi.file_wrapper = <class wsgiref.util.FileWrapper at 0xb70525fc>
wsgi.input = <socket._fileobject object at 0xb7050e6c>
wsgi.multiprocess = False
wsgi.multithread = True
wsgi.run_once = False
wsgi.url_scheme = 'http'
wsgi.version = (1, 0)

遗留问题:

  • senlin中加载配置文件时,pipeline的处理在哪里?
  • url请求的调用流程还需要再梳理一下。

参考资料:

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

推荐阅读更多精彩内容