探究FBV视图

fbv视图主要是讲解用编写视图函数所需要用到的函数,如重定向的redirect;渲染页面的render和HttpResponse;文件下载;上传所需要的函数;还有如何操作cookie,有具体源码,可参考他们是如何实现的。

返回响应内容的解释

响应类型 说明
HttpResponse(‘Hello world’) 状态码200,请求已成功被服务器接受
HttpResponseRedirect(’/’) 状态码302,重定向首页地址
HttpResponsePermanentRedirect(’/’) 状态码301,永久重定向首页地址
HttpResponseBadRequest(‘400’) 状态码400,访问的页面不存在或请求错误
HttpResponseNotFound(‘404’) 状态码404,网页不存在或网页的URL失效
HttpResponseForbidden(‘403’) 状态码403,没有访问权限
HttpResponseNotAllowed(‘405’) 状态码405,不允许使用该请求方式
HttpResponse(‘500’) 状态码500,服务器内容错误
JsonResponse({‘foo’:‘bar’}) 默认状态码200,响应内容为JSON数据
StreamingHttpResponse() 默认状态码200,响应内容以流式输出

HttpResponse类的作用

从HttpResponse的参数可知,第一个参数是响应内容,一般是网页内容或JSON数据,网页内容是以HTML语言为主的,JSON数据用于生成API接口数据。第二个参数用于设置HTTP状态码,它支持HTTP所有的状态码。
从HttpResponse的使用过程可知,如果生成网页内容,就需要将HTML语言以字符串的形式表示,如果网页内容过大,就会增加视图函数的代码量,同时也没有体现模板的作用。

render()函数的详解

render(request,template_name,context=None,content_type=None,status=None,using=None)

request: 浏览器向服务器发送的请求对象,包含用户信息、请求内容和请求方式等。
template_name: 设置模板文件名,用于生成网页内容。
context: 对模板上下文(模板变量)赋值,以字典个格式表示,默认情况下是一个空字典。
content_type: 响应内容的数据格式,一般情况下使用默认值即可。
status: HTTP状态码,默认为200。
using: 设置模板引擎,用于解析模板文件,生成网页内容。

设置重定向

下方的类只支持路由地址而不支持路由命名的传入

class HttpResponseRedirect(HttpResponseRedirectBase):
    status_code = 302


class HttpResponsePermanentRedirect(HttpResponseRedirectBase):
    status_code = 301

redirect函数

def redirect(to, *args, permanent=False, **kwargs):
      # 参数permanent若为真则调用HttpResponsePermanentRedirect,否则调用HttpResponseRedirect,默认为False
    redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect
    return redirect_class(resolve_url(to, *args, **kwargs)) # 对参数to进行判断,若参数to是路由地址则返回to的值,若参数是路由命名,则使用reverse函数转换路由地址
    # 若to是模型对象,则将模型转化为相应的路由地址(不常用)

异常响应

每个应用下的404响应应该相同,所以而已配置全局的status的状态码。
error默认的报错相关的请求参数;
需要把debug设为False

#urls.py,主项目文件下的

from django.urls import path, include
urlpatterns = [
    # 指向index的路由文件urls.py
    path('', include(('index.urls', 'index'), namespace='index')),
]
# 全局404页面配置
handler404 = 'index.views.pag_not_found'
# 全局500页面配置
handler500 = 'index.views.page_error'
# views.py
from django.shortcuts import render
from django.http import Http404


def index(request):
    if request.GET.get('error', ''): # error默认的报错相关的请求参数
        raise Http404("page does not exist")
    else:
        return render(request, 'index.html')


def pag_not_found(request,exception):
    """全局404的配置函数 """
    return render(request, '404.html', status=404)


def page_error(request):
    """全局500的配置函数 """
    return render(request, '500.html', status=500)

文件下载功能

三种方式:

HttpResponse

底层继承HttpResponseBase;需要设置下载文件的类型,以字典的形式

