django模块http之HttpResponse源码解析

web 同时被 2 个专栏收录
17 篇文章 1 订阅
12 篇文章 0 订阅

HttpResponse 源码文件

由于django函数每次返回的httprespones的内容不是很明白其中原理,再到使用JsonResponse后报错,于是找到源码,想研究一下,是如何进行对照的数据返回及报错原理,

响应是很重要的一步了,前端接到响应值需要浏览器和前端代码进行分析解读,也是http请求协议的重要组成部分,这会让我们更加清楚这个流程,从而获得更充分的了解,知己知彼百战百胜!开始吧!

开始前建议先下载本资源中的3.0.x源码,github下也可下载,为减少麻烦,请默认下载本博客中的源码资源。并且使用相对应的编辑器,以便于更好的追踪代码

  • 源码资源: 3.0.1资源下载链接
  • 编辑器:pycharm
  • python版本: 3.5.1
  • 测试环境:centos7.3
  • 为了方便debug运行调试,请将下图中源码中HttpResponseBase类的图框处默认改为UTF-8,这样我们就可以直接运行了
    在这里插入图片描述

!!!建议参照下载文件资源进行阅读,本文无法将代码全部罗列出!!!

打开文件目录如下:找到http中的reponse.py这里可以找到HttpResponse类这是第一步
在这里插入图片描述
在这里插入图片描述

根据查到的HttpResponse的代码找到HttpResponseBase上图(显示部分)

  • 分析HttpResponseBase
    HttpResponseBase类下的注释

