Django中间件
中间件是面向切面编程的好例子,它是一个可以介入Django的request和response处理过程的钩子框架,一个轻量级、底层的“插件”系统,用于在全局修改Django的输入或输出。
要使用中间件,首先要在settings中设置:
1 2 3 4 5 6 7 8 9 | 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' , ] |
上述是Django项目的默认设置,每一项字符串都代表一个中间件。中间件就像洋葱一样,每个中间件都是一个层,在用户请求阶段,调用定义的view函数之前,请求以自上而下的顺序通过所有的层,view函数处理之后,响应以自下而上的顺序通过所有的层,期间经过的每个中间件都会对请求或者响应进行处理。
自定义中间件
要自定义中间件,写一个类即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class SimpleMiddleware( object ): def __init__( self , get_response): self .get_response = get_response # One-time configuration and initialization. def __call__( self , request): # Code to be executed for each request before # the view (and later middleware) are called. response = self .get_response(request) # Code to be executed for each request/response after # the view is called. return response |
__init__初始化方法只在web服务启动时调用一次,get_response参数是必需的,这个参数指的是下一个中间件或者view函数(如果是最后一个中间件)。
每个请求都会调用一次__call__,先对请求做处理,然后将请求发往下一个中间件(get_response),一层层的迭代,就像栈一样,返回时对响应做处理。
如果其中一个层的中间件决定短路并返回响应而不调用其get_response,那么该层下面的层都不会看到请求或响应,只有该中间件和之上的中间件才会看到响应。
短路可以起到一些ip过滤之类的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class IpFilterMiddleware( object ): def __init__( self , get_response): self .get_response = get_response # One-time configuration and initialization. def __call__( self , request): if request.META.has_key( 'HTTP_X_FORWARDED_FOR' ): ip = request.META[ 'HTTP_X_FORWARDED_FOR' ] else : ip = request.META[ 'REMOTE_ADDR' ] if ip = = '127.0.0.1' : return HttpResponse( 'You are forbidden' ) response = self .get_response(request) return response |
设置:
1 2 | MIDDLEWARE = [ 'path.to.IpFilterMiddleware' , |
上述中间件把当地访问拦截了,实际应用中可以为ip建立黑名单,然后检查访问ip是否在黑名单中。
我们也可以将中间件标记为未使用,只要在__init__中引发一个django.core.exceptions.MiddlewareNotUsed异常即可。
其它的中间件钩子
除了前面描述的基本请求/响应中间件模式,您还可以向基于类的中间件添加三种其他特殊方法:
process_view(request, view_func, view_args, view_kwargs)
在调用view之前被调用。
它应该返回一个None 或一个HttpResponse对象。 如果返回None,Django 将会继续处理这个请求,执行其它的process_view() 中间件,然后调用对应的视图。 如果它返回一个HttpResponse对象,也会起到短路作用。
process_exception(request, exception)
当一个view引发异常时,Django会调用process_exception()来处理。返回一个None或一个HttpResponse对象。如果返回HttpResponse对象,会将响应交给处理响应的中间件处理。由于处理响应时是从下到上的,此层以上的process_exception()是不会被调用的。
process_template_response(request, response)
response参数应该是一个由view或者中间件返回的TemplateResponse对像(或等价的对象)。
如果响应的实例有render()方法,process_template_response()会在view刚好执行完毕之后被调用。这个方法必须返回一个实现了render方法的响应对象。
开发阶段通常将DEBUG打开以调试错误,部署时会关闭DEBUG以免用户看到错误信息。我们可以写一个异常中间件,让有权限的管理员可以在DEBUG关闭的情况下看到错误信息,这样就避免了切换的麻烦;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import sys from django.views.debug import technical_500_response class ExceptionMiddleware( object ): def __init__( self , get_response): self .get_response = get_response # One-time configuration and initialization. def __call__( self , request): response = self .get_response(request) return response def process_exception( self , request, exception): if request.user.is_admin: return technical_500_response(request, * sys.exc_info()) |
置于中间件的最底层,这样管理员可以看到用户无法看到的错误详细信息。
关于异常处理
Django会自动将视图或中间件引发的异常转换为具有错误状态代码的适当HTTP响应,这种转换发生在每个中间件之前和之后,我们是无需使用try捕捉异常的。
与旧版本中间件的兼容
在Django 1.10版本之前,中间件设置名为MIDDLEWARE_CLASSES,是长这样的:
1 2 3 4 5 6 7 8 9 10 11 | class SessionMiddleware( object ): def __init__( self ): engine = import_module(settings.SESSION_ENGINE) self .SessionStore = engine.SessionStore def process_request( self , request): session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) request.session = self .SessionStore(session_key) def process_response( self , request, response): ... |
Django提供django.utils.deprecation.MiddlewareMixin以简化与MIDDLEWARE和旧的MIDDLEWARE_CLASSES兼容的中间件类。 Django中包含的所有中间件类都兼容这两种设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class MiddlewareMixin( object ): def __init__( self , get_response = None ): self .get_response = get_response super (MiddlewareMixin, self ).__init__() def __call__( self , request): response = None if hasattr ( self , 'process_request' ): response = self .process_request(request) if not response: response = self .get_response(request) if hasattr ( self , 'process_response' ): response = self .process_response(request, response) return response |
新版继承自MiddlewareMixin的SessionMiddleware:
1 2 3 4 5 6 7 8 9 10 11 12 | class SessionMiddleware(MiddlewareMixin): def __init__( self , get_response = None ): self .get_response = get_response engine = import_module(settings.SESSION_ENGINE) self .SessionStore = engine.SessionStore def process_request( self , request): session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) request.session = self .SessionStore(session_key) def process_response( self , request, response): ... |
混合类提供一个__init__()方法,它接受一个可选的get_response参数,并将其存储在self.get_response中。
__call__()方法:
- 调用self.process_request(request)(已定义的话)。
- 调用self.get_response(request)获取后续中间件或者视图的响应。
- 调用self.process_response(request, response)(已定义的话)。
- 返回响应。
在大多数情况下,继承这种混合类足以使旧式中间件与新系统兼容,具有足够的向后兼容性。
上述的异常中间件也可以写为:
1 2 3 4 5 6 | from django.utils.deprecation import MiddlewareMixin class ExceptionMiddleware(MiddlewareMixin): def process_exception( self , request, exception): if request.user.is_admin: return technical_500_response(request, * sys.exc_info()) |
使用MIDDLEWARE和MIDDLEWARE_CLASSES之间的行为差异:
- 在MIDDLEWARE_CLASSES下,即使上层中间件的process_request方法短路,下层的中间件也会调用它的process_response方法。 在MIDDLEWARE下, 如果中间件发生短路,则只有中间件和之上的中间件才会看到响应。
- 在MIDDLEWARE_CLASSES下,process_exception适用于从中间件process_request方法引发的异常。 在MIDDLEWARE下,process_exception仅适用于view或者TemplateResponse的render方法引发的异常 。 从中间件引发的异常转换为适当的HTTP响应,然后传递给下一个中间件。
- 在MIDDLEWARE_CLASSES下,如果process_response方法引发异常,则会跳过所有较早中间件的process_response方法然后返回500错误,而在MIDDLEWARE下,从中间件引发的异常将立即转换为适当的HTTP响应,然后下一个中间件将会看到该响应。 由于中间件引发异常,中间件不会被跳过。
处理流式响应
StreamingHttpResponse并没有content属性,使用时必须加以判断:
1 2 3 4 | if response.streaming: response.streaming_content = wrap_streaming_content(response.streaming_content) else : response.content = alter_content(response.content) |
用生成器处理过大的内容:
1 2 3 | def wrap_streaming_content(content): for chunk in content: yield alter_content(chunk) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探