class HttpResponse(HttpResponseBase):
    """
    An HTTP response class with a string as content.

    This content that can be read, appended to, or replaced.
    """

    streaming = False

    def __init__(self, content=b'', *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Content is a bytestring. See the `content` property methods.
        self.content = content

StreamingHttpResponse

class StreamingHttpResponse(HttpResponseBase): # StreamingHttpResponse
    # 将文件以字节流的方式读取,在StreamingHttpResponse类实例化时传入文件的字节流
    """
    A streaming HTTP response class with an iterator as content.

    This should only be iterated once, when the response is streamed to the
    client. However, it can be appended to or replaced with a new iterator
    that wraps the original content (or yields entirely new content).
    """

    streaming = True
    # streaming_content的数据格式可设为迭代器对象或字节流,代表数据或文件内容
    #  *args, **kwargs来自HttpResponseBase,即设置数据格式content_type和响应状态码status等参数。
    def __init__(self, streaming_content=(), *args, **kwargs):
        super().__init__(*args, **kwargs)
        # `streaming_content` should be an iterable of bytestrings.
        # See the `streaming_content` property methods.
        self.streaming_content = streaming_content

FileResponse

class FileResponse(StreamingHttpResponse):
 block_size = 4096

    def __init__(self, *args, as_attachment=False, filename='', **kwargs):
        self.as_attachment = as_attachment # as_attachment的数据类型为布尔型,若为False,则不提供文件下载功能
        # 文件将在浏览器中打开并读取,若浏览器无法打开文件,则将文件下载到本地计算机,但没有设置文件后缀名
        # 若为True,则开启文件下载功能,将文件下载到本地计算机,并设置文件后缀名
        self.filename = filename
        #filename设置下载文件的文件名,该参数与as_attachment的设置有关。若参数as_attachment为False,则参数filename不起
        # 任何作用。在as_attachment为True的前提下。若参数filename为空,则使用该文件原有的文件名作为下载文件的文件名,反之以
        # 参数filename作为下载文件的文件名
        super().__init__(*args, **kwargs)
  。。。。。
# 下方的函数也在类FileResponse中
if self.as_attachment: # set_headers()函数这里设置了as_attachment为true之后的filename一系列操作
    filename = self.filename or os.path.basename(filename)
    if filename:
        try:
            filename.encode('ascii')
            file_expr = 'filename="{}"'.format(filename)
        except UnicodeEncodeError:
            file_expr = "filename*=utf-8''{}".format(quote(filename))
        self['Content-Disposition'] = 'attachment; {}'.format(file_expr)

上述三种下载文件方式实例

views.py

from django.shortcuts import render
from django.http import HttpResponse, Http404
from django.http import StreamingHttpResponse
from django.http import FileResponse

def index(request): #该函数已改,但还是运行不了
    return render(request, 'index.html')

def download1(request):
    file_path = r'D:\\Django_projects\\project_img\\1.jpg'
    try:
        r = HttpResponse(open(file_path, 'rb')) # 将文件以字节流的方式读取并传入响应类HttpResponse进行实例化
        r['content_type'] = 'application/octet-stream'
        r['Content-Disposition'] = 'attachment; filename=640.jpg' # 强制给了个名字
        return r
    except Exception:
        raise Http404('Download error')

def download2(request):
    file_path = r'D:\\Django_projects\\project_img\\1.jpg'
    try:
        # 同样以字节流的方式读取
        r = StreamingHttpResponse(open(file_path, 'rb'))
        r['content_type'] = 'application/octet-stream'
        r['Content-Disposition'] = 'attachment;filename=duck.jpg;' #  ;这个不强制给名字的话就会下载个HTML文件
        return r
    except Exception:
        raise Http404('Download error')
# 文件流读取 -- 迭代器 # 不属于该文,可不看
# 复用下方函数的方法是在下载方式那(如StreamingHttpResponse)调用该file_iter()函数
def file_iter(r, chunk_size=1024):
    # chunk_size=1024 表示一次最大1024k
    with open(file, 'rb') as f:
        while True:
            c = f.read(chunk_size)
            if c:
                yield c
            else:
                break

def download3(request):
    file_path = r'D:\\Django_projects\\project_img\\1.jpg'
    try:
        f = open(file_path, 'rb')
        r = FileResponse(f, as_attachment=False) # 为True下载,默认False不下载,浏览器中打开,然后下载的话没文件后缀
        return r
    except Exception:
        raise Http404('Download error')

urls.py
下载文件也是一种路由,需要上设置url

from django.urls import path
from . import views

urlpatterns = [
    # 定义首页的路由
    path('', views.index, name='index'),
    path('download/file1', views.download1, name='download1'),
    path('download/file2', views.download2, name='download2'),
    path('download/file3', views.download3, name='download3'),
]

上述3种下载方式之间的差异

## HttpResponse这种方式简单粗暴,适合小文件的下载,但如果这个文件非常大,这种方式会占用大量的内存,甚至导致服务器崩溃。
其原理是,HttpResponse会先读取文件到内存,然后再输出
## StreamingHttpResponse + 迭代器 yield;StreamingHttpResponse对象用于将文件流发送给浏览器,与HttpResponse对象非常相似,对于文件下载功能,使用StreamingHttpResponse对象更合理。
## FileResponse是StreamingHttpResonse的一个子类,属于文件;比起StreamingHttpResonse它并不需要设置响应输出类型和方法(当然也可设置),只需设置参数as_attachmennt(布尔型)和filename(文件名)

HTTP请求对象

8种请求方式

get请求是在路由地址后添加"?"和参数内容,参数内容以key=value形式表示,等号前面的是参数名,后面的是参数值
如果涉及多个参数,每个参数之间就使用"&"隔开。
post请求一般以表单的形式传递

视图函数中常用的request参数来源

WSGIRequest
源码位置:django.core.handelers.wsgi.py

class WSGIRequest(HttpRequest):
    def __init__(self, environ):
        script_name = get_script_name(environ)
        # If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
        # trailing slash), operate as if '/' was requested.
        path_info = get_path_info(environ) or '/'
        self.environ = environ
        self.path_info = path_info
        # be careful to only replace the first slash in the path because of
        # http://test/something and http://test//something being different as
        # stated in https://www.ietf.org/rfc/rfc2396.txt
        self.path = '%s/%s' % (script_name.rstrip('/'),
                               path_info.replace('/', '', 1))
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        self.content_type, self.content_params = cgi.parse_header(environ.get('CONTENT_TYPE', ''))
        if 'charset' in self.content_params:
            try:
                codecs.lookup(self.content_params['charset'])
            except LookupError:
                pass
            else:
                self.encoding = self.content_params['charset']
        try:
            content_length = int(environ.get('CONTENT_LENGTH'))
        except (ValueError, TypeError):
            content_length = 0
        self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
        self._read_started = False
        self.resolver_match = None

HttpRequest
源码位置:django.core.http.request.py

class HttpRequest:
    """A basic HTTP request."""

    # The encoding used in GET/POST dicts. None means use default setting.
    _encoding = None
    _upload_handlers = []

    def __init__(self):
        # WARNING: The `WSGIRequest` subclass doesn't call `super`.
        # Any variable assignment made here should also happen in
        # `WSGIRequest.__init__()`.

        self.GET = QueryDict(mutable=True)
        self.POST = QueryDict(mutable=True)
        self.COOKIES = {}
        self.META = {}
        self.FILES = MultiValueDict()

        self.path = ''
        self.path_info = ''
        self.method = None
        self.resolver_match = None
        self.content_type = None
        self.content_params = None

实例
views.py

from django.shortcuts import render

def index(request):
    # 使用method属性判断请求方式
    if request.method == 'GET':
        # 类方法的使用
        print(request.is_secure()) # # 是否采用HTTPS协议
        print(request.is_ajax()) # 是否采用ajax发送HTTP请求,判断原理是请求头中是否包含"'HTTP_X_REQUESTED_WITH':'XMLHttpRequest'"键值对
        print(request.get_host()) # 获取服务器的域名。 127.0.0.1:8000
        print(request.get_full_path()) # 返回路由地址 /?user1=jj&pk=11
        print(request.get_raw_uri())# 返回完整的网址信息,如服务器域名,端口,路由地址等 http://127.0.0.1:8000/?user1=jj&pk=11
        # 属性的使用
        print(request.COOKIES) # {'session_id': '2|1:0|10:1637392827|10:session_id|48:NTVhNjRiOTQtNDlkMi0xMWVjLWI0MDYtYTg2ZGFhNzRmYTIx|7517d72021c78aafbbd3844a667cdbc169aad04279a9e3d016e46ac81b8d76e3', 'csrftoken': 'DCBjabqtqsX7QhqruXdNzeuuosxGzeehEPDukJzPL30swalc3RvbWFkMPGzMYgGk', 'sessionid': 'cxc0a2qtwz7hd9cxg6dzmpbc5txuludq'}
        print(request.content_type) # text/plain
        print(request.content_params) # {}
        print(request.scheme) # http
        # 获取GET请求的请求参数
        print('feng-------------------11')
        print(request.GET.get('user1', '')) # 获取GET请求的请求参数 jj
        # user他的值是内置数据模型User,未登录的话,则是一个实例;但和这无关,user可直接作为模板变量
        return render(request, 'index.html')
    elif request.method == 'POST':
        # 获取POST请求的请求参数
        print(request.POST.get('user1','')) # 这第二项参数为空是为什么;数值不存在的话,我们可以指定显示的默认值:
        return render(request, 'index.html')

html文件

<form action="" method="POST">
    {% csrf_token %}
    <input type="text" name="user1"/>
    <input type="submit" value="提交"/>
</form>

文件上传功能

源码uploadedfile.py定义的4个功能类,主要是定义文件上传功能
位置:django/core/files/uploadedfile.py
SimpleUploadedFile将文件的文件名、大小和类型生成字典格式

class SimpleUploadedFile(InMemoryUploadedFile): # 将文件的文件名、大小和类型生成字典格式
    """
    A simple representation of a file, which just has content, size, and a name.
    """
    def __init__(self, name, content, content_type='text/plain'):
        content = content or b''
        super().__init__(BytesIO(content), None, name, content_type, len(content), None, None)

    @classmethod
    def from_dict(cls, file_dict):
        """
        Create a SimpleUploadedFile object from a dictionary with keys:
           - filename
           - content-type
           - content
        """
        return cls(file_dict['filename'],
                   file_dict['content'],
                   file_dict.get('content-type', 'text/plain'))

将文件对象存放在服务器的内存里,适用于小文件的上传

class InMemoryUploadedFile(UploadedFile): # 将文件对象存放在服务器的内存里,适用于小文件的上传
    """
    A file uploaded into memory (i.e. stream-to-memory).
    """
    def __init__(self, file, field_name, name, content_type, size, charset, content_type_extra=None):
        super().__init__(file, name, content_type, size, charset, content_type_extra)
        self.field_name = field_name

    def open(self, mode=None):
        self.file.seek(0)
        return self

    def chunks(self, chunk_size=None):
        self.file.seek(0)
        yield self.read()

    def multiple_chunks(self, chunk_size=None):
        # Since it's in memory, we'll never have multiple chunks.
        return False

TemporaryUploadedFile将文件数据临时存放在服务器所指定的文件夹里,适用于大文件的上传。

class TemporaryUploadedFile(UploadedFile): # 将文件数据临时存放在服务器所指定的文件夹里,适用于大文件的上传。
    """
    A file uploaded to a temporary location (i.e. stream-to-disk).
    """
    def __init__(self, name, content_type, size, charset, content_type_extra=None):
        _, ext = os.path.splitext(name)
        file = tempfile.NamedTemporaryFile(suffix='.upload' + ext, dir=settings.FILE_UPLOAD_TEMP_DIR)
        super().__init__(file, name, content_type, size, charset, content_type_extra)

    def temporary_file_path(self):
        """Return the full path of this file."""
        return self.file.name

    def close(self):
        try:
            return self.file.close()
        except FileNotFoundError:
            # The file was moved or deleted before the tempfile could unlink
            # it. Still sets self.file.close_called and calls
            # self.file.file.close() before the exception.
            pass

文件上传的基本类,该类主要获取文件的文件名、大小和类型等基本信息。

class UploadedFile(File): # 文件上传的基本类,该类主要获取文件的文件名、大小和类型等基本信息。
    """
    An abstract uploaded file (``TemporaryUploadedFile`` and
    ``InMemoryUploadedFile`` are the built-in concrete subclasses).

    An ``UploadedFile`` object behaves somewhat like a file object and
    represents some file data that the user submitted with a form.
    """

    def __init__(self, file=None, name=None, content_type=None, size=None, charset=None, content_type_extra=None):
        super().__init__(file, name)
        self.size = size
        self.content_type = content_type
        self.charset = charset
        self.content_type_extra = content_type_extra

    def __repr__(self):
        return "<%s: %s (%s)>" % (self.__class__.__name__, self.name, self.content_type)

    def _get_name(self):
        return self._name

    def _set_name(self, name):
        # Sanitize the file name so that it can't be dangerous.
        if name is not None:
            # Just use the basename of the file -- anything else is dangerous.
            name = os.path.basename(name)

            # File names longer than 255 characters can cause problems on older OSes.
            if len(name) > 255:
                name, ext = os.path.splitext(name)
                ext = ext[:255]
                name = name[:255 - len(ext)] + ext

        self._name = name

    name = property(_get_name, _set_name)

为了完善文件的创建、读写和关闭处理。django定义了个uploadhandler.py
源码:django//core/files/uploadhandler.py
定义了7个handler类

### 可划分为两类:异常处理和程序处理
异常处理:上传过程中出现的异常情况,如中断上传
程序处理:实现文件上传
### 均有4个配置属性(7个handler类中)
FILE_UPLOAD_MAX_MEMORY_SIZE:判断文件大小的条件(以字节数表示),默认值为2.5MB
FILE_UPLOAD_PERMISSIONS:可以在源码文件storage.py中找到,用于配置文件上传的用户权限
FILE_UPLOAD_TEMO_DIR
FILE_UPLOAD_HANDLERS

主要是处理异常和程序处理。

实例

views.py

from django.shortcuts import render
from django.http import HttpResponse
import os
from django.views.decorators.csrf import csrf_exempt # 防止啥csrf_什么的攻击

@csrf_exempt # 这个我记得是取消csrfexempt之类的把。
def upload(request):
    # 请求方法为POST时,执行文件上传
    if request.method == "POST":
        # 获取上传的文件,如果没有文件,则默认为None
        # 获取上传的文件
        myFile = request.FILES.get("myfile", None) # myfile为表单的中input标签的name属性
        if not myFile:
            return HttpResponse("no files for upload!")
        # 打开特定的文件进行二进制的写操作;写入文件,之前却没有文件名;你须确保自己以创建了存储该文件的父文件夹
        f = open(os.path.join("D://upload", myFile.name), 'wb+') # 这里采用os.path.join,把后面的文件名加入上来
        # 分块写入文件
        # read()方法也可读取文件,但只适合小文件,multiple_chunks()方法判断,返回True(文件大于2.5MB),否则False,可用该方法来选择读取文件的方式
        for chunk in myFile.chunks(): # 按流响应式读取文件,在for循环进行迭代,将大文件分块写入服务器所指定的保存位置
            f.write(chunk)
        f.close()
        return HttpResponse("upload over!")
    else:
        # 请求方法为GET时,生成文件上存页面
        return render(request, 'upload.html')

html

实例知识点:

enctype="multipart/form-data" 去掉的话,就无法从请求对象的FILES获取文件信息(request.FILES);上述views.py中的文件对象myFile包含文件的基本信息;同时它也是request.FILES.get('form表单上传文件的name值')返回的对象

<form enctype="multipart/form-data" action="" method="post">
   {% csrf_token %}
   <input type="file" name="myfile" />
   <br>
   <input type="submit" value="上存文件"/>
</form>

修改文件上传过程,在很多人上传时很有用
在主项目文件夹下自定义文件夹handler.py文件

from django.core.files.uploadhandler import *
from django.core.files.uploadedfile import *
# 该类就是自定义文件处理过程
class myFileUploadHandler(TemporaryFileUploadHandler): # 用于修改文件上传的处理过程
    def new_file(self, *args, **kwargs):
        super().new_file(*args, **kwargs) # 调用父类的该函数
        print('This is my FileUploadHandler') # 来验证类myFileUploadHandler是否被调用生效
        self.file = TemporaryUploadedFile(self.file_name, self.content_type, 0, self.charset, self.content_type_extra)

在settings.py中进行配置

# 配置文件数据的临时存放文件夹
FILE_UPLOAD_TEMP_DIR = 'D:\\temp\\' # 后缀加不加呢
# 判断文件大小的条件
FILE_UPLOAD_MAX_MEMORY_SIZE = 209715200
# 设置文件上存的处理过程
FILE_UPLOAD_HANDLERS = ['MyDjango.handler.myFileUploadHandler']
# # 默认配置
# FILE_UPLOAD_HANDLERS = (
#     "django.core.files.uploadhandler.MemoryFileUploadHandler",
#     "django.core.files.uploadhandler.TemporaryFileUploadHandler",)

Cookie实现反爬虫

由于Htpp协议是无状态的;浏览器向服务器发送请求,服务器做出响应之后,两者便会断开连接(会话结束),下次用户再次访问服务器,服务器便没有办法识别此用户是谁。
比如登录操作,没有Cookie的话,只能通过查询数据库来实现,若刷新了页面,那就要重新查询数据库,很麻烦;有了Cookie的话就可以实现浏览器和服务器建立长久联系的会话。

在django中操作Cookie对象

## 获取cookie
request.COOKIES[key]
request.COOKIES.get(key)
# 普通cookie是明文传输的,可以直接在客户端直接打开,所以需要加盐,解盐之后才能查看
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
## 设置cooke;并将cookie返回给浏览器
rep = HttpResponse(...) 或 rep = render(request, ...)
rep.set_cookie(key,value,...)
# 给cookie签名
rep.set_signed_cookie(key,value,salt='加密盐',...)
return rep
## 在响应内容中删除cookie
rep = HttpResponse(...) 或 rep = render(request, ...)
rep.delete_cookie(key)
return rep

添加Cookie的方法是由set_cookie方法实现的,该方法是由响应类HttpResponseBase定义的。

# 设置Cookie
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) # 设置Cookie的有效时间,以秒为单位
        else:
            self.cookies[key]['expires'] = expires
    else:
        self.cookies[key]['expires'] = ''
    if max_age is not None:
        self.cookies[key]['max-age'] = max_age
        # IE requires expires, so set it if hasn't been already.
        if not expires:
            self.cookies[key]['expires'] = http_date(time.time() + max_age)
    if path is not None:
        self.cookies[key]['path'] = path
    if domain is not None:
        self.cookies[key]['domain'] = domain
    if secure:
        self.cookies[key]['secure'] = True
    if httponly:
        self.cookies[key]['httponly'] = True
    if samesite:
        if samesite.lower() not in ('lax', 'strict'):
            raise ValueError('samesite must be "lax" or "strict".')
        self.cookies[key]['samesite'] = samesite

