Oslo系列之oslo.middleware

俗话说:善有善报,恶有恶报;话又说:人善被人欺,马善被人骑!古今论坛,后人总结生存言谈:拥有善良之心待人,不向恶势力低头屈身。愿你吸收生存之道,日子过得美满逍遥!

oslo.middleware库可以添加在WSGI pipeline用来拦截request/response请求。其基类可以为WSGI增强功能,如添加、删除、修改HTTP头部信息,支持限制大小和连接数等。本文主要分析Oslo组件如何使用oslo.middleware来实现这些功能。

oslo.middleware中的主要pipeline

oslo.middleware中定义了多个WSGI的pipeline,如cors、request_id等。所以,本文首先总结了oslo.middleware中常用的pipeline的实现、作用以及使用场景。在oslo.middleware中,所有的pipeline的实现类都继承自一个父类ConfigurableMiddleware,其是一个基本的WSGI middleware装饰器。该类在实例化时会绑定一个WSGI应用对象APP,则该ConfigurableMiddleware对象在实际使用中便会对绑定的WSGI APP的request/response进行相应的包装。在ConfigurableMiddleware类中,分别定义了process_request(request)和process_response(response, request)方法分别对request和response进行处理。最后,该类还为paste.deploy实现了factory方法,使其可以通过读取WSGI配置文件调用相关类的装饰器。了解了ConfigurableMiddleware类的实现之后,下面便可以根据ConfigurableMiddleware类的实现针对具体的pipeline的实现进行详细的解读。

CORS

CORS指的是跨域资源共享,该机制允许Web服务器进行跨域访问控制,从而使得跨域数据传输得以安全进行。浏览器支持在API容器中使用CORS,以降低跨域HTTP请求所带来的风险。CORS在原有HTTP协议基础上新增了一组HTTP首部字段,用以表示服务器声明哪些源站可以访问哪些资源,这些字段包括Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age等。关于CORS的具体介绍,本文不作详细解释,感兴趣的朋友可以参考以下的博文https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS。

而oslo.middleware中的CORS即是为WSGI请求或响应添加CORS字段和处理的pipeline,其覆写了父类的process_request(request)和process_response(response, request)方法来实现其功能。

1
2
3
4
5
6
7
8
9
10
simple_headers = [
'Accept',
'Accept-Language',
'Content-Type',
'Cache-Control',
'Content-Language',
'Expires',
'Last-Modified',
'Pragma'
]

一般地,浏览器发送HTTP请求时,每个请求和请求的响应都会包含一些基本的头部字段;这些字段包括:Accept、Accept-Language、Content-Type、Cache-Control、Content-Language、Expires、Lash-Modified、Pragma等。而oslo.middleware中CORS的功能则是通过获取请求或请求的响应,为该请求添加跨域资源共享的头部字段。CORS覆写了父类的process_response(response, request=None)方法为每个响应添加CORS头部字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def process_response(self, response, request=None):
'''Check for CORS headers, and decorate if necessary.
Perform two checks. First, if an OPTIONS request was issued, let the
application handle it, and (if necessary) decorate the response with
preflight headers. In this case, if a 404 is thrown by the underlying
application (i.e. if the underlying application does not handle
OPTIONS requests, the response code is overridden.
In the case of all other requests, regular request headers are applied.
'''

# Sanity precheck: If we detect CORS headers provided by something in
# in the middleware chain, assume that it knows better.
if 'Access-Control-Allow-Origin' in response.headers:
return response

# Doublecheck for an OPTIONS request.
if request.method == 'OPTIONS':
return self._apply_cors_preflight_headers(request=request,
response=response)

# Apply regular CORS headers.
self._apply_cors_request_headers(request=request, response=response)

# Finally, return the response.
return response

首先,其会判断该响应的头部字段中是否含有Access-Control-Allow-Origin字段,如果有则直接返回该响应;否则,其会判断发送的请求的方法是否为OPTION,如果为OPTION则表示该请求为跨域资源共享预检请求,程序将调用CORS对象的_apply_cors_preflight_headers(request, response)方法为该请求的响应添加Vary、Access-Control-Allow-Origin、Access-Control-Allow-Credentials、Access-Control-Max-Age、Access-Control-Allow-Methods、Access-Control-Allow-Headers等字段;否则表示该请求为跨域资源共享基本请求,程序将调用CORS对象的_apply_cors_request_headers(request, response)方法为该请求的响应添加Vary、Access-Control-Allow-Origin、Access-Control-Allow-Credentials、Access-Control-Allow-Headers等字段。

RequestId

request_id在OpenStack的WSGI调用中具有非常重要的作用,它唯一标识了OpenStack API的获取的每一个请求,并将其记录到日志中,便于程序员定位问题等。RequestId类便是为每个发送到OpenStack API的请求添加request_id。该类覆写了父类的call方法,首先会判断该请求是否包含一个全局的request_id,即请求头部是否包含X-Openstack-Request-Id字段,如果包含则将该全局的request_id添加到该环境变量openstack.global_request_id中;接着该类会通过oslo.context中的generate_request_id()方法生成一个request_id,将其添加到环境变量openstack.request_id中,并将其追加到对应响应的头部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@webob.dec.wsgify
def __call__(self, req):
self.set_global_req_id(req)