“”" An HTTP response base class with dictionary-accessed headers.
This class doesn’t handle content. It should not be used directly.
Use the HttpResponse and StreamingHttpResponse subclasses instead."""

带有字典访问头的HTTP响应基类。 这个类不处理内容。它不应该直接使用。
使用HttpResponse和StreamingHttpResponse子类代替。

明确改基类HttpResponseBase的作用,他只是字典访问头的HTTP响应基类,不处理问题,和直接调用

  1. 分析HttpResponseBase类下的初始化(部分加入了自己的)
    在这里插入图片描述
    基类的描述信息:

_headers is a mapping of the lowercase name to the original case of
the header (required for working with legacy systems) and the header
value. Both the name of the header and its value are ASCII strings.
“# _headers是一个小写名称到原始大小写的映射
#标头(使用遗留系统所需的)和标头
#价值。头的名称和它的值都是ASCII字符串。”
This parameter is set by the handler. It’s necessary to preserve the
historical behavior of request_finished.
#该参数由处理器设置。有必要保存
request_finished的历史行为

  1. status_code ==200 默认咱们的请求状态码为200表示http请求成功
  2. 参数 content_type 开发经常遇到的,前端接口调用api接口后返回的数据一定会有一个content_type 数据类型

其中:text/html, text/plain, text/css, text/javascript, image/jpeg,
image/png, image/gif, 都是常见的页面资源类型。

application/x-www-form-urlencoded,
multipart/form-data, application/json, application/xml
这四个是ajax的请求,表单提交或上传文件的常用的资源类型。在这里插入图片描述

  1. 参数 status 这个是传参定义该请求的状态,以100-599为http请求状态码,reason为原因, charset为字符集这些都是可选择设置的
  2. 属性
    _headers: 就是我们http请求数据的请求头定义一个 {}
    _resource_closers: 定义了资源中心的 [ ]
    _handler_class: # 源码中描述,该参数由处理器设置。有必要保存
    cookies: cookies我们当然很熟系,但是他是继承
    from django.http.cookie import SimpleCookie
    SimpleCookie() 这个类为cookie的设置方法(这里暂时只研究http协议,暂不讨论cookies的方法,之后会单独写一篇博客处理他们)
    closed:设置一个bool类型的False关闭参数
    _reason_phrase: 接收我们传输的原因
    _charset:接收我们传入的字符集
    self[‘Content-Type’]:

类参数中如何设置self[‘Content-Type’]?

研究语法时,自己编辑测试写类这样的self[“items”]这样的语法报错,研究发现该类里还有三个python的特殊函数(在HttpResponseBase基类中)

1__xxxitem__:使用 [’’] 的方式操作属性时被调用

2__setitem__:每当属性被赋值的时候都会调用该方法,因此不能再该方法内赋值 self.name = value 会死循环

3__getitem__:当访问不存在的属性时会调用该方法

4__delitem__:当删除属性时调用该方法
在这里插入图片描述
我为测试三个方法写的代码,可以利用它们解决这三个函数的实际应用效果

class Test(object):
    def __init__(self, name=None, age=None, gender=None):
        self._headers = {}
        self["name"] = name
        self.age = age
        self.gender = gender

    def __setitem__(self, name, value):
        self.__dict__[name] = value

    def __getitem__(self, item):
        return self.__dict__[item]

    def __delitem__(self, key):
        del self.__dict__[key]

    def __str__(self):
        # print(self.get("cons"))
        return "这个类的名字是{},年龄是{},性别是{}".format(self['name'], self.gender, self.age)


if __name__ == '__main__':
    print(Test(age=11, name="pxq", gender="女"))  # >>> 这个类的名字是pxq,年龄是女,性别是11
    t = Test(age=11, name="pxq", gender="女")
    print(t["gender"]) # >>> 女 
    print(t["name"]) # >>> pxq
    print(t["age"]) # >>> 11
    t["class"] = "1班" 
    print(t["class"]) # >>> 1班
    del t["gender"]
    print(t["gender"]) 
    """
            Traceback (most recent call last):
                File "/var/www/WebGUI/www/test.py", line 384, in <module>
                    print(t["gender"])
                File "/var/www/WebGUI/www/test.py", line 365, in __getitem__
                    return self.__dict__[item]
        KeyError: 'gender'
    """

初始化参数及其属性看完,接着我们看看下图他的方法,这里的对reason_phrase和charset属性进行查看及修改,细节不需要多说,我们重点看看charset属性方法里的**_charset_from_content_type_re** 图中84行已经被修改,原来的值为 return settings.DEFAULT_CHARSET 这个是从django.conf配置中获取,这个位置代码也需要单独对django.conf的源码解析(放在下一篇博客解析吧)
在这里插入图片描述
_charset_from_content_type_re 在代码的21行,这边查询可以看出,它从初始化中构成的dict里找寻charset的实际values,再用下图的代码进行匹配,从而取出charset的默认值,这一行是单独放在文件头部的,原因需要进一步理解!
在这里插入图片描述下一个函数方法就是设置HttpResponseBase类的charset,,上面详细提到了property不过多赘述。

如图函数serialize_headers
在这里插入图片描述这里在函数里嵌套了一个to_bytes()函数,主要将数据进行统一bytes处理最后拼接成一个\r\n
这里有一个地方有问题的是98行 ,self._headers.values()这个按照我们python语法是循环出字典的values的,就不应该有key,我单独将代码取出,用字典代替操作,结果报错,不太清楚为什么?
这个位置还希望有大神帮我看看,下面这个例子,和上面这个有什么问题!
在这里插入图片描述在这里插入图片描述

看下一段之前上面还有一段不是很懂,这个位置将header里的数据通过 serialize_headers 函数获取 header 以 bytestring 的格式传给__bytes__或者__str__
这是在一段论坛上看到的不太理解,未来回顾的时候,再细品吧!记录一下!
在这里插入图片描述这个_content_type_for_repr函数主要是打印一下content-type的值

接下来是_convert_to_charset这个函数,看看注释内容

Convert headers key/value to ascii/latin-1 native strings.
charset must be ‘ascii’ or ‘latin-1’. If mime_encode is True and
value can’t be represented in the given charset, apply MIME-encoding.
将标头键/值转换为ascii/latin-1本地字符串。
‘字符集’必须是’ascii’或’latin-1’。如果’ mime_encode '为True,并且
“value”不能在给定的字符集中表示,请应用MIME-encoding。

这个函数主要是将传输的内容进行根据标准的,或者配置好的字符编码进行处理
这里开始不截图,改为直接贴代码,代码完全是源码复制下来的!~

    def has_header(self, header):
        """Case-insensitive check for a header."""
        return header.lower() in self._headers

    __contains__ = has_header

    def items(self):
        return self._headers.values()

    def get(self, header, alternate=None):
        return self._headers.get(header.lower(), (None, alternate))[1]

看上述代码函数中has_header()这里主要是对header进行验证,contains 的功能主要是测试该类中,对象中是否含有该参数!或者查验参数是否在对象内。
items,get 主要是对header里的数据进行读取和查看

    def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
                   domain=None, secure=False, httponly=False, samesite=None):
        """
        Set a cookie.

        ``expires`` can be:
        - a string in the correct format,
        - a naive ``datetime.datetime`` object in UTC,
        - an aware ``datetime.datetime`` object in any time zone.
        If it is a ``datetime.datetime`` object then calculate ``max_age``.
        """
        self.cookies[key] = value
        if expires is not None:
            if isinstance(expires, datetime.datetime):
                if timezone.is_aware(expires):
                    expires = timezone.make_naive(expires, timezone.utc)
                delta = expires - expires.utcnow()
                # Add one second so the date matches exactly (a fraction of
                # time gets lost between converting to a timedelta and
                # then the date string).
                delta = delta + datetime.timedelta(seconds=1)
                # Just set max_age - the max_age logic will set expires.
                expires = None
                max_age = max(0, delta.days * 86400 + delta.seconds)
            else:
                self.cookies[key]['expires'] = expires
        else:
            self.cookies[key]['expires'] = ''

这个位置是源码中的设置cookie字段,cookie这里,我打算另外写一篇文章去解决他,所以这个位置,我们大概知道httpResponceBases里包含了组成http请求header,设置cookie,删除cookie等。另外还有
制作bytes,和关闭类就属于简单的代码了,细节还需要修炼内功才能完全掌握,类中还有write,flush,tell,readable,seekable,writable,writelines 这些都属于类中的功能型函数,在基础类中并没有实现,只是告诉了类所具有的功能。HttpResponseBase的介绍大概就到这里说完。

    def make_bytes(self, value):
        """Turn a value into a bytestring encoded in the output charset."""
        # Per PEP 3333, this response body must be bytes. To avoid returning
        # an instance of a subclass, this function returns `bytes(value)`.
        # This doesn't make a copy when `value` already contains bytes.

        # Handle string types -- we can't rely on force_bytes here because:
        # - Python attempts str conversion first
        # - when self._charset != 'utf-8' it re-encodes the content
        if isinstance(value, (bytes, memoryview)):
            return bytes(value)
        if isinstance(value, str):
            return bytes(value.encode(self.charset))
        # Handle non-string types.
        return str(value).encode(self.charset)

    # These methods partially implement the file-like object interface.
    # See https://docs.python.org/library/io.html#io.IOBase

    # The WSGI server must call this method upon completion of the request.
    # See http://blog.dscpl.com.au/2012/10/obligations-for-calling-close-on.html
    def close(self):
        for closer in self._resource_closers:
            try:
                closer()
            except Exception:
                pass
        # Free resources that were still referenced.
        self._resource_closers.clear()
        self.closed = True
        signals.request_finished.send(sender=self._handler_class)

首先我们先总结一下关于HttpResponseBase类的一些问题:

  1. django中的HttpResponseBase是基类,是所有http请求返回数据的基类,父类!
  2. 类中包含,组成数据header,设置cookie,返回数据的类型转换,设置字符集编码,设置原因内容,返回请求状态200等
  3. 类中使用的python内置方法及魔法函数,@property,@reason_phrase.setter,setitemdelitem
    getitem,__contains__等
  4. 该类只用做基类,不处理直接返回数据内容需求,实际只是拼接了http响应头部和一些必要方法。
  5. 类中的属性设置,属性查找,一些特殊方法的用途,特殊的语法,感觉读完感觉茅塞顿开,重点是不理解的地方将自己敲代码验证才是最快最高效的做中学!

接下来我们看看第一个继承类函数HttpResponse是怎么操作的,这为解决我们使用HttpResponse有至关重要的作用
在这里插入图片描述

我们首先看到HttpResponse类里的内容进行分析解析如上图:

  1. 首先看到的是HttpResponse这个类继承了HttpResponseBase这个类
  2. 声明了类的属性streaming=False
  3. _init_初始化了继承类
  4. 类属性content为调用HttpResponse传参
  5. repr_一个打印类的时候如 print(HttpResponse())会打印出类的属性,一般会先找类中是否有_str_方法,打印类的时候,优先调用_str_方法,没有的时候调用_repr,再没有就会打印类的内存地址了。(参考书籍—《流畅的python》)

问题: HttpResponse类里的serialize函数拼接http完整数据为什么使用 “\r\n\r\n”?

解析:

HTTP包结构如下图: “请求行”+“请求头”+“请求数据” == 完整的http数据包
在这里插入图片描述
可以看到请求heard与body数据这里用空行分开(这个是HTTP请求包结构必须知道的点,请求头与请求body中用一行空行分隔开参考数据《图解http》),故有第一个\r\n ,最后请求heard的末尾也需要’\r\n’所以出现源码中的**‘\r\n\r\n’**
在这里插入图片描述

继续HttpResponse类的源码上图接上:

  1. 私有变量__bytes__等于刚刚我们拼接的完整的HTTP消息,包括头,作为字节字符串。

  2. @property这个举例说明

    首先,我们想将voltage属性进行修改,但是又不想让所有使用的人随意修改,或者修改也行,我会告诉你格式是否正确,
    这个时候,我们将该属性设置为私有属性 _voltage,这个时候我们想通过Parrot().voltage就打印不出数据啦,反之用我们所看到的定义一个装饰器property方法的属性名称方法 def voltage(self):
    如下代码所示,这样我们再使用Parrot().voltage()就可以打印出 _voltage = 100000的值啦
    还有
    property 的 getter,setter 和 deleter
    方法同样可以用作装饰器,这个具体用法请同学移步查阅相关资料。

 class Parrot(object):
    def __init__(self):
        self._voltage = 100000
 
    @property
    def voltage(self):
        """Get the current voltage."""
        return self._voltage
上面的代码将 voltage() 方法转化成同名只读属性的 getter 方法。

property 的 getter,setter 和 deleter 方法同样可以用作装饰器:

class C(object):
    def __init__(self):
        self._x = None
 
    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x
 
    @x.setter
    def x(self, value):
        self._x = value
 
    @x.deleter
    def x(self):
        del self._x
  1. def content(self):
    @property
    def content(self):
    这个方法中,就使用了@property进行装饰属性,
    @content.setter
    def content(self,values):
    这个方法就可以用来设置属性值。

  2. @content.setter
    def content(self,values):
    方法中又get到两个新的python一些内置方法分别是
    hasattr(value, ‘___iter __’) 和 isinstance(value, (bytes, str)
  3. hasattr() 函数用于判断对象是否包含对应的属性。
class Coordinate:
	    x = 10
	    y = -5
	    z = 0
	 
point1 = Coordinate() 
print(hasattr(point1, 'x'))
print(hasattr(point1, 'y'))
print(hasattr(point1, 'z'))
print(hasattr(poin

t1, ‘no’)) # 没有该属性

  1. isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。
	>>>a = 2
	>>> isinstance (a,int)
	True
	>>> isinstance (a,str)
	False
	>>> isinstance (a,(str,int,list))    # 是元组中的一个返回 True
	True
  • 0
    点赞
  • 2
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值