def setdefault(self, key, value):
    """Set a header unless it has already been set."""
    if key not in self:
        self[key] = value
# 设置并加密cookie
def set_signed_cookie(self, key, value, salt='', **kwargs):
    # key设置Cookie的key,类似于字典的Key,value设置Cookie的Value,salt设置加密盐,**kwargs可选参数,用于设置set_cookie的参数
    value = signing.get_cookie_signer(salt=key + salt).sign(value)
    return self.set_cookie(key, value, **kwargs)
# 删除cookie
def delete_cookie(self, key, path='/', domain=None):
    # Most browsers ignore the Set-Cookie header if the cookie name starts
    # with __Host- or __Secure- and the cookie doesn't use the secure flag.
    secure = key.startswith(('__Secure-', '__Host-'))
    self.set_cookie(
        key, max_age=0, path=path, domain=domain, secure=secure,
        expires='Thu, 01 Jan 1970 00:00:00 GMT', # expires设置Cookie的有效时间,以日期格式为单位
    )

上述源码中主要有9个函数参数

参数名 描述
key 这个cookie的key
value 这个cookie的value
max_age 最长生命周期,单位是秒
expires 过期时间,跟max_age类似,只不过这个参数需要传递一个具体的日期,如datetime或者是符合日期格式的字符串。注意,如果同时设置了expires和max_age,那么将会使用expires的值作为过期日期
path 对域名下哪个路径有效。默认是对域名下的所有路径都有效
domain 针对哪个域名有效。默认是针对主域名下都有效,如果只要针对某个子域名才有效,可以设置这个属性
secure 是否是安全的,如果设置为True,那么只能在https协议下才可用
httponly 默认是False。如果为True,那么在客户端不能通过JavaScript进行操作
samesite 设置强制模式,可选值为lax或strict,主要防止CSRF攻击
常见的反爬虫主要是设置max_age、expires和path.前两者是设置Cookie的有效期,后一者是Cookie的生成路径
cookie内置的加密方法
# 设置并加密cookie
def set_signed_cookie(self, key, value, salt='', **kwargs):
  # key设置Cookie的key,类似于字典的Key,value设置Cookie的Value,salt设置加密盐,**kwargs可选参数,用于设置set_cookie的参数
  value = signing.get_cookie_signer(salt=key + salt).sign(value)
  return self.set_cookie(key, value, **kwargs)

