DRF之版本组件

前言

API版本控制是一个关键的实践,它有助于确保API的稳定性、可靠性和兼容性,同时为API的长期维护和演进提供支持。通过在请求中携带版本号,开发者可以更好地管理API的不同版本,并确保对客户端的影响最小化

1. GET参数传递

配置文件中的VERSION_PARAM决定哪个参数表示版本信息

参数携带的版本信息会被封装到request.version

settings.py中和版本组件有关的参数

REST_FRAMEWORK = {
    # 携带版本信息的请求参数名
    'VERSION_PARAM': 'version',
    # 未传入版本参数时的默认版本
    'DEFAULT_VERSION': "v1",
    # 版本范围列表
    'ALLOWED_VERSIONS': ["v1", "v2"],
    # 默认版本类
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.QueryParameterVersioning',
}

在这里插入图片描述
如图,URL参数中传递version=v1,则request.version存储了版本信息v1

2. 反向生成URL

与django中的反向生成URL不同,drf反向生成的URL可以携带相关版本信息

在这里插入图片描述
将路由的name传入,并且将request传入(版本信息在request中)即可反向生成URL

3. 源码解读

入口在APIView类的initial()方法

class BaseVersioning:
    default_version = api_settings.DEFAULT_VERSION
    allowed_versions = api_settings.ALLOWED_VERSIONS
    version_param = api_settings.VERSION_PARAM

    def determine_version(self, request, *args, **kwargs):
        msg = '{cls}.determine_version() must be implemented.'
        raise NotImplementedError(msg.format(
            cls=self.__class__.__name__
        ))

    def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
        return _reverse(viewname, args, kwargs, request, format, **extra)

    def is_allowed_version(self, version):
        # 列表为空 -> 所有版本均可
        if not self.allowed_versions:
            return True
        # version是默认值或者在allowed_versions列表范围内
        return ((version is not None and version == self.default_version) or
                (version in self.allowed_versions))


class QueryParameterVersioning(BaseVersioning):
    """
    GET /something/?version=0.1 HTTP/1.1
    Host: example.com
    Accept: application/json
    """
    invalid_version_message = _('Invalid version in query parameter.')

    def determine_version(self, request, *args, **kwargs):
        # 去请求参数中获取键为version_param的值即为版本,若不存在则读取settings.py中的默认值
        version = request.query_params.get(self.version_param, self.default_version)
        if not self.is_allowed_version(version):
            raise exceptions.NotFound(self.invalid_version_message)
        return version

    # 传入视图名称和request
    def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
        # 本质上就是调用django的reverse反向生成URL
        url = super().reverse(
            viewname, args, kwargs, request, format, **extra
        )
        # 将版本信息添加或替换进去
        if request.version is not None:
            return replace_query_param(url, self.version_param, request.version)
        return url


def replace_query_param(url, key, val):
    (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url))
    query_dict = parse.parse_qs(query, keep_blank_values=True)
    query_dict[force_str(key)] = [force_str(val)]
    query = parse.urlencode(sorted(query_dict.items()), doseq=True)
    return parse.urlunsplit((scheme, netloc, path, query, fragment))


class APIView(View):
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

    def determine_version(self, request, *args, **kwargs):
        if self.versioning_class is None:
            return (None, None)
        # 实例化类对象,即QueryParameterVersioning(),,欸有__init__方法
        scheme = self.versioning_class()
        # 返回版本信息与版本类对象
        return (scheme.determine_version(request, *args, **kwargs), scheme)

    def initial(self, request, *args, **kwargs):
        # 将版本信息与版本类封装到request中
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)

    def dispatch(self, request, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            response = handler(request, *args, **kwargs)
        except Exception as exc:
            response = self.handle_exception(exc)

        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response


class HomeView(APIView):
    # 配置文件 VERSION_PARAM
    # http://127.0.0.1:8000/home/?xx=123&page=44&vertion=v1     ->    request.version
    versioning_class = QueryParameterVersioning

    def get(self, request):
        print(request.version)
        return Response("...")

4. URL路径传递

urls.py中path和re_path两种方式均可

在这里插入图片描述

# 源码
class URLPathVersioning(BaseVersioning):    
    def determine_version(self, request, *args, **kwargs):
        version = kwargs.get(self.version_param, self.default_version)
        if version is None:
            version = self.default_version

        if not self.is_allowed_version(version):
            raise exceptions.NotFound(self.invalid_version_message)
        return version

kwargs中读取版本,因此视图函数要用*args**kwargs接收,其他源码逻辑与GET参数传递基本相同

5. Accept请求头传递

反向生成的URL不含版本

在这里插入图片描述

参考代码

# urls.py
from django.urls import path, re_path
from api import views

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('home/', views.HomeView.as_view(), name='hm'),
    # path('api/<str:version>/home/', views.HomeView.as_view(), name='hm'),
    # re_path(r'^api/(?P<version>\w+)/home/$', views.HomeView.as_view(), name='hm'),
]
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning, AcceptHeaderVersioning


# Create your views here.
class HomeView(APIView):
    # 配置文件 VERSION_PARAM
    # http://127.0.0.1:8000/home/?xx=123&page=44&vertion=v1     ->    request.version
    versioning_class = AcceptHeaderVersioning

    def get(self, request, *args, **kwargs):
        print(request.version)
        print(request.versioning_scheme)
        url = request.versioning_scheme.reverse('hm', request=request)
        print("反向生成的url:", url)
        return Response("...")

6. 总结

后续开发时,一般都采用URL路径传递的形式,下列给出开发示例

  • settings.py进行全局配置
REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER': None,
    'VERSION_PARAM': 'version',
    'DEFAULT_VERSION': "v1",
    'ALLOWED_VERSIONS': ["v1", "v2"],
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
}
  • views.py
from rest_framework.views import APIView
from rest_framework.response import Response


class HomeView(APIView):
    def get(self, request, *args, **kwargs):
        print(request.version)
        print(request.versioning_scheme)
        url = request.versioning_scheme.reverse('hm', request=request)
        print("反向生成的url:", url)
        return Response("...")
  • urls.py
from django.urls import path, re_path
from api import views

urlpatterns = [
    path('api/<str:version>/home/', views.HomeView.as_view(), name='hm'),
]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值