req_id = context.generate_request_id()
req.environ[ENV_REQUEST_ID] = req_id
response = req.get_response(self.application)

return_headers = [HTTP_RESP_HEADER_REQUEST_ID]
return_headers.extend(self.compat_headers)

for header in return_headers:
if header not in response.headers:
response.headers.add(header, req_id)
return response

CorrelationId

correlation_id为OpenStack API接收到的每个请求设置一个关联的ID,程序员可以根据该ID查询与相关请求的操作。CorrelationId通过覆写process_request(request)方法,为OpenStack API接收到的每个请求添加X_CORRELATION_ID存储其correlation_id。

HTTPProxyToWSGI

HTTPProxyToWSGI为OpenStack API提供反向代理的功能,其通过一个远程HTTP反向代理服务器重载WSGI的环境变量。该类通过覆写process_request(request)方法为OpenStack API接收到的每一个请求添加了RFC7239中指定的HTTP反向代理所需的头部字段。在接收到请求时,首先判断OpenStack服务是否允许使用HTTP代理头部,如果不允许则直接返回。接着,获取请求环境变量中的HTTP_FORWARDED是否为空,如果不为空则通过RFC7239标准解析该字段,获取相应的代理参数并添加到请求wsgi的环境变量中;否则,直接获取请求环境变量中与HTTP代理相关的字段添加到请求wsgi的环境变量中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def process_request(self, req):
if not self._conf_get('enable_proxy_headers_parsing'):
return
fwd_hdr = req.environ.get("HTTP_FORWARDED")
if fwd_hdr:
proxies = self._parse_rfc7239_header(fwd_hdr)
# Let's use the value from the first proxy
if proxies:
proxy = proxies[0]

forwarded_proto = proxy.get("proto")
if forwarded_proto:
req.environ['wsgi.url_scheme'] = forwarded_proto

forwarded_host = proxy.get("host")
if forwarded_host:
req.environ['HTTP_HOST'] = forwarded_host

forwarded_for = proxy.get("for")
if forwarded_for:
req.environ['REMOTE_ADDR'] = forwarded_for

else:
# World before RFC7239
forwarded_proto = req.environ.get("HTTP_X_FORWARDED_PROTO")
if forwarded_proto:
req.environ['wsgi.url_scheme'] = forwarded_proto

forwarded_host = req.environ.get("HTTP_X_FORWARDED_HOST")
if forwarded_host:
req.environ['HTTP_HOST'] = forwarded_host

forwarded_for = req.environ.get("HTTP_X_FORWARDED_FOR")
if forwarded_for:
req.environ['REMOTE_ADDR'] = forwarded_for

v = req.environ.get("HTTP_X_FORWARDED_PREFIX")
if v:
req.environ['SCRIPT_NAME'] = v + req.environ['SCRIPT_NAME']

除了上述的pipeline外,oslo.middleware还定义了很多种pipeline,如限制请求体大小的RequestBodySizeLimiter、获取不到请求的响应抛出异常的CatchErrors、添加SSL认证的SSLMiddleware、基于API请求发送stats指令的StatsMiddleware等。而这些pipeline在实现上与上述pipeline的实现大同小异,因此这里便不在挨个展开介绍,感兴趣的朋友可以根据上述博客的内容举一反三,自行研究。下面,将详细介绍oslo.middleware在OpenStack其他组件的使用方法。

oslo.middleware的使用方式

因为oslo.middleware为WSGI的框架paste.deploy提供对应的pipeline,因此在OpenStack其他组件中的使用也非常简单。这里以Nova组件的WSGI为例,介绍oslo.middleware中各pipeline的使用。

Nova组件的WSGI配置文件api-paste.ini中,定义了多个app、pipeline以及composite,其中多个便直接使用了oslo.middleware中定义的pipeline,如2.1版本的计算服务API
openstack_compute_api_v21中便使用了cors、http_proxy_to_wsgi、sizelimit等多个oslo.middleware的pipeline。其配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler noauth2 osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21

[filter:request_id]
paste.filter_factory = oslo_middleware:RequestId.factory

[filter:sizelimit]
paste.filter_factory = oslo_middleware:RequestBodySizeLimiter.factory

[filter:cors]
paste.filter_factory = oslo_middleware.cors:filter_factory
oslo_config_project = nova

因此,在需要使用oslo.middleware中的pipeline时,OpenStack各组件只需要在相应服务的WSGI配置文件中添加一个对应的filter,并将paste.filter_factory指定为相应类的factory方法即可。而如果一个pipeline需要指定配置参数时,既可以直接在filter中指定,也可以在对应服务的配置文件中的oslo_middleware配置组下添加。而如果需要直接在代码中使用oslo.middleware的各pipeline的实现类,只需要将对应的模块导入即可。

原文链接:https://blog.csdn.net/Bill_Xiang_/article/details/78552271

-------------本文结束 感谢您的阅读-------------
作者Magiceses
有问题请 留言 或者私信我的 微博
满分是10分的话,这篇文章你给几分