该方法来自django/core/signing.py文件夹下
下面是最主要的两个类

class Signer:

    def __init__(self, key=None, sep=':', salt=None):
        # Use of native strings in all versions of Python
        self.key = key or settings.SECRET_KEY # 自己设置的Key或者settings.py中设置的
        self.sep = sep
        if _SEP_UNSAFE.match(self.sep):
            raise ValueError(
                'Unsafe Signer separator: %r (cannot be empty or consist of '
                'only A-z0-9-_=)' % sep,
            )
        self.salt = salt or '%s.%s' % (self.__class__.__module__, self.__class__.__name__)

    def signature(self, value):
        return base64_hmac(self.salt + 'signer', value, self.key)

    def sign(self, value):
        return '%s%s%s' % (value, self.sep, self.signature(value))

    def unsign(self, signed_value): # 解密cookie,self.sep不会是用来帮助解密的把
        if self.sep not in signed_value:
            raise BadSignature('No "%s" found in value' % self.sep)
        value, sig = signed_value.rsplit(self.sep, 1)
        if constant_time_compare(sig, self.signature(value)):
            return value
        raise BadSignature('Signature "%s" does not match' % sig)


class TimestampSigner(Signer):

    def timestamp(self):
        return baseconv.base62.encode(int(time.time()))

    def sign(self, value): # 用于加密,采用base64_hmac()函数,b64_encode的加密算法
        value = '%s%s%s' % (value, self.sep, self.timestamp()) # self.sep这参数哪来的
        return super().sign(value)

    def unsign(self, value, max_age=None): # Cookie的解密过程
        """
        Retrieve original value and check it wasn't signed more
        than max_age seconds ago.
        """
        result = super().unsign(value)
        value, timestamp = result.rsplit(self.sep, 1)
        timestamp = baseconv.base62.decode(timestamp)
        if max_age is not None:
            if isinstance(max_age, datetime.timedelta):
                max_age = max_age.total_seconds()
            # Check timestamp is not older than max_age
            age = time.time() - timestamp
            if age > max_age:
                raise SignatureExpired(
                    'Signature age %s > %s seconds' % (age, max_age))
        return value

加密cookie实例

views.py

from django.http import Http404, HttpResponse
from django.shortcuts import render, redirect
from django.shortcuts import reverse

def index(request):
    return render(request, 'index.html')

def create(request): # 一般实现过程不会通过路由,而是使用前端的AJAX方法或干脆隐藏请求信息
    r = redirect(reverse('index:index'))
    # 添加Cookie;2种方式
    # response.set_cookie('uid', 'Cookie_Value') # Cookie_Value需要自行加密
    # uuid是键,id是值
    r.set_signed_cookie('uuid', 'id', salt='MyDj', max_age=10) # 内置添加Cookie并加密
    return r

def myCookie(request):
    # 获取Cookie,与python字典读取方式一致
    # request.COOKIES['uuid']
    cookieExist = request.COOKIES.get('uuid', '') 
    if cookieExist:
        # 验证加密后的Cookies是否有效
        try:
            # 下方函数写错了
            request.get_signed_cookie('uuid', salt='MyDj') # cookie解密处理
            # 应该是set_signed_cookie设置加密Cookie,也许是对的get_signed_cookie解密Cookie,我之前还以为是unsign()函数
        except:
            raise Http404('当前Cookie无效哦!')
        return HttpResponse('当前Cookie为:' + cookieExist) # 将解密后的Cookie返回到页面上
    else:
        raise Http404('当前访问没有Cookie哦!')

