Django中间件与上下文渲染器

django中一个请求的流程

我们从浏览器发出一个请求 Request,得到一个响应后的内容 HttpResponse ,这个请求传递到 Django的过程如下:


image.png

也就是说,每一个请求都是先通过中间件中的 process_request 函数,这个函数返回 None 或者 HttpResponse 对象,如果返回前者,继续处理其它中间件,如果返回一个 HttpResponse,就处理中止,返回到网页上。

Django中间件(Middleware)

  • 中间件,顾名思义,就是处在中间的一些软件。比如匹配到了URL,但是还没有执行view函数的时候,这个时候可以执行一些代码,这个代码就是中间件。
  • Django已经内置了许多中间件,这些中间件已经可以满足90%以上的需求,如果还不够,还可以定义自己的中间件,以下将对Django自带的中间件进行详细解释,自带在settings.py中MIDDLEWARE配置:


    image.png
  1. django.middleware.security.SecurityMiddleware:一些安全设置,比如XSS脚本过滤。
  2. django.contrib.sessions.middleware.SessionMiddleware:session支持中间件,加入这个中间件,会在数据库中生成一个django_session的表。
  3. django.middleware.common.CommonMiddleware:通用中间件,会处理一些URL,比如baidu.com会自动的处理成www.baidu.com。比如/blog/111会处理成/blog/111/自动加上反斜杠。
  4. django.middleware.csrf.CsrfViewMiddleware:跨域请求伪造中间件。加入这个中间件,在提交表单的时候会必须加入csrf_token,cookie中也会生成一个名叫csrftoken的值,也会在header中加入一个HTTP_X_CSRFTOKEN的值来放置CSRF攻击。
  5. django.contrib.auth.middleware.AuthenticationMiddleware:用户授权中间件。他会在每个HttpRequest对象到达view之前添加当前登录用户的user属性,也就是你可以在view中通过request访问user。
  6. django.contrib.messages.middleware.MessageMiddleware:消息中间件。展示一些后台信息给前端页面。如果需要用到消息,还需要在INSTALLED_APPS中添加django.contrib.message才能有效。如果不需要,可以把这两个都删除。
  7. django.middleware.clickjacking.XFrameOptionsMiddleware:防止通过浏览器页面跨Frame出现clickjacking(欺骗点击)攻击出现。

中间件执行顺序

  • 在调用视图前,按照settings中配置的MIDDLEWARE顺序执行
  • 在调用视图后,中间件会按相反的顺序执行

编写一个自定义的中间件

  • 你可以定义一个独立的python类来作为一个自定义中间件
  1. __init__(self, get_response):
    中间件的初始化只会在django启动时调用一次,并且只接受get_response函数,所以一般都不会去重写它
    def __init__(self,get_response):
        #get_response其实就是我们的view function
        self.get_response = get_response

2.__call__(self, resquest):

def __call__(self, request):
    # 在这里编写执行view视图需要处理的代码    
    response = self.get_response(request)
  # 在这里编写执行view视图后需要处理的代码
    return response

ps: response = self.get_response(request) 必须要有
settings.py中添加自定义的中间件:

添加

# -*- coding: utf-8 -*-
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponseServerError
#原生中间件
class MiddleWareTest:
    def __init__(self,get_response):
        #get_response其实就是我们的view function
        self.get_response = get_response
        self.count = {}
    def __call__(self,request):
        print('view执行之前')
        # 在这里编写执行view视图需要处理的代码
        # 防止访问频率过高
        #有些网站服务器会使用ngix等代理http,或者是该网站做了负载均衡,导致使用remote_addr抓取到的是1270
        #这时使用HTTP_X_FORWARDED_FOR才获得是用户的真实IP。推荐使用以下代码:
        if 'HTTP_X_FORWARDED_FOR' in request.META:
            ip = request.META['HTTP_X_FORWARDED_FOR']
            print(ip)
        else:
            #使用django来获取用户访问的IP地址,如果用户是正常情况下
            ip = request.META['REMOTE_ADDR']

        count = self.count.get(ip,0) #不存在则返回自定的0,存在则返回ip对应的值;

        if count and count > 3:
            #其实就是返回403拒绝访问
            return HttpResponseForbidden
        else:
            count += 1
            self.count[ip] = count
            #这段代码其实就是执行我们的view视图
            response = self.get_response(request)

            # 这里编写你需要的请求之后的处理
            print('view执行之后')
            return response
