Before this, we already made a rule that the user must log in before voting, but now a new problem comes. If many functions in our application need the user to log in first, for example, if exporting the Excel report and viewing the statistical chart are both limited to logged-in users, do we need to add code in every view function to check whether userid is in the session? The answer is no. If we do that, our view functions will surely be full of repeated code. Programming master Martin Fowler once said: code has many bad smells, and duplication is the worst one. In Python programs, we can use decorators to give extra abilities to functions. In Django projects, we can put repeated code like checking whether the user is logged in into middleware.
Middleware is a component placed between the request process and response process of a Web application. It plays the role of an interceptor and filter in the whole Web application. Through middleware, we can intercept requests and responses, and filter them. Simply speaking, this means doing extra processing. Usually, one middleware component only focuses on one specific thing. For example, the Django framework uses SessionMiddleware to support session, and uses AuthenticationMiddleware to implement request authentication based on session. By combining multiple middleware components together, we can complete more complex tasks. This is exactly what Django does.
The configuration file of a Django project includes middleware configuration, as shown below.
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',
]Let us explain the role of these middleware a little:
CommonMiddleware- basic setting middleware. It can handle some configuration parameters below.DISALLOWED_USER_AGENTS- disallowed user agents (browsers)APPEND_SLASH- whether to append/USE_ETAG- browser cache relatedSecurityMiddleware- security-related middleware. It can handle security-related configuration items.SECURE_HSTS_SECONDS- time for forcing HTTPSSECURE_HSTS_INCLUDE_SUBDOMAINS- whether HTTPS covers subdomainsSECURE_CONTENT_TYPE_NOSNIFF- whether the browser is allowed to guess the content typeSECURE_BROWSER_XSS_FILTER- whether to enable the cross-site scripting attack filterSECURE_SSL_REDIRECT- whether to redirect to HTTPSSECURE_REDIRECT_EXEMPT- exempt from redirecting to HTTPSSessionMiddleware- session middleware.CsrfViewMiddleware- middleware that prevents cross-site request forgery by generating tokens.XFrameOptionsMiddleware- middleware that prevents clickjacking attacks by setting response header parameters.
During the request process, the middleware above is executed from top to bottom in the order they are written. Then URL parsing happens, and finally the request comes to the view function. During the response process, the middleware above is executed from bottom to top in the order they are written, which is exactly the opposite of the order used during the request process.
There are two ways to implement middleware in Django: class-based implementation and function-based implementation. The second one is closer to the way decorators are written. A decorator is actually an application of the proxy pattern. It puts cross-cutting concerns, functions that do not have an unavoidable connection with normal business logic, such as identity authentication, log recording, and encoding conversion, into the proxy. The proxy object completes the behavior of the proxied object and adds extra functions. Middleware intercepts and filters user requests and responses and adds extra processing. On this point, it is exactly the same as a decorator, so implementing middleware in a function-based way is almost exactly the same as writing a decorator. Below we use custom middleware to implement user login validation.
"""
middlewares.py
"""
from django.http import JsonResponse
from django.shortcuts import redirect
# Resource paths that can only be accessed after login
LOGIN_REQUIRED_URLS = {'/praise/', '/criticize/', '/excel/', '/teachers_data/'}
def check_login_middleware(get_resp):
def wrapper(request, *args, **kwargs):
# The requested resource path is in the set above
if request.path in LOGIN_REQUIRED_URLS:
# If the session contains userid, treat the user as logged in
if 'userid' not in request.session:
# Check whether it is an Ajax request
if request.is_ajax():
# Ajax request returns JSON data to tell the user to log in
return JsonResponse({'code': 10003, 'hint': 'Please log in first'})
else:
backurl = request.get_full_path()
# Non-Ajax request is directly redirected to the login page
return redirect(f'/login/?backurl={backurl}')
return get_resp(request, *args, **kwargs)
return wrapperOf course, we can also define a class to act as a decorator. If the class has the __call__ magic method, objects of this class can be called just like functions. So the following is another way to implement middleware. The principle is exactly the same as the code above.
There is another class-based way to implement middleware. This way is no longer recommended in newer versions of Django, but in code you meet, you may still run into this style. The rough code is shown below.
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
pass
def process_view(self, request, view_func, view_args, view_kwargs):
pass
def process_template_response(self, request, response):
pass
def process_response(self, request, response):
pass
def process_exception(self, request, exception):
passThe five methods in the class above are all middleware hook functions. They are called back when a user request is received, before entering the view function, when rendering the template, when returning the response, and when an exception happens. Of course, whether to write these methods depends on the needs of the middleware. Not every situation needs all five methods to be overridden. The picture below should help everyone understand this writing style.
After writing the middleware code, we need to change the configuration file to activate the middleware and make it work.
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',
'debug_toolbar.middleware.DebugToolbarMiddleware',
'vote.middlewares.check_login_middleware',
]Notice the order of the elements in the middleware list above. When a request from the user is received, the middleware is executed one by one from top to bottom. Only after these middleware finish does the request finally reach the view function. Of course, during this process, the user's request can be intercepted, just like in the custom middleware above. If the user accesses protected resources without logging in, the middleware redirects the request directly to the login page, and the later middleware and the view function will not continue to run. During the response process, the middleware above is executed one by one from bottom to top. In this way, we can also do further processing to the response.
The execution order of middleware is very important. For middleware that depends on each other, we must make sure the middleware being depended on is placed before the middleware that depends on it. Just like the custom middleware we wrote above, it must be placed after SessionMiddleware, because we need to depend on the session object that this middleware binds to the request in order to know whether the user has logged in.
