2021SC@SDUSC
WSGI与Nova
WSGI简介
全称Python Web Server Gateway Interface,指定了web服务器和Python web应用或web框架之间的标准接口,以提高web应用在一系列web服务器间的移植性。
web处理请求流程:
- 用户操作操作浏览器发送请求;
- 请求转发至对应的web服务器
- web服务器将请求转交给web应用程序,web应用程序处理请求
- web应用将请求结果返回给web服务器,由web服务器返回用户响应结果
- 浏览器收到响应,向用户展示
WSGI则是一种协议,web服务器在将请求转交给web应用程序之前,需要先将http报文转换为WSGI规定的格式。
WSGI规定,Web程序必须有一个可调用对象,且该可调用对象接收两个参数,返回一个可迭代对象:
- environ:字典,包含请求的所有信息
- start_response:在可调用对象中调用的函数,用来发起响应,参数包括状态码,headers等
WSGI将 web 组件分为三类: web服务器,web中间件,web应用程序, wsgi基本处理模式为 : WSGI Server -> (WSGI Middleware)* -> WSGI Application 。
WSGI Server/gateway
wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。文字很难解释清楚wsgi server到底是什么东西,以及做些什么事情,最直观的方式还是看wsgi server的实现代码。以python自带的wsgiref为例,wsgiref是按照wsgi规范实现的一个简单wsgi server。wsgi server 基本工作流程
- 服务器创建socket,监听端口,等待客户端连接。
- 当有请求来时,服务器解析客户端信息放到环境变量environ中,并调用绑定的handler来处理请求。
- handler解析这个http请求,将请求信息例如method,path等放到environ中。
- wsgi handler再将一些服务器端信息也放到environ中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中。
- wsgi handler 调用注册的wsgi app,并将environ和回调函数传给wsgi app
- wsgi app 将reponse header/status/body 回传给wsgi handler
- 最终handler还是通过socket将response信息塞回给客户端。
WSGI Application
wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。这个对象接收两个参数,通常为environ,start_response。environ就像前面介绍的,可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,wsgi application通过调用start_response,将response headers/status 返回给wsgi server。此外这个wsgi app会return 一个iterator对象 ,这个iterator就是response body。
WSGI & Nova
尽管计算和元数据API可以使用提供基于事件的HTTP服务器的独立脚本来运行,但一般认为使用支持WSGI的通用HTTP服务器(如Apache或nginx)来运行它们更具有性能和灵活性。
nova项目提供了两个自动生成的入口点来支持此功能:nova-api-wsgi和nova-metadata-wsgi。它们读取nova.conf和api-paste.ini,并生成大多数WSGI服务器所需的模块级应用。如果nova是用pip安装的,这两个脚本将被安装到任何环境下的预期bin目录。
Nova-api(nova/cmd/api.py) 服务启动时,初始化 nova/wsgi.py 中的类 Server,建立了 socket 监听 IP 和端口,再由 eventlet.spawn 和 eventlet.wsgi.server 创建 WSGI server:
class Server(object): """Server class to manage a WSGI server, serving a WSGI application.""" def __init__(self, name, app, host='0.0.0.0', port=0, pool_size=None, protocol=eventlet.wsgi.HttpProtocol, backlog=128, use_ssl=False, max_url_len=None): """Initialize, but do not start, a WSGI server.""" self.name = name self.app = app self._server = None self._protocol = protocol self._pool = eventlet.GreenPool(pool_size or self.default_pool_size) self._logger = logging.getLogger("nova.%s.wsgi.server" % self.name) self._wsgi_logger = logging.WritableLogger(self._logger) if backlog < 1: raise exception.InvalidInput( reason='The backlog must be more than 1') bind_addr = (host, port) # 建立 socket,监听 IP 和端口 self._socket = eventlet.listen(bind_addr, family, backlog=backlog) def start(self): """Start serving a WSGI application. :returns: None """ # 构建所需参数 wsgi_kwargs = { 'func': eventlet.wsgi.server, 'sock': self._socket, 'site': self.app, 'protocol': self._protocol, 'custom_pool': self._pool, 'log': self._wsgi_logger, 'log_format': CONF.wsgi_log_format } if self._max_url_len: wsgi_kwargs['url_length_limit'] = self._max_url_len # 由 eventlet.sawn 启动 server self._server = eventlet.spawn(**wsgi_kwargs)
Application 的加载由 nova/wsgi.py 的类 Loader 完成,Loader 的 load_app 方法调用了 paste.deploy.loadapp 加载了 WSGI 的配置文件 /etc/nova/api-paste.ini:
class Loader(object): """Used to load WSGI applications from paste configurations.""" def __init__(self, config_path=None): # 获取 WSGI 配置文件的路径 self.config_path = config_path or CONF.api_paste_config def load_app(self, name): # paste.deploy 读取配置文件并加载该配置 return paste.deploy.loadapp("config:%s" % self.config_path, name=name)
配置文件 api-paste.ini 如下所示,我们通常使用 v2 API,即 composite:openstack_compute_api_v2,也通常使用 keystone 做认证,即 keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2,从 fautlwrap 到 ratelimit 均是 middleware,我们也可根据需求增加某些 middleware。
[composite:osapi_compute] use = call:nova.api.openstack.urlmap:urlmap_factory /v2: openstack_compute_api_v2 /v3: openstack_compute_api_v3 [composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2 keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2 [composite:openstack_compute_api_v3] ... [filter:keystonecontext] paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory [filter:authtoken] paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory [app:osapi_compute_app_v2] paste.app_factory = nova.api.openstack.compute:APIRouter.factory [app:osapi_compute_app_v3] paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory