一致的查询参数验证¶
https://blueprints.launchpad.net/nova/+spec/consistent-query-parameters-validation
目前查询参数在每个 API 方法中以不一致的方式进行验证。这使得 API 代码难以维护且容易出错。本规范旨在提出一种一致的机制来验证查询参数。
问题描述¶
API 层支持使用 json-schema 验证请求的输入体。存在支持模式微版本的机制。但是,没有集中支持来验证查询参数,导致处理不一致。
API 方法中不一致的查询参数验证。 相似的查询参数具有不同的验证方式。或者某些查询参数根本没有验证。例如,服务器列表 API 和 simple_tenant_usage API 之间接受不同的日期时间格式。服务器列表中的 changes-since 接受 ISO 8601 格式 的日期时间 [1]。simple_tenant_usage 中的 start/end 接受一些自定义格式 [2]。
在不深入研究代码的情况下,开发人员和用户无法知道 API 支持哪些查询参数。并且有些查询参数直接传递到 SQL 查询中。例如,服务器列表 API 中 sort_key 的值直接传递到 DB 层。[3]
DB 模式直接暴露给 REST API。当 DB 模式发生变化时,API 也会发生变化。与上述示例相同。 sort_key 的值直接传递到 DB 层,导致 DB 对象的内部属性 __wrapper__ 暴露给 REST API。
用例¶
这是一项关于 API 层代码重构的工作。旨在减轻维护 API 代码的负担。使用场景是 Nova 的开发人员
开发人员需要对查询参数进行一致的验证。
开发人员不希望将验证代码与其他 API 代码混合在一起。
开发人员需要一个中心位置来声明支持的查询参数。
最终,最终用户将获得以下好处
一致的查询参数验证。
稳定的 API,API 不再会因底层 DB 模式的变化而改变。
提议的变更¶
本规范建议使用 JSON-schema 将查询参数的验证从 API 代码中提取出来。
JSON-schema 也是开发人员熟悉的工具,因此基于此构建新机制是好的。
为了使用 JSON-schema 验证查询参数,需要将查询参数转换为扁平化的 JSON 数据。例如,有三个查询参数
name 接受一个正则表达式字符串。它只能指定一次。
sort_key 可以接受一个字符串,有效的字符串是 created_at 和 updated_at。它可以被多次指定。
deleted 是布尔值,只能指定一次。
请求如下
The request:
GET http://openstack.example.com/v2.1/servers?name=abc&sort_key=created_at&sort_key=updated_at&deleted=True
查询参数转换为扁平化的 JSON 数据
{
'name': ['^abc'],
'sort_key': ['created_at', 'updated_at']
'deleted': ['True']
}
此扁平化的 JSON 数据可以直接通过 JSON-schema 进行验证,相应的 JSON-schema 如下
{
'type': 'object',
'properties': {
'name': {
'type': 'array',
'items': {'type': 'string', 'format': 'regex'},
'maxItems': 1
}
'sort_key': {
'type': 'array',
'items': {'type': 'string',
'enum': ['created_at', 'updated_at']}
},
'deleted': {
'type': 'array',
'items': parameter_types.boolean,
'maxItems': 1
}
'additionalProperties': False,
}
为了减少复制/粘贴,引入了两个宏函数
{
'type': 'object'
'properties': {
'name': single_param({'type': 'string', 'format': 'regex'})
'sort_key': multi_params({'type': 'string', 'enum': ['created_at', 'updated_at']}),
'deleted': single_param(parameter_types.boolean)
}
'additionalProperties': False
}
模式将通过新的装饰器附加到每个 API
@validation.query_params_schema(schema, min_version, max_version)
可以在装饰器中指定给定 json-schema 支持的微版本范围。装饰器的用法与 body jsons-schema 装饰器相同。
如果存在匹配请求版本的模式,则验证失败时将返回 400 错误。
additionalProperties 的行为如下
当 additionalProperties 的值为 True 时,表示允许额外的查询参数。但这些额外的查询参数将被删除。
当 additionalProperties 的值为 False 时,表示不允许额外的查询参数。
additionalProperties 的值将为 True,直到我们决定在未来限制参数,并使用新的微版本进行更改。现在我们仍然需要在查询字符串中启用随机输入。但是,额外的参数将被删除以保护系统。此外,为了匹配当前行为,我们需要为所有参数启用多个值(使用宏函数 ‘multi_params’ 提取多个值的模式)。对于遗留 v2 API 模式,additionalProperties 的值也应为 True,这使得遗留 v2 API 模式也受到保护。
当前 API 仅在 API 用户在请求中指定了多个值时,才接受单个值参数的一个值。只有接受的值将被验证。新的验证机制支持多个值参数。区别在于,新的机制将验证所有值,即使只有一个被接受。但考虑到这种情况很少见,是可以接受的。
备选方案¶
如果我们保持一切不变,查询参数验证的代码将难以维护。这会导致隐藏查询参数中的错误。
数据模型影响¶
无
REST API 影响¶
本提案将使用 keypairs API 作为示例。对于使用 json-schema 验证其他 API 的查询参数,将在其他提案中说明。
在 keypairs API 中,新的查询参数是在 Microversion 2.10 和 2.35 中添加的。例如,将为 index 方法添加以下装饰器
schema_v2_1 = {
'type': 'object',
'properties': { }
'additionalProperties': True
}
schema_v2_10 = copy.deepcopy(schema_v2_1)
schema_v2_10['properties'] = {'user_id': multi_params({'type': 'string'}}
schema_v2_35 = copy.deepcopy(schema_v2_10)
schema_v2_35['properties']['limit'] = multi_params(
{'type': 'string', 'format': 'integer'})
schema_v2_35['properties']['marker'] = multi_params({'type': 'string'})
@validation.query_params_schema(schema_v2_35, '2.35')
@validation.query_params_schema(schema_v2_10, '2.10', '2.34')
@validation.query_params_schema(schema_v2_1, '2.0', '2.9)
def index(req):
....
Keypairs API 的行为如下
对于 GET /keypairs?user_id=1&user_id=2
过去:接受,但我们忽略 user_id=2
现在:接受,但我们忽略 user_id=2
未来:在添加新的微版本后返回 400
对于 GET /keypairs?limit=abc
拒绝,该值为整数
对于 GET /keypairs?limit=abc&limit=1
过去:接受,忽略 limit=abc
现在:拒绝,limit 的所有值都应该是整数
未来:拒绝,只能指定单个值。
安全影响¶
无
通知影响¶
无
其他最终用户影响¶
无
性能影响¶
无
其他部署者影响¶
无
开发人员影响¶
本提案提高了 API 代码的可维护性。
实现¶
负责人¶
- 主要负责人
Alex Xu <hejie.xu@intel.com>
- 其他贡献者
ZhenYu Zheng <zhengzhenyu@huawei.com>
工作项¶
引入新的装饰器以启用查询参数的 json-schema
在 keypairs API 中使用 json-schema 进行查询参数验证。
依赖项¶
无
测试¶
需要单元测试和功能测试来确保新机制按预期工作。在使用新机制代替现有的查询参数处理时,现有的单元测试和功能测试仍然可以通过测试。
文档影响¶
开发人员参考需要描述如何使用新机制。
参考资料¶
历史¶
发布名称 |
描述 |
|---|---|
Ocata |
引入 |