版本控制是一种礼貌地淘汰已部署客户端的方式。
— Roy Fielding。
API 版本控制允许你在不同客户端之间改变行为。REST framework 提供了多种不同的版本控制方案。
版本控制由传入的客户端请求确定,可以基于请求 URL 或基于请求头。
对于版本控制,有多种有效的实现方式。对于非版本化系统,如果是在设计长期系统且有多个不受控的客户端时,也可能适用。
在 REST framework 中实现版本控制
当启用了 API 版本控制时,request.version
属性将包含一个字符串,对应于传入客户端请求中请求的版本。
默认情况下,版本控制未启用,request.version
始终返回 None
。
根据版本改变行为
如何根据版本改变 API 行为由你决定,但一个常见的例子是切换到更新版本的不同序列化风格。例如:
def get_serializer_class(self):
if self.request.version == 'v1':
return AccountSerializerVersion1
return AccountSerializer
为版本化 API 反向解析 URL
REST framework 包含的 reverse
函数与版本控制方案相关联。你需要确保将当前 request
作为关键字参数包含进去,如下所示。
from rest_framework.reverse import reverse
reverse('bookings-list', request=request)
上述函数将应用任何适用于请求版本的 URL 转换。例如:
- 如果使用
NamespaceVersioning
,并且 API 版本为 'v1',那么使用的 URL 查找将是'v1:bookings-list'
,可能解析为http://example.org/v1/bookings/
这样的 URL。 - 如果使用
QueryParameterVersioning
,并且 API 版本为1.0
,那么返回的 URL 可能是类似http://example.org/bookings/?version=1.0
。
版本化 API 和超链接序列化器
当使用超链接序列化风格与基于 URL 的版本控制方案时,确保将请求作为上下文传递给序列化器。
def get(self, request):
queryset = Booking.objects.all()
serializer = BookingsSerializer(queryset, many=True, context={'request': request})
return Response({'all_bookings': serializer.data})
这样做将允许返回的任何 URL 包含适当的版本控制信息。
配置版本控制方案
版本控制方案由 DEFAULT_VERSIONING_CLASS
设置键定义。
REST_FRAMEWORK = {
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.NamespaceVersioning'
}
除非明确设置,否则 DEFAULT_VERSIONING_CLASS
的值将为 None
。在这种情况下,request.version
属性将始终返回 None
。
你还可以在单个视图上设置版本控制方案。通常你不需要这样做,因为使用全局统一的版本控制方案更有意义。如果你确实需要这样做,可以使用 versioning_class
属性。
class ProfileList(APIView):
versioning_class = versioning.QueryParameterVersioning
其他版本控制设置
以下设置键也用于控制版本控制:
DEFAULT_VERSION
:当没有版本控制信息时,用于request.version
的值。默认为None
。ALLOWED_VERSIONS
:如果设置,此值将限制版本控制方案可以返回的版本集,并且如果提供的版本不在该集合中将引发错误。注意,默认版本设置的值始终被视为允许版本集的一部分(除非它是None
)。默认为None
。VERSION_PARAM
:用于任何版本控制参数的字符串,例如在媒体类型或 URL 查询参数中。默认为'version'
。
你还可以通过定义自己的版本控制方案并在视图或视图集上设置 default_version
、allowed_versions
和 version_param
类变量来分别设置版本控制类以及这三个值。例如,如果你想要使用 URLPathVersioning
:
from rest_framework.versioning import URLPathVersioning
from rest_framework.views import APIView
class ExampleVersioning(URLPathVersioning):
default_version = ...
allowed_versions = ...
version_param = ...
class ExampleView(APIView):
versioning_class = ExampleVersioning
API 参考
该方案要求客户端在 Accept
请求头的媒体类型中指定版本。版本作为媒体类型参数包含在内,补充主要媒体类型。
以下是一个使用接受头版本控制风格的 HTTP 请求示例。
GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/json; version=1.0
在上述示例请求中,request.version
属性将返回字符串 '1.0'
。
严格来说,JSON 媒体类型未指定包含额外参数。如果你正在构建一个良好规范的公共 API,你可能会考虑使用供应商媒体类型。为此,配置渲染器以使用基于 JSON 的渲染器和自定义媒体类型:
class BookingsAPIRenderer(JSONRenderer):
media_type = 'application/vnd.megacorp.bookings+json'
现在你的客户端请求将如下所示:
GET /bookings/ HTTP/1.1
Host: example.com
Accept: application/vnd.megacorp.bookings+json; version=1.0
URLPathVersioning
该方案要求客户端在 URL 路径中指定版本。
GET /v1/bookings/ HTTP/1.1
Host: example.com
Accept: application/json
你的 URL 配置必须包含一个模式,以 version
关键字参数匹配版本,以便版本控制方案可以获取此信息。
urlpatterns = [
re_path(
r'^(?P(v1|v2))/bookings/$',
bookings_list,
name='bookings-list'
),
re_path(
r'^(?P(v1|v2))/bookings/(?P[0-9]+)/$',
bookings_detail,
name='bookings-detail'
)
]
NamespaceVersioning
对于客户端,该方案与 URLPathVersioning
相同。唯一的区别在于它在 Django 应用中的配置方式,它使用 URL 命名空间而不是 URL 关键字参数。
GET /v1/something/ HTTP/1.1
Host: example.com
Accept: application/json
在此示例中,我们为一组视图提供了两个不同的 URL 前缀,每个前缀都在不同的命名空间下:
# bookings/urls.py
urlpatterns = [
re_path(r'^$', bookings_list, name='bookings-list'),
re_path(r'^(?P[0-9]+)/$', bookings_detail, name='bookings-detail')
]
# urls.py
urlpatterns = [
re_path(r'^v1/bookings/', include('bookings.urls', namespace='v1')),
re_path(r'^v2/bookings/', include('bookings.urls', namespace='v2'))
]
如果只需要简单的版本控制方案,URLPathVersioning
和 NamespaceVersioning
都是合理的选择。URLPathVersioning
可能更适合小型临时项目,而 NamespaceVersioning
对于大型项目可能更容易管理。
HostNameVersioning
主机名版本控制方案要求客户端在 URL 的主机名中指定请求的版本。
例如,以下是对 http://v1.example.com/bookings/
URL 的 HTTP 请求:
GET /bookings/ HTTP/1.1
Host: v1.example.com
Accept: application/json
默认情况下,此实现期望主机名匹配以下正则表达式:
^([a-zA-Z0-9]+)\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+$
注意第一个组被括号括起来,表示这是主机名中匹配的部分。
在调试模式下,HostNameVersioning
方案可能使用起来不太方便,因为你通常会访问像 127.0.0.1
这样的原始 IP 地址。在这种情况下,网上有许多关于如何使用自定义子域访问本地主机的教程可能会对你有所帮助。
基于主机名的版本控制在你需要根据版本将传入请求路由到不同服务器时特别有用,因为你可以为不同的 API 版本配置不同的 DNS 记录。
QueryParameterVersioning
该方案是一种简单的风格,将版本作为 URL 中的查询参数包含。例如:
GET /something/?version=0.1 HTTP/1.1
Host: example.com
Accept: application/json
自定义版本控制方案
要实现自定义版本控制方案,可以继承 BaseVersioning
并覆盖 .determine_version
方法。
示例
以下示例使用自定义的 X-API-Version
头来确定请求的版本。
class XAPIVersionScheme(versioning.BaseVersioning):
def determine_version(self, request, *args, **kwargs):
return request.META.get('HTTP_X_API_VERSION', None)
如果你的版本控制方案基于请求 URL,你还需要改变如何确定版本化的 URL。为此,应该覆盖类上的 .reverse()
方法。可以参阅源代码获取示例。