#中间件除了类的方法,还可以用function
  • 在调用视图前,他有以下函数
    process_view(request, view_func, view_args, view_kwargs): return None or HttpResponse
  • 调用视图之后
    process_template_response(request, response): return 实现了render方法的HttpResponse
    如果需要调用这个函数,返回的response必须包含render方法,一般该函数做模版内容处理
    process_exception(request, exception): return None or HttpResponse
    该请求在报出异常时,执行该方法
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponseServerError
from django.utils.deprecation import MiddlewareMixin
class MiddleWareTest(MiddlewareMixin):
    def process_view(self, request, view_func, view_args, view_kwargs):
        """
            @request, 当前请求对象
            @view_func, 就是我们需要执行的view_func方法,
            @view_args, 就是我们的view_func所带的位置参数
            @view_kwargs, 就是我们的view_func所带的键值参数
        """
        print('process_view')
        response = view_func(request,*view_args,**view_kwargs)
        # 这个方法如果返回的是一个response, 它就提前结束请求
        return response

    def process_template_response(request, response):

        print('process_template_response')
        return response

    def process_exception(self, request, exception):
        # 这个异常必须是在执行view中出现的异常,它才能进入这个里面

        print('----exception----------')

中间件开发准则

  • 不能继承自任何类(可以继承 object )
  • 中间件可以存在于你Python 路径中的任何位置。 Django所关心的只是被包含在MIDDLEWARE中的配置。
  • 将Django 中可用的中间件作为例子随便看看。https://docs.djangoproject.com/en/1.11/ref/middleware/
  • 如果你认为你写的中间件组建可能会对其他人有用,那就把它共享到社区! 让我们知道它,我们会考虑把它添加到Django中
  • 中间件一般是实现一个系统级别的应用或者面向整个工程应用,所以一般情况下,中间件不能有业务代码

使用中间件开发流量统计

  1. 添加一个middleware类
    你创建的类原则上是可以放置在任何路径,只要你能够引用,如果你要写自己的中间件,我们一般会在某个app下添加一个middleware文件或者创建一个文件夹,专门存储中间件文件

'''
流量统计
'''
class StatFlowMiddleware(object):
    def __init__(self,get_response):
        self.get_response = get_response
        self.filename = 'count.txt'
    def __call__(self, request):
        # 获取IP
        if 'HTTP_X_FORWARDED_FOR' in request.META:
            ip = request.META['HTTP_X_FORWARDED_FOR']
        else:
            ip = request.META['REMOTE_ADDR']
        with open(self.filename,'a+') as f:
            f.seek(0)
            content = f.read()
        if content:
            content = json.loads(content) # 反序列化,将json格式转换为python对象
            count = content.get(ip)
            if count > 5:
                return HttpResponseForbidden()
            # 这段代码其实就是执行我们的view视图
            response = self.get_response(request)
            # 只有这个请求返回为200,才代表这个请求需要被统计
            if response.status_code == 200:
                if content:
                    content[ip] = content[ip] + 1
                else:
                    content = {ip: 1}
                with open(self.filename, "w+") as f:
                    f.write(json.dumps(content)) #序列化,python对象转化为json格式
            return response
  1. 添加至settings.py
    image.png

Django上下文渲染器

有时候我们想让一些内容在多个模板中都要有,比如导航内容,我们又不想每个视图函数都写一次这些变量内容,怎么办呢?这时候就可以用 Django 上下文渲染器来解决。

  • 内置的上下文渲染器
    image.png
django.template.context_processors.debug: 测试模块
debug - True。你可以在模板中用它测试是否在DEBUG 模式。
sql_queries –  一个{'sql': ..., 'time': ...} 字典的列表,表示请求期间到目前为止发生的每个SQL 查询及花费的时间。这个列表按查询的顺序排序,并直到访问时才生成。
django.template.context_processors.request: 处理请求模块
requset - 表示当前的HttpRequest。
django.contrib.auth.context_processors.auth: django内置的用户登录管理
user – 一个 auth.User实例代表当前登录的用户 (或者 一个 AnonymousUser 实例, 如果用户没有登录).
perms – 一个 django.contrib.auth.context_processors.PermWrapper实例, 代表当前登录用户所拥有的权限.
django.contrib.messages.context_processors.messages:
messages – 通过消息框架设置的消息(字符串形式)列表。
DEFAULT_MESSAGE_LEVELS – 消息等级名称到它们数值 的映射。
学以致用
  1. 使用中间件完成数据统计,每个IP只统计一次,看看每分钟有多少个IP访问当前网站。
  • 开发中间件middleware.py
