【4.0】DRF之Request类源码分析

发布时间 2023-07-31 12:24:29作者: Chimengmeng

【一】引入

class BooksView(APIView):
    def post(self, request):
        '''

        :param request: 新的request,不是原来的那个
        :return:
        '''
        print(type(request))  # rest_framework中的新 request

        # 继承 APIView 后,无论是post请求还是 get请求,获取数据都是从 data 中获取
        print(request.data)  # 新的request中有了data 属性

        # 原来的request.POST中还有数据,但是数据类型支持的没有data全,比如json格式的数据
        print(request.POST)

        # 提交文件是 form-data 格式,还在 request.FILES 中
        print(request.FILES)

        # 新的request的其他属性,使用起来和原来的一样
        print(request.method)
        print(request.body)  # 有文件传入时,用 data 属性就会报错
        print(request.path)

        # Response 和 JsonResponse 很像,但是比 JsonResponse 功能更加强大
        # 可以传字符串,列表,字典都可以,就是不能传对象
        return Response("ok")
  • 总结

    • 新的request有个data属性,以后只要是在body体中的数据

      • 无论什么编码格式
      • 无论什么请求方式
    • 取文件数据还是从 FILES 中取

    • 取其他的属性(method/request/path...),和原来的一样

      • 原理是:新的Request重写的__getattr__方法,通过反射取到原来的request中的属性
    • request.GET 现在可以使用 request.query_params

      @property
      def query_params(self):
          return self._request.GET
      

【二】源码分析

【1】新的request分析

class Request:

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )
		
        # 将原来的 request 重命名隐藏为新的 _request
        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)
  • 原来的request现在变成了 新的request._request

【2】Request 源码中的 __getattr__方法

def __getattr__(self, attr):
    try:
        return getattr(self._request, attr)
    except AttributeError:
        return self.__getattribute__(attr)
  • __getattr__ 方法被定义为先尝试从一个名为 _request 的属性中获取指定的属性(attr
    • 如果 _request 中存在该属性,则返回其对应的值。
    • 如果 _request 中不存在该属性,就会触发 AttributeError 异常。
  • 如果发生 AttributeError 异常
    • 代码将会调用 self.__getattribute__(attr) 来处理。
    • __getattribute__ 是另一个特殊方法,用于获取对象的属性。
    • 通过调用 self.__getattribute__(attr),实际上是直接访问对象本身的属性,而不是从 _request 中获取属性。
  • 这种处理方式是为了避免在 _request 属性中出现的任何属性错误导致无限递归的情况。
    • 因为在 __getattribute__ 方法内部访问属性时
    • 如果再次调用 __getattr__ 方法,就会导致无限循环。

下面是示例代码的详解:

def __getattr__(self, attr):
    try:
        return getattr(self._request, attr)
    except AttributeError:
        return self.__getattribute__(attr)
  • 参数:

    • self:对象本身。
    • attr:要访问的属性名。
  • 处理逻辑:

    • 首先,尝试从 _request 属性中获取指定的属性,使用 getattr(self._request, attr)
    • 如果成功获取到属性,则返回它的值。
    • 如果发生 AttributeError 异常(即 _request 中不存在该属性),则捕获异常。
    • 在异常处理中,调用 self.__getattribute__(attr) 直接访问对象本身的属性。
    • 如果仍然发生 AttributeError 异常,则说明对象本身也没有这个属性,将会抛出原始的 AttributeError 异常。
  • 魔法方法补充: __getattr__
    • . 拦截(对象.属性)
    • 当属性不存在时,会触发 类中 __getattr__的执行

【3】Request 中的 query_params 方法

@property
def query_params(self):

    return self._request.GET
  • query_params 是一个只读属性(使用 @property 装饰器),用于获取请求中的查询参数。

代码逻辑如下:

  • 属性装饰器 @property 用于将方法转换为只读属性。

    • 这样,在访问 query_params 属性时,实际上是调用该方法并返回其结果。
  • query_params 方法返回了 self._request.GET,其中 _request 是一个私有属性,可能是指向当前请求对象的引用。

    • 在 Django 中,请求对象有一个名为 GET 的属性,用于获取请求的查询参数。
  • 假设 _request 是一个指向请求对象的引用,上述代码可以有效地将请求对象的 GET 属性作为查询参数返回给调用方。
  • 通过访问 query_params 属性,可以获得请求中包含的所有查询参数的字典形式。

【补充】魔法方法 __getattr__

  • __getattr__ 是一个特殊方法(也称为魔法方法或特殊属性),可以用于在访问一个对象的不存在的属性时进行拦截和处理。

  • 当我们使用"对象.属性"的形式访问对象的属性时

    • 如果该属性不存在,Python 会默认触发 __getattr__ 方法的执行。
  • __getattr__ 方法接收一个参数,即要访问的属性名,可以在这个方法中定义自定义的逻辑来处理这种情况。

    • 它允许我们动态地添加属性或者对属性访问进行处理。

【1】__getattr__ 方法的定义:

def __getattr__(self, name):
    # 自定义处理逻辑

【2】触发条件:

  • 当通过对象访问属性时
  • 如果该属性不存在,就会触发 __getattr__ 方法的执行。

【3】参数:

  • self:对象本身。
  • name:要访问的属性名。

【4】返回值:

  • 可以返回一个属性值,以满足属性访问的需求。
  • 如果仍然不存在属性,则引发 AttributeError 异常。

【5】示例:

下面的示例演示了如何使用 __getattr__ 方法来动态处理属性的访问:

class MyClass:
    def __getattr__(self, name):
        if name == 'attribute1':
            return 'This is attribute1'
        elif name == 'attribute2':
            return 'This is attribute2'
        else:
            raise AttributeError(f"'MyClass' object has no attribute '{name}'")

obj = MyClass()
print(obj.attribute1)  # 输出:This is attribute1
print(obj.attribute2)  # 输出:This is attribute2
print(obj.attribute3)  # 输出:AttributeError: 'MyClass' object has no attribute 'attribute3'
  • 在上述示例中

    • 当访问 obj.attribute1 或者 obj.attribute2 时,__getattr__ 方法会被触发并返回相应的值。
  • 但是当访问一个不存在的属性时

    • obj.attribute3__getattr__ 方法引发 AttributeError 异常。
  • 通过使用 __getattr__ 方法,我们可以对属性访问进行灵活的处理,例如实现延迟加载、使用动态属性等功能。

【补充】Http的get请求可不可以携带body数据

可以带,但是不建议带

  • 根据HTTP/1.1和HTTP/2协议规范,GET请求通常不包含请求体(body),而是将参数以键值对的形式添加到URL的查询字符串中。
    • GET请求的主要目的是从服务器获取资源或数据
    • 并且请求参数会被编码在URL中
    • 而非请求体中。
  • 然而,虽然不建议在GET请求的请求体中携带数据,但HTTP协议本身并没有明确禁止该操作。
    • 一些服务器和应用程序可能会接受具有请求体的GET请求,并根据具体实现来处理这些请求。
  • 由于GET请求的设计初衷是用于数据获取而非数据提交,因此将数据放入请求体中可能会导致一些问题。
    • 例如,一些服务器可能会忽略请求体中的数据或拒绝处理带有请求体的GET请求。
  • 总结起来
    • 尽管HTTP的GET请求通常不应该在请求体中携带数据,但是一些特定情况下,服务器和应用程序可能会支持处理带有请求体的GET请求。
    • 使用时请注意遵循HTTP协议的规范,并确保与服务器端的需求相匹配。