中间件和元数据

使用中间件

Python WSGI 中间件(或简称“中间件”)可用于“包装”Python WSGI 应用程序(即 Web 应用或 REST/HTTP API)的请求和响应,例如 Swift 的 WSGI 服务器(代理服务器、账户服务器、容器服务器、对象服务器)。Swift 使用中间件为 Swift WSGI 服务器添加(有时是可选的)行为。

可以通过修改其 paste 配置文件将中间件添加到 Swift WSGI 服务器。大部分 Swift 中间件应用于 代理服务器

给定以下基本配置

[DEFAULT]
log_level = DEBUG
user = <your-user-name>

[pipeline:main]
pipeline = proxy-server

[app:proxy-server]
use = egg:swift#proxy

您可以添加 健康检查 中间件,方法是为该过滤器添加一个部分并将其添加到管道中

[DEFAULT]
log_level = DEBUG
user = <your-user-name>

[pipeline:main]
pipeline = healthcheck proxy-server

[filter:healthcheck]
use = egg:swift#healthcheck

[app:proxy-server]
use = egg:swift#proxy

有些中间件是必需的,如果它们尚未存在,核心 Swift 代码会自动将其插入到您的管道中(例如,代理服务器将插入 捕获错误守门人 到管道的开头)。您可以使用 可发现性 接口查看给定 Swift 端点(包括中间件)上可用的功能。

创建您自己的中间件

查看示例是了解如何编写中间件的最佳方法。

Swift 中的许多可选功能都实现为 中间件,并提供在 swift.common.middleware 中,但 Swift 中间件也可以打包并作为独立项目分发。一些示例列在 关联项目 页面上。

下面提供了一个人为的中间件示例,它通过检查自定义 HTTP 标头(例如 X-Webhook)来修改请求行为,并使用 系统元数据 (Sysmeta) 将数据持久化到后端存储,以及像 get_container_info() 缓存/查询和 wsgify() 装饰器等常见模式

from swift.common.http import is_success
from swift.common.swob import wsgify
from swift.common.utils import split_path, get_logger
from swift.common.request_helpers import get_sys_meta_prefix
from swift.proxy.controllers.base import get_container_info
from eventlet import Timeout
from eventlet.green.urllib import urllib_request

# x-container-sysmeta-webhook
SYSMETA_WEBHOOK = get_sys_meta_prefix('container') + 'webhook'


class WebhookMiddleware(object):
    def __init__(self, app, conf):
        self.app = app
        self.logger = get_logger(conf, log_route='webhook')

    @wsgify
    def __call__(self, req):
        obj = None
        try:
            (version, account, container, obj) = \
                split_path(req.path_info, 4, 4, True)
        except ValueError:
            # not an object request
            pass
        if 'x-webhook' in req.headers:
            # translate user's request header to sysmeta
            req.headers[SYSMETA_WEBHOOK] = \
                req.headers['x-webhook']
        if 'x-remove-webhook' in req.headers:
            # empty value will tombstone sysmeta
            req.headers[SYSMETA_WEBHOOK] = ''
        # account and object storage will ignore x-container-sysmeta-*
        resp = req.get_response(self.app)
        if obj and is_success(resp.status_int) and req.method == 'PUT':
            container_info = get_container_info(req.environ, self.app)
            # container_info may have our new sysmeta key
            webhook = container_info['sysmeta'].get('webhook')
            if webhook:
                # create a POST request with obj name as body
                webhook_req = urllib_request.Request(webhook, data=obj)
                with Timeout(20):
                    try:
                        urllib_request.urlopen(webhook_req).read()
                    except (Exception, Timeout):
                        self.logger.exception(
                            'failed POST to webhook %s' % webhook)
                    else:
                        self.logger.info(
                            'successfully called webhook %s' % webhook)
        if 'x-container-sysmeta-webhook' in resp.headers:
            # translate sysmeta from the backend resp to
            # user-visible client resp header
            resp.headers['x-webhook'] = resp.headers[SYSMETA_WEBHOOK]
        return resp


def webhook_factory(global_conf, **local_conf):
    conf = global_conf.copy()
    conf.update(local_conf)

    def webhook_filter(app):
        return WebhookMiddleware(app, conf)
    return webhook_filter