import datetime
'''
使用中间件完成数据统计,每个IP只统计一次,看看每分钟有多少个IP访问当前网站;
1.获取访问ip,然后写入到文件里面去;
如果文件里面存在,那么不写入;
'''
class StatisticsIpware:
    startTime = datetime.datetime.now()
    file = 'count.txt'
    count = 0
    def __init__(self,get_response):
        self.get_response = get_response
    def __call__(self, request):
        #获取ip
        if 'HTTP_X_FORWARDED_FOR' in request.META:
            ip = request.META['HTTP_X_FORWARDED_FOR']
        else:
            ip = request.META['REMOTE_ADDR']
        with open(StatisticsIpware.file,'a+') as f: #a+ 若文件存在,则文件指针将位于结尾,若该文件不存在,创建新文件用于读写。
            f.seek(0)  #将指针移动到开头,以便read从开头到末尾读取
            content = f.read()
        # 这段代码其实就是执行我们的view视图
        response = self.get_response(request)
        #执行view后执行
        self.visiTime = datetime.datetime.now()
        spaceTime = (self.visiTime - StatisticsIpware.startTime).seconds #利用求出时间差,seconds获取秒
        if response.status_code == 200:
            if spaceTime <= 30: #如果时间差小于1分钟
                print(ip)
                if ip not in content: #如果不存在ip
                    with open(StatisticsIpware.file,'a+') as f:
                        f.write('{},'.format(ip)) #则写入
                        StatisticsIpware.count += 1
            else:
                print('当前访问量1分钟{}'.format(StatisticsIpware.count))
        return response
  • settings.py配置中间件
    image.png
  1. 使用上下文渲染器完成首页菜单栏
  • 定义上下文渲染器context_processors.py
# -*- coding: utf-8 -*-
def menu_bar(request):
    return {'first':u'首页','second':u'基础教程','third':u'项目实战','fourth':u'每日一练'}

def my_ip(request):
    # 获取ip
    if 'HTTP_X_FORWARDED_FOR'  in request.META:
        ip = request.META['HTTP_X_FORWARDED_FOR']
    else:
        ip = request.META['REMOTE_ADDR']
    return {'IP':ip}
  • 上下文渲染器 加入到settings.py 中:
    image.png
  • 页面模板:
    基础模板menu_base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}菜单模板{% endblock %}</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        #menu_div{
            width: 100%;
            height: 50px;
            background: rgba(0,0,0,0.8);
        }
        #ip_div{
            position: absolute;
            bottom: 30px;
            color: indianred;
        }
        .menu_bar{
            list-style: none;
            margin-left: 60px;
        }
        li{
            float: left;
            padding-left: 60px;
            color: white;
            line-height: 50px;
            text-align: center;
        }
    </style>
</head>
<body>
<div id="menu_div">
    <ul class="menu_bar">
        <li>{% block li_first%}{% endblock %}</li>
        <li>{% block li_second%}{% endblock %}</li>
        <li>{% block li_third%}{% endblock %}</li>
        <li>{% block li_fourth%}{% endblock %}</li>
    </ul>
</div>
<div id="ip_div">访问ip:<span>{% block ip%}{% endblock %}</span></div>
</body>
</html>

首页indexpage.html

{% extends "menu_base.html" %}
{% block title %}首页{% endblock %}
{% block li_first%}{{first}}{% endblock %}
{% block li_second%}{{second}}{% endblock %}
{% block li_third%}{{third}}{% endblock %}
{% block li_fourth%}{{fourth}}{% endblock %}
{% block ip %}{{IP}}{% endblock %}

主页·homepage.html

{% extends "menu_base.html" %}
{% block title %}主页{% endblock %}
{% block li_first%}{{first}}{% endblock %}
{% block li_second%}{{second}}{% endblock %}
{% block li_third%}{{third}}{% endblock %}
{% block li_fourth%}{{fourth}}{% endblock %}
{% block ip %}{{IP}}{% endblock %}

效果:
image.png

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容