zoukankan      html  css  js  c++  java
  • Django之锁和事务,中间件

    一 锁

    行级锁

    select_for_update(nowait=False, skip_locked=False) #注意必须用在事务里面,至于如何开启事务,看下面的事务一节。

    返回一个锁住行直到事务结束的查询集,如果数据库支持,它将生成一个 SELECT ... FOR UPDATE 语句。

    举个例子:

    entries = Entry.objects.select_for_update().filter(author=request.user)  #加互斥锁,由于mysql在查询时自动加的是共享锁,所以我们可以手动加上互斥锁。create、update、delete操作时,mysql自动加行级互斥锁

    所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。

    一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞,直到锁被释放。 如果这不想要使查询阻塞的话,使用select_for_update(nowait=True)。 如果其它事务持有冲突的锁,互斥锁, 那么查询将引发 DatabaseError 异常。你也可以使用select_for_update(skip_locked=True)忽略锁定的行。 nowait和  skip_locked是互斥的,同时设置会导致ValueError。

    目前,postgresql,oracle和mysql数据库后端支持select_for_update()。 但是,MySQL不支持nowait和skip_locked参数。

    使用不支持这些选项的数据库后端(如MySQL)将nowait=True或skip_locked=True转换为select_for_update()将导致抛出DatabaseError异常,这可以防止代码意外终止。

    表锁

    class LockingManager(models.Manager):
        """ Add lock/unlock functionality to manager.
    
        Example::
    
            class Job(models.Model): #其实不用这么负载,直接在orm创建表的时候,给这个表定义一个lock和unlock方法,借助django提供的connection模块来发送锁表的原生sql语句和解锁的原生sql语句就可以了,不用外层的这个LckingManager(model.Manager)类
    
                manager = LockingManager()
    
                counter = models.IntegerField(null=True, default=0)
    
                @staticmethod
                def do_atomic_update(job_id)
                    ''' Updates job integer, keeping it below 5 '''
                    try:
                        # Ensure only one HTTP request can do this update at once.
                        Job.objects.lock()
    
                        job = Job.object.get(id=job_id)
                        # If we don't lock the tables two simultanous
                        # requests might both increase the counter
                        # going over 5
                        if job.counter < 5:
                            job.counter += 1                                        
                            job.save()
    
                    finally:
                        Job.objects.unlock()
    
    
        """    
    
        def lock(self):
            """ Lock table. 
    
            Locks the object model table so that atomic update is possible.
            Simulatenous database access request pend until the lock is unlock()'ed.
    
            Note: If you need to lock multiple tables, you need to do lock them
            all in one SQL clause and this function is not enough. To avoid
            dead lock, all tables must be locked in the same order.
    
            See http://dev.mysql.com/doc/refman/5.0/en/lock-tables.html
            """
            cursor = connection.cursor()
            table = self.model._meta.db_table
            logger.debug("Locking table %s" % table)
            cursor.execute("LOCK TABLES %s WRITE" % table)
            row = cursor.fetchone()
            return row
    
        def unlock(self):
            """ Unlock the table. """
            cursor = connection.cursor()
            table = self.model._meta.db_table
            cursor.execute("UNLOCK TABLES")
            row = cursor.fetchone()
            return row  
    View Code

    二 事务

    关于MySQL的事务处理,mysql博客说的很清楚了,那么Django是如果做事务处理的。django1.8版本之前是有很多种添加事务的方式的,中间件的形式(全局的)、函数装饰器的形式,上下文管理器的形式等,但是很多方法都在1.8版之后给更新了,下面只说最新的:

    1 全局开启

    在Web应用中,常用的事务处理方式是将每个请求都包裹在一个事务中。这个功能使用起来非常简单,只需要将它的配置项ATOMIC_REQUESTS设置为True。

    它是这样工作的:当有请求过来时,Django会在调用视图方法前开启一个事务。如果请求却正确处理并正确返回了结果,Django就会提交该事务。否则,Django会回滚该事务。

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'mxshop',
            'HOST': '127.0.0.1',
            'PORT': '3306',
            'USER': 'root',
            'PASSWORD': '123',
            'OPTIONS': {
                "init_command": "SET default_storage_engine='INNODB'",
           #'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", #配置开启严格sql模式
    
    
            }
            "ATOMIC_REQUESTS": True, #全局开启事务,绑定的是http请求响应整个过程        "AUTOCOMMIT":False, #全局取消自动提交,慎用
        },  'other':{    'ENGINE': 'django.db.backends.mysql',             ......  } #还可以配置其他数据库
    }
    View Code

    上面这种方式是统一个http请求对应的所有sql都放在一个事务中执行(要么所有都成功,要么所有都失败)。是全局性的配置, 如果要对某个http请求放水(然后自定义事务),可以用non_atomic_requests修饰器,那么他就不受事务的管控了

    from django.db import transaction
    
    @transaction.non_atomic_requests
    def my_view(request):
        do_stuff()
    
    @transaction.non_atomic_requests(using='other')
    def my_other_view(request):
        do_stuff_on_the_other_database()
    View Code

    但是Django 文档中说,不推荐这么做。因为如果将事务跟 HTTP 请求绑定到一起的时,然而view 是依赖于应用程序对数据库的查询语句效率和数据库当前的锁竞争情况。当流量上来的时候,性能会有影响。

    所以推荐用下面这种方式,通过 transaction.atomic 来更加明确的控制事务。atomic允许在执行代码块时,在数据库层面提供原子性保证。 如果代码块成功完成, 相应的变化会被提交到数据库进行commit;如果执行期间遇到异常,则会将该段代码所涉及的所有更改回滚。

    2 局部使用事务

    atomic(using=None, savepoint=True)[source] ,参数:using='other',就是当操作其他数据库的时候,这个事务才生效,看上面的数据库配置,除了default,还有一个other,默认的是default。savepoint的意思是开启事务保存点,推荐看一下数据库里面的事务部分关于保存点的解释。

    原子性是数据库事务的一个属性。使用atomic,就可以创建一个具备原子性的代码块。一旦代码块正常运行完毕,所有的修改会被提交到数据库。反之,如果有异常,更改会被回滚。

    被atomic管理起来的代码块还可以内嵌到方法中。这样的话,即便内部代码块正常运行,如果外部代码块抛出异常的话,它也没有办法把它的修改提交到数据库中。

    用法1:给函数做装饰器来使用 

    from django.db import transaction
    
    @transaction.atomic
    def viewfunc(request):
        # This code executes inside a transaction.
        do_stuff()
    View Code

    用法2:作为上下文管理器来使用,其实就是设置事务的保存点

    from django.db import transaction
    
    def viewfunc(request):
        # This code executes in autocommit mode (Django's default).
        do_stuff()
    
        with transaction.atomic():   #保存点
            # This code executes inside a transaction.
            do_more_stuff()
    
        do_other_stuff()
    View Code

    一旦把atomic代码块放到try/except中,完整性错误就会被自然的处理掉了,比如下面这个例子:

    from django.db import IntegrityError, transaction
    
    @transaction.atomic
    def viewfunc(request):
        create_parent()
    
        try:
            with transaction.atomic():
                generate_relationships()
        except IntegrityError:
            handle_exception()
    
        add_children()
    View Code

    用法3:还可以嵌套使用,函数的事务嵌套上下文管理器的事务,上下文管理器的事务嵌套上下文管理器的事务等。下面的是函数嵌套上下文的例子:

    from django.db import IntegrityError, transaction
    
    @transaction.atomic
    def viewfunc(request):
        create_parent()
    
        try:
            with transaction.atomic():
                generate_relationships()       #other_task()  #还要注意一点,如果在事务里面写了别的操作,只有这些操作全部完成之后,事务才会commit,也就是说,如果这个任务是查询上面更改的数据表里面的数据,那么看到的还是事务提交之前的数据。
        except IntegrityError:
            handle_exception()
    
        add_children()
    View Code

    这个例子中,即使generate_relationships()中的代码打破了数据完整性约束,仍然可以在add_children()中执行数据库操作,并且create_parent()产生的更改也有效。需要注意的是,在调用handle_exception()之前,generate_relationships()中的修改就已经被安全的回滚了。因此,如果有需要,照样可以在异常处理函数中操作数据库。

    因为当atomic块中的代码执行完的时候,Django会根据代码正常运行来执行相应的提交或者回滚操作。如果在atomic代码块里面捕捉并处理了异常,就有可能隐盖代码本身的错误,从而可能会有一些意料之外的不愉快事情发生。
    
      担心主要集中在DatabaseError和它的子类(如IntegrityError)。如果这种异常真的发生了,事务就会被破坏掉,而Django会在代码运行完后执行回滚操作。如果试图在回滚前执行一些数据库操作,Django会抛出TransactionManagementError。通常会在一个ORM相关的信号处理器抛出异常时遇到这个行为。
    
    捕获异常的正确方式正如上面atomic代码块所示。如果有必要,添加额外的atomic代码块来做这件事情,也就是事务嵌套。这么做的好处是:当异常发生时,它能明确地告诉你那些操作需要回滚,而那些是不需要的。
    尽量不要在atomic代码块中捕获异常

    为了保证原子性,atomic还禁止了一些API。像试图提交、回滚事务,以及改变数据库连接的自动提交状态这些操作,在atomic代码块中都是不予许的,否则就会抛出异常。

      下面是Django的事务管理代码:

    • 进入最外层atomic代码块时开启一个事务;

    • 进入内部atomic代码块时创建保存点;

    • 退出内部atomic时释放或回滚事务;注意如果有嵌套,内层的事务也是不会提交的,可以释放(正常结束)或者回滚

    • 退出最外层atomic代码块时提交或者回滚事务;

    可以将保存点参数设置成False来禁止内部代码块创建保存点。如果发生了异常,Django在退出第一个父块的时候执行回滚,如果存在保存点,将回滚到这个保存点的位置,否则就是回滚到最外层的代码块。外层事务仍然能够保证原子性。然而,这个选项应该仅仅用于保存点开销较大的时候。毕竟它有个缺点:会破坏上文描述的错误处理机制。

    注意:transaction只对数据库层的操作进行事务管理,不能理解为python操作的事务管理

    def example_view(request):
        tag = False
        with transaction.atomic():
            tag = True
            change_obj() # 修改对象变量
            obj.save()
            raise DataError
        print("tag = ",tag) #结果是True,也就是说在事务中的python变量赋值,即便是事务回滚了,这个赋值也是成功的
    View Code

    还要注意:如果配置了全局的事务,它和局部事务可能会产生冲突,可能会发现你局部的事务完成之后,如果函数里面其他的sql除了问题,也就是没在这个上下文管理器的局部事务包裹范围内的函数里面的其他的sql出现了问题,局部事务也是提交不上的,因为全局会回滚这个请求和响应所涉及到的所有的sql,所以还是建议以后的项目尽量不要配置全局的事务,通过局部事务来搞定,当然了,看业务场景。

    transaction的其他方法

    @transaction.atomic
    def viewfunc(request):
    
      a.save()
      # open transaction now contains a.save()
      sid = transaction.savepoint()  #创建保存点
    
      b.save()
      # open transaction now contains a.save() and b.save()
    
      if want_to_keep_b:
          transaction.savepoint_commit(sid) #提交保存点
          # open transaction still contains a.save() and b.save()
      else:
          transaction.savepoint_rollback(sid)  #回滚保存点
          # open transaction now contains only a.save()
    
      transaction.commit() #手动提交事务,默认是自动提交的,也就是说如果没有设置取消自动提交,那么这句话不用写,如果配置了那个AUTOCOMMIT=False,那么就需要自己手动进行提交。
    View Code

    为保证事务的隔离性,还可以结合上面的锁来实现,也就是说在事务里面的查询语句,使用select_for_update显示的加锁方式来保证隔离性,事务结束后才会释放这个锁,例如:

    @transaction.atomic ## 轻松开启事务
    def handle(self):
        ## 测试是否存在此用户
        try:
            ## 锁定被查询行直到事务结束
            user = 
        User.objects.select_for_update().get(open_id=self.user.open_id)
            #other sql 语句
        except User.DoesNotExist:
            raise BaseError(-1, 'User does not exist.')
        
    View Code

    通过Django外部的python脚本来测试一下事务:

    import os
    
    if __name__ == '__main__':
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings")
        import django
        django.setup()
    
        import datetime
        from app01 import models
    
        try:
            from django.db import transaction
            with transaction.atomic():
                new_publisher = models.Publisher.objects.create(name="火星出版社")
                models.Book.objects.create(title="橘子物语", publish_date=datetime.date.today(), publisher_id=10)  # 指定一个不存在的出版社id
        except Exception as e:
            print(str(e))
    View Code

    一些设置事务的小原则:

        1.保持事务短小     2.尽量避免事务中rollback     3.尽量避免savepoint     4.默认情况下,依赖于悲观锁     5.为吞吐量要求苛刻的事务考虑乐观锁     6.显示声明打开事务     7.锁的行越少越好,锁的时间越短越好

    中间件

    一 前戏

    给视图函数加装饰器来判断是用户是否登录,把没有登录的用户请求跳转到登录页面。通过给几个特定视图函数加装饰器实现了这个需求。但是以后添加的视图函数可能也需要加上装饰器,这样是不是稍微有点繁琐。

    使用中间件,就可以用更适宜的方式来实现类似给所有请求都做相同操作的功能了

    二 中间件介绍

    中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。

    Django的中间件的定义:

    Middleware is a framework of hooks into Django’s request/response processing. <br>It’s a light, low-level “plugin” system for globally altering Django’s input or output.

    如果修改请求,例如被传送到view中的HttpRequest对象。 或者想修改view返回的HttpResponse对象,这些都可以通过中间件来实现。

    可能还想在view执行之前做一些操作,这种情况就可以用 middleware来实现。

    说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。

    一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下面的MIDDLEWARE配置项,django默认自带的一些中间件:

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    View Code

    MIDDLEWARE配置项是一个列表,列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。

    已经接触过一个csrf相关的中间件,一开始让把他注释掉,再提交post请求的时候,就不会被forbidden了,后来学会使用csrf_token之后就不再注释这个中间件了。

        

    三 自定义中间件

    想多了解,在开源中国中有很多关于中间件的详细解释

    中间件可以定义五个方法,分别是:(主要的是process_request和process_response)

    • process_request(self,request)

    • process_view(self, request, view_func, view_args, view_kwargs)

    • process_template_response(self,request,response)

    • process_exception(self, request, exception)

    • process_response(self, request, response)

    以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户。

    当用户发起请求的时候会依次经过所有的的中间件,这个时候的请求时process_request,最后到达views的函数中,views函数处理后,在依次穿过中间件,这个时候是process_response,最后返回给请求者。

          

    上述截图中的中间件都是django中的,也可以自己定义一个中间件,可以自己写一个类,但是必须继承MiddlewareMixin

    自定义一个中间件示例

    目录:在项目中创建一个包,随便起名字,一般都放在一个叫做utils的包里面,表示一个公用的组件,创建一个py文件,随便起名字,例如叫做:middlewares.py,内容如下

    from django.utils.deprecation import MiddlewareMixin
    
    class MD1(MiddlewareMixin):
        #自定义中间件,不是必须要有下面这两个方法,有request方法说明请求来了要处理,有response方法说明响应出去时需要处理,不是非要写这两个方法,如果没写process_response方法,那么会一层一层的往上找,哪个中间件有process_response方法就将返回对象给哪个中间件
        def process_request(self, request):
            print("MD1里面的 process_request")
    
        def process_response(self, request, response):
            print("MD1里面的 process_response")
            return response       
    View Code

    process_request

    process_request有一个参数,就是request,这个request和视图函数中的request是一样的。

    它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。

    多个中间件时,Django是如何执行其中的process_request方法的。

    from django.utils.deprecation import MiddlewareMixin
    
    
    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1里面的 process_request")
    
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2里面的 process_request")
            pass
    View Code

    在settings.py的MIDDLEWARE配置项中注册上述两个自定义中间件:

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'middlewares.MD1',  # 自定义中间件MD1,这个写的是你项目路径下的一个路径,例如,如果放在项目下,文件夹名成为utils,那么这里应该写utils.middlewares.MD1
        'middlewares.MD2'  # 自定义中间件MD2
    ]
    View Code

    此时,访问一个视图,会发现终端中打印如下内容:

    MD1里面的 process_request
    MD2里面的 process_requestapp01 中的 index视图

    把MD1和MD2的位置调换一下,再访问一个视图,会发现终端中打印的内容如下:

    MD2里面的 process_request
    MD1里面的 process_requestapp01 中的 index视图

    看结果知道:视图函数还是最后执行的,MD2比MD1先执行自己的process_request方法。

    在打印一下两个自定义中间件中process_request方法中的request参数,会发现它们是同一个对象。

    由此总结一下:中间件的process_request方法是在执行视图函数之前执行的。

    当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。

    不同中间件之间传递的request都是同一个对象

    多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。

    process_response

    它有两个参数,一个是request,一个是response,request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象。该方法的返回值也必须是HttpResponse对象。

    给上述的M1和M2加上process_response方法:

    from django.utils.deprecation import MiddlewareMixin
    
    
    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1里面的 process_request")
            #不必须写return值
        def process_response(self, request, response):#request和response两个参数必须有,名字随便取
            print("MD1里面的 process_response")        #print(response.__dict__['_container'][0].decode('utf-8')) #查看响应体里面的内容的方法,或者直接使用response.content也可以看到响应体里面的内容,由于response是个变量,直接点击看源码是看不到的,打印type(response)发现是HttpResponse对象,查看这个对象的源码就知道有什么方法可以用了。
         return response  #必须有返回值,写return response  ,这个response就像一个接力棒一样
            #return HttpResponse('瞎搞') ,如果写了这个,那么视图返回过来的内容就被它给替代了
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2里面的 process_request")
            pass
    
        def process_response(self, request, response): #request和response两个参数必须要有,名字随便取
            print("MD2里面的 process_response") 
            return response  #必须返回response,不然上层的中间件就没有拿到httpresponse对象,就会报错
    View Code

    访问一个视图,看一下终端的输出:

    MD2里面的 process_request
    MD1里面的 process_request
    app01 中的 index视图
    MD1里面的 process_response
    MD2里面的 process_response

    看结果可知:

    process_response方法是在视图函数之后执行的,并且顺序是MD1比MD2先执行。(此时settings.py中 MD2比MD1先注册)

    多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。

    再看一个例子:

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse
    
    class Md1(MiddlewareMixin):
    
        def process_request(self,request):
            print("Md1请求")     #process_request方法里面不写返回值,默认也是返回None,如果你自己写了return None,也是一样的效果,不会中断你的请求,但是如果你return 的一个httpresponse对象,那么就会在这个方法中断你的请求,直接返回给用户,这就成了非正常的流程了        #并且,如果你在这里return了httpresponse对象,那么会从你这个中间件类中的process_response方法开始执行返回操作,所以这个类里面只要有process_response方法,肯定会执行
     
        def process_response(self,request,response):
            print("Md1返回")
            return response
    
    class Md2(MiddlewareMixin):
    
        def process_request(self,request):
            print("Md2请求")
            #return HttpResponse("Md2中断")
        def process_response(self,request,response):
            print("Md2返回")
            return response
    View Code

    结果:

    Md1请求
    Md2请求
    view函数...
    Md2返回
    Md1返回
    View Code

    注意:如果当请求到达请求2的时候直接不符合条件返回,即return HttpResponse("Md2中断"),程序将把请求直接发给中间件2返回,然后依次返回到请求者,结果如下:

    返回Md2中断的页面,后台打印如下:

    Md1请求
    Md2请求
    Md2返回
    Md1返回

    流程图如下:

        

    之前做的cookie认证,都是通过在函数上面加装饰器搞的,比较麻烦,看看中间件怎么搞,如果写的是session认证的,必须放在django自带的session中间件的下面,所以自定义中间之后,需要注意中间件的摆放顺序。

    class M1(MiddlewareMixin):
    
        def process_request(self,request):
    
            #设置路径白名单,只要访问的是login登陆路径,就不做这个cookie认证
            if request.path not in [reverse('login'),]:
                print('我是M1中间件') #客户端IP地址
                # return HttpResponse('sorry,没有通过我的M1中间件')
                is_login = request.COOKIES.get('is_login', False)
    
                if is_login:
                    pass
                else:
                    # return render(request,'login.html')
                    return redirect(reverse('login'))
            else:
                return None #别忘了return None,或者直接写个pass
    
        def process_response(self,request,response):
    
            print('M1响应部分')
            # print(response.__dict__['_container'][0].decode('utf-8'))
            return response
            # return HttpResponse('瞎搞')
    View Code

    尝试一下通过中间件来控制用户的访问次数,让用户在一分钟之内不能访问我的网站超过20次。   

    process_view

    process_view(self, request, view_func, view_args, view_kwargs)

    该方法有四个参数

    request是HttpRequest对象。

    view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)

    view_args是将传递给视图的位置参数的列表.

    view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。

    Django会在调用视图函数之前调用process_view方法。

    它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,Django不会调用对应的视图函数。 它将执行中间件的process_response方法并将应用到该HttpResponse并返回结果。

            

    给MD1和MD2添加process_view方法:

    from django.utils.deprecation import MiddlewareMixin
    
    
    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1里面的 process_request")
    
        def process_response(self, request, response):
            print("MD1里面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__) #就是url映射到的那个视图函数,也就是说每个中间件的这个process_view已经提前拿到了要执行的那个视图函数        #ret = view_func(request) #提前执行视图函数,不用到了上图的试图函数的位置再执行,如果视图函数有参数的话,可以这么写 view_func(request,view_args,view_kwargs) 
            #return ret  #直接就在MD1中间件这里这个类的process_response给返回了,就不会去找到视图函数里面的这个函数去执行了。
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2里面的 process_request")
            pass
    
        def process_response(self, request, response):
            print("MD2里面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD2 中的process_view")
            print(view_func, view_func.__name__)
    View Code

    访问index视图函数,看一下输出结果:

    MD2里面的 process_request
    MD1里面的 process_request
    --------------------------------------------------------------------------------
    MD2 中的process_view
    <function index at 0x000001DE68317488> index
    --------------------------------------------------------------------------------
    MD1 中的process_view
    <function index at 0x000001DE68317488> index
    app01 中的 index视图
    MD1里面的 process_response
    MD2里面的 process_response
    View Code

    process_view方法是在process_request之后,reprocess_response之前,视图函数之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的

            

     

    process_exception

    process_exception(self, request, exception)

    该方法两个参数:

    一个HttpRequest对象

    一个exception是视图函数异常产生的Exception对象。

    这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。

            

    给MD1和MD2添加上这个方法:

    from django.utils.deprecation import MiddlewareMixin
    
    
    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1里面的 process_request")
    
        def process_response(self, request, response):
            print("MD1里面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD1 中的process_exception")
    
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2里面的 process_request")
            pass
    
        def process_response(self, request, response):
            print("MD2里面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD2 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD2 中的process_exception")
    View Code

    如果视图函数中无异常,process_exception方法不执行。

    在视图函数中抛出一个异常:

    def index(request):
        print("app01 中的 index视图")
        raise ValueError("呵呵")
        return HttpResponse("O98K")

    在MD1的process_exception中返回一个响应对象:

    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1里面的 process_request")
    
        def process_response(self, request, response):
            print("MD1里面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD1 中的process_exception")
            return HttpResponse(str(exception))  # 返回一个响应对象
    View Code

    看输出结果:

    MD2里面的 process_request
    MD1里面的 process_request
    --------------------------------------------------------------------------------
    MD2 中的process_view
    <function index at 0x0000022C09727488> index
    --------------------------------------------------------------------------------
    MD1 中的process_view
    <function index at 0x0000022C09727488> index
    app01 中的 index视图
    呵呵
    MD1 中的process_exception
    MD1里面的 process_response
    MD2里面的 process_response
    View Code

           

     

    注意,这里并没有执行MD2的process_exception方法,因为MD1中的process_exception方法直接返回了一个响应对象。

    process_template_response(用的比较少)

    process_template_response(self, request, response)

    它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。

    process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。

    class MD1(MiddlewareMixin):
    
        def process_request(self, request):
            print("MD1里面的 process_request")
    
        def process_response(self, request, response):
            print("MD1里面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD1 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD1 中的process_exception")
            return HttpResponse(str(exception))
    
        def process_template_response(self, request, response):
            print("MD1 中的process_template_response")
            return response
    
    
    class MD2(MiddlewareMixin):
        def process_request(self, request):
            print("MD2里面的 process_request")
            pass
    
        def process_response(self, request, response):
            print("MD2里面的 process_response")
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("-" * 80)
            print("MD2 中的process_view")
            print(view_func, view_func.__name__)
    
        def process_exception(self, request, exception):
            print(exception)
            print("MD2 中的process_exception")
    
        def process_template_response(self, request, response):
            print("MD2 中的process_template_response")
            return response
    View Code

    views.py中

    def index(request):
        print("app01 中的 index视图")
      #raise ValueError('出错啦') 
        def render():
            print("in index/render")          #raise ValueError('出错啦') #至于render函数中报错了,那么会先执行process_template_response方法,然后执行process_exception方法,如果是在render方法外面报错了,那么就不会执行这个process_template_response方法了。
            return HttpResponse("O98K") #返回的将是这个新的对象
        rep = HttpResponse("OK")
        rep.render = render
        return rep
    View Code

    访问index视图,终端输出的结果:

    MD2里面的 process_request
    MD1里面的 process_request
    --------------------------------------------------------------------------------
    MD2 中的process_view
    <function index at 0x000001C111B97488> index
    --------------------------------------------------------------------------------
    MD1 中的process_view
    <function index at 0x000001C111B97488> index
    app01 中的 index视图
    MD2 中的process_template_response
    MD1 中的process_template_response
    in index/render
    MD1里面的 process_response
    MD2里面的 process_response
    View Code

    从结果看出:视图函数执行完之后,立即执行了中间件的process_template_response方法,顺序是倒序,先执行MD2的,在执行MD1的,接着执行了视图函数返回的HttpResponse对象的render方法,返回了一个新的HttpResponse对象,接着执行中间件的process_response方法。

    四 中间件执行流程

    上一部分,了解了中间件中的5个方法,它们的参数、返回值以及什么时候执行,现在总结一下中间件的执行流程。

    请求到达中间件之后,先按照正序执行每个注册中间件的process_reques方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法,将HttpResponse对象返回给浏览器。也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。

        

    process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,继续按顺序执行,所有process_view方法执行完后执行视图函数。加入中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,也就是中间件6的process_response方法开始倒序执行。

        

    process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。总结所有的执行流程如下:

        

     

        

     

    五 中间件版登陆认证

    中间件版的登录验证需要依靠session,所以数据库中要有django_session表。

    urls.py

    from django.conf.urls import url
    from app01 import views
    
    urlpatterns = [
        url(r'^index/$', views.index),
        url(r'^login/$', views.login, name='login'),
    ]
    View Code

    views.py

    from django.shortcuts import render, HttpResponse, redirect
    
    
    def index(request):
        return HttpResponse('this is index')
    
    
    def home(request):
        return HttpResponse('this is home')
    
    
    def login(request):
        if request.method == "POST":
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
    
            if user == "Q1mi" and pwd == "123456":
                # 设置session
                request.session["user"] = user
                # 获取跳到登陆页面之前的URL
                next_url = request.GET.get("next")
                # 如果有,就跳转回登陆之前的URL
                if next_url:
                    return redirect(next_url)
                # 否则默认跳转到index页面
                else:
                    return redirect("/index/")
        return render(request, "login.html")
    View Code

    login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="x-ua-compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>登录页面</title>
    </head>
    <body>
    <form action="{% url 'login' %}">
        <p>
            <label for="user">用户名:</label>
            <input type="text" name="user" id="user">
        </p>
        <p>
            <label for="pwd">密 码:</label>
            <input type="text" name="pwd" id="pwd">
        </p>
        <input type="submit" value="登录">
    </form>
    </body>
    </html>
    View Code

    middlewares.py

    class AuthMD(MiddlewareMixin):
        white_list = ['/login/', ]  # 白名单
        balck_list = ['/black/', ]  # 黑名单
    
        def process_request(self, request):
            from django.shortcuts import redirect, HttpResponse
    
            next_url = request.path_info
            print(request.path_info, request.get_full_path())
    
            if next_url in self.white_list or request.session.get("user"):
                return
            elif next_url in self.balck_list:
                return HttpResponse('This is an illegal URL')
            else:
                return redirect("/login/?next={}".format(next_url))
    View Code

    在settings.py中注册

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'middlewares.AuthMD',
    ]
    View Code

    AuthMD中间件注册后,所有的请求都要走AuthMD的process_request方法。

    访问的URL在白名单内或者session中有user用户名,则不做阻拦走正常流程;

    如果URL在黑名单中,则返回This is an illegal URL的字符串;

    正常的URL但是需要登录后访问,让浏览器跳转到登录页面。

    注:AuthMD中间件中需要session,所以AuthMD注册的位置要在session中间的下方。

    附:Django请求流程图

          img

    六 中间件案例

    应用案例

    1、做IP访问频率限制

    某些IP访问服务器的频率过高,进行拦截,比如限制每分钟不能超过20次。

    2、URL访问过滤

    如果用户访问的是login视图(放过)

    如果访问其他视图,需要检测是不是有session认证,已经有了放行,没有返回login,这样就省得在多个视图函数上写装饰器了!

    源码试读

    延伸扩展内容,可以尝试着读一下以下两个自带的中间件:

    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
  • 相关阅读:
    前台隐藏或者看不见
    关于线程安全
    JAVA中的length属性和length()方法和size()方法的区别
    内存泄露和内存溢出
    配置tomcat,只需要启动一次
    ThickBox关闭本页,刷新父页
    Myeclipse断点问题
    解决windows远程(Telnet)最大连接数的问题
    父页刷新的方法
    Jsp页面大小写转换
  • 原文地址:https://www.cnblogs.com/sundawei7/p/11656505.html
Copyright © 2011-2022 走看看