实际上,此中间件将在所有成功的对象上传上调用容器中存储的 URL 作为 X-Webhook。

如果此示例位于 <swift-repo>/swift/common/middleware/webhook.py - 您可以通过创建一个新的过滤器部分并将其添加到管道来添加它

[DEFAULT]
log_level = DEBUG
user = <your-user-name>

[pipeline:main]
pipeline = healthcheck webhook proxy-server

[filter:webhook]
paste.filter_factory = swift.common.middleware.webhook:webhook_factory

[filter:healthcheck]
use = egg:swift#healthcheck

[app:proxy-server]
use = egg:swift#proxy

大多数 Python 包通过入口点公开中间件。有关 use 选项的语法的更多信息,请参阅 PasteDeploy 文档。Swift 包含的所有中间件都安装为支持 egg:swift 语法。

中间件可以通过使用 register_swift_info() 来利用 Swift 的 可发现性 支持来宣传其可用性和功能。

from swift.common.registry import register_swift_info
def webhook_factory(global_conf, **local_conf):
    register_swift_info('webhook')
    def webhook_filter(app):
        return WebhookMiddleware(app)
    return webhook_filter

如果中间件处理标头或查询参数中的敏感信息,可能需要在日志记录时对其进行删除,请使用 register_sensitive_header()register_sensitive_param() 函数。这应该在过滤器工厂中完成

from swift.common.registry import register_sensitive_header
def webhook_factory(global_conf, **local_conf):
    register_sensitive_header('webhook-api-key')
    def webhook_filter(app):
        return WebhookMiddleware(app)
    return webhook_filter

中间件可以通过在请求 WSGI 环境中设置 swift.proxy_logging_status 来覆盖 proxy_logging 中间件记录的状态整数。该值应为整数。该值将替换日志消息中的默认状态整数,除非 proxy_logging 中间件在处理请求时检测到客户端断开连接或异常,在这种情况下,swift.proxy_logging_status 将被 499 或 500 分别覆盖。

Swift 元数据

一般来说,元数据是与资源相关联但不是资源本身包含的数据的信息 - 通过 HTTP 标头设置和检索。(例如,Swift 对象的内容类型,在 HTTP 响应标头中返回)

Swift 中的所有用户资源(即帐户、容器、对象)都可以关联用户元数据。中间件还可以使用系统元数据将自定义元数据安全地持久化到帐户和容器。

用户元数据

用户元数据采用 X-<type>-Meta-<key>: <value> 的形式,其中 <type> 取决于资源类型(即帐户、容器、对象),<key><value> 由客户端设置。

用户元数据通常应保留供客户端或客户端应用程序使用。用户元数据的完美用例是 python-swiftclientX-Object-Meta-Mtime,它将其存储在它上传的对象上以实现其 --changed 选项,该选项仅上传自上次上传以来已更改的文件。

新的中间件应避免在用户元数据命名空间内存储元数据,以避免在引入新的元数据键时与现有的用户元数据发生潜在冲突。一个借用用户元数据命名空间的遗留中间件示例是 TempURL。一个使用自定义非用户元数据的中间件示例是 静态大对象

通过对容器或帐户资源进行的 PUT 或 POST 请求存储的用户元数据,直到通过随后的 PUT 或 POST 请求删除它,该请求包含没有值的标头 X-<type>-Meta-<key> 或标头 X-Remove-<type>-Meta-<key>: <ignored-value>。在后一种情况下,<ignored-value> 不会被存储。帐户或容器被删除时,存储在帐户或容器资源中的所有用户元数据都会被删除。

存储在对象资源中的用户元数据具有不同的语义;对象用户元数据在对同一对象进行的任何后续 PUT 或 POST 请求之前持续存在,此时所有存储在该对象上的用户元数据都会被批量删除并替换为 PUT 或 POST 请求中包含的任何用户元数据。因此,不可能在不更改某些项目的情况下更新存储在对象上的用户元数据项的子集。

系统元数据 (Sysmeta)

系统元数据采用 X-<type>-Sysmeta-<key>: <value> 的形式,其中 <type> 取决于资源类型(即帐户、容器、对象),<key><value> 由在 Swift WSGI 服务器中运行的受信任代码设置。