urls.py


from django.urls import path, include
urlpatterns = [
    # 指向index的路由文件urls.py
    path('', include(('index.urls', 'index'), namespace='index')),
]

允许自定义Cookie加密解密机制

Cookie加密解密是由源码文件signing.py的TimestampSigner类实现的,只要定义该类子类,并重写即可
sign和unsign方法即可

from django.core.signing import TimestampSigner
class myTimestampSigner(TimestampSigner):
    def sign(self, value): # 自定义加密
        print(value)
        return value + 'Test'

    # 自定义解密
    def unsign(self, value, max_age=None):
        print(value)
        return value[0:-4]

settings.py的配置文件

# 默认的Cookie加密(解密)引擎;可不写
# SIGNING_BACKEND = 'django.core.signing.TimestampSigner'
# 自定义Cookie加密(解密)引擎
SIGNING_BACKEND = 'MyDjango.mySigner.myTimestampSigner'

请求头实现反爬虫

使徒的参数request是由类WSGIRequest根据用户请求而生成的实例化对象。
类WSGIRequest是继承HttpRequest,而类HttpRequest设置MEYA属性
因此浏览器访问Django时,会由WSGI协议实现两者的通信,浏览器的请求头来自类WSGIRequest的属性environ,而属性environ来自类WSGIHandler.

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest # WSGIRequest类

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.load_middleware()

    def __call__(self, environ, start_response):
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ) # 类实例化
        response = self.get_response(request)

