【com】基于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_METHODhttp请求方法,例如GETPOST
SCRIPT_NAMEURL起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,则可以为空字符串
PATH_INFOURL除了起始部分后的剩余部分,用于找到相应的应用程序对象
QUERY_STRINGURL中?后面的部分
CONTENT_TYPEhttp请求中Content-Type部分
CONTENT_LENGTHhttp请求中Content-Length部分
SERVER_NAME/SERVER_PORTSCRIPT_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.versionWSGI版本,值的形式为(1,0),表示1.0版本
wsgi.url_schemeurl模式,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请求的调用流程还需要再梳理一下。

参考资料:

    原文作者:EldonZhao
    原文地址: https://www.jianshu.com/p/c58b3dcd8dfb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