客户端请求中所有形式为 X-<type>-Sysmeta-<key> 的标头将在被任何中间件处理之前从请求中删除。后端系统响应中所有形式为 X-<type>-Sysmeta-<key> 的标头将在所有中间件处理响应后删除,但在将响应发送到客户端之前删除。有关更多信息,请参阅 守门人 中间件。

系统元数据提供了一种以安全可靠的方式将潜在的私有自定义元数据与关联的 Swift 资源存储,而无需实际通过核心 Swift 服务器传递自定义元数据。传入过滤确保命名空间不能被客户端请求直接修改,传出过滤确保删除使用特定系统元数据键的中间件使其无害。新的中间件应利用系统元数据。

可以通过包含带有 PUT 或 POST 请求的标头来设置帐户和容器上的系统元数据。如果标头名称与现有的系统元数据项的名称匹配,现有项的值将被更新。否则,现有项将被保留。具有空值的系统元数据标头将导致具有相同名称的任何现有项被删除。

只能使用 PUT 请求设置对象上的系统元数据。所有现有的系统元数据项都将被删除并替换为包含在 PUT 请求中的任何系统元数据标头。系统元数据不会被 POST 请求更新或删除:以与更新对象用户元数据无法更新单个项目相同的方式更新单个系统元数据项目尚未支持。在中间件需要在 POST 请求中存储自己的元数据的情况下,可以使用对象瞬态 Sysmeta。

对象元数据

除了上面描述的用户元数据和系统元数据之外,对象还有其他元数据。

不可变元数据

对象有几个不可变的元数据项。与系统元数据一样,它们只能使用 PUT 请求设置。但是,它们不遵循通用的 X-Object-Sysmeta-<key> 命名方案,并且不会自动从客户端响应中删除。

对象不可变元数据包括

X-Timestamp
Content-Length
Etag

X-TimestampContent-Length 元数据必须包含在发送到对象服务器的 PUT 请求中。Etag 元数据由对象服务器在处理 PUT 请求时生成,但与发送到 PUT 请求的任何 Etag 标头进行检查。

对象不可变元数据,以及 Content-Type,是对象服务器存储并返回在对象列表中中的唯一对象元数据。

内容类型

对象 Content-Type 元数据与不可变元数据、系统元数据不同。

Content-Type 必须包含在发送到对象服务器的 PUT 请求中。与不可变元数据或系统元数据不同,Content-Type 是可变的,并且可以包含在发送到对象服务器的 POST 请求中。但是,与对象用户元数据不同,如果 POST 请求不包含新的 Content-Type 元数据,现有的 Content-Type 元数据将持续存在。这是因为对象必须具有 Content-Type 元数据,该元数据也由容器服务器存储并在对象列表中返回。

Content-Type 是既可变又在未在 POST 请求中指定时持续存在的唯一对象元数据项。

对象瞬态 Sysmeta

如果中间件需要在 POST 请求中存储对象元数据,可以使用形式为 X-Object-Transient-Sysmeta-<key>: <value> 的标头。

客户端请求中所有形式为 X-Object-Transient-Sysmeta-<key> 的标头将在被任何中间件处理之前从请求中删除。后端系统响应中所有形式为 X-Object-Transient-Sysmeta-<key> 的标头将在所有中间件处理响应后删除,但在将响应发送到客户端之前删除。有关更多信息,请参阅 守门人 中间件。

对一个对象进行瞬态系统元数据 (transient-sysmeta) 更新,其语义与对该对象进行用户元数据更新相同(参见 用户元数据),即,每当对一个对象进行 PUT 或 POST 请求时,所有现有的瞬态系统元数据都会被批量删除,并替换为 PUT 或 POST 请求中包含的瞬态系统元数据。因此,由中间件设置的瞬态系统元数据容易被后续客户端生成的 POST 请求删除,除非中间件小心地在每个 POST 请求中包含其瞬态系统元数据。同样,客户端设置的用户元数据容易被后续中间件生成的 POST 请求删除,因此中间件应避免生成独立于任何客户端请求的 POST 请求。

瞬态系统元数据故意使用与用户元数据不同的头部前缀,以便中间件可以避免与用户元数据键的潜在冲突。

瞬态系统元数据故意使用与系统元数据不同的头部前缀,以强调该数据仅在后续 POST 请求之前持久存在。