请求头信息只能浏览器进行设置,服务器只能读取请求头信息,通过请求头实现的反爬虫机制需要借助前端的AJAX实现

请求头实现反爬虫实例:

views.py

from django.http import JsonResponse
from django.shortcuts import render

def index(request):
    return render(request, 'index.html')

# API接口判断请求头
def getHeader(request):
    # Djnago获取请求头的固有格式都时HTTP_XXX,XXX代表请求头的某个属性
    header = request.META.get('HTTP_SIGN', '') # 自定义属性HTTP_SIGN是否存在
    if header:
        value = {'header': header}
    else:
        value = {'header': 'null'}
    return JsonResponse(value)

urls.py

from django.urls import path
from . import views

urlpatterns = [
    # 定义路由
    path('', views.index, name='index'),
    path('getHeader', views.getHeader, name='getHeader')
]

html

{% load static %}
<script src="{% static 'jquery.js' %}"></script>
<script>
$(function(){
  $("#bt01").click(function(){
    var value = $.ajax({
        type: "GET",
        url:"/getHeader", //请求的url发送相应的请求
        dataType:'json',
        async: false,
        headers: {
            "sign":"123",
        }
        }).responseJSON;
        value = '<h2>'+value["header"]+'</h2>'; //ajax加入请求头
        $("#myDiv").html(value);
    });
});
</script>
<body>
<h3>Hello Headers</h3>
<div id="myDiv"><h2>AJAX获取请求头</h2></div>
<button id="bt01" type="button">改变内容</button>
</body>
努力拼搏吧,不要害怕,不要去规划,不要迷茫。但你一定要在路上一直的走下去,尽管可能停滞不前,但也要走。
原文地址:https://www.cnblogs.com/wkhzwmr/p/15639701.html