Django
Tutorial Django: Middlewares
Django, middleware: Aprende su uso e implementación. Configura y crea middleware para autenticar, gestionar sesiones y mejorar la seguridad en tus aplicaciones.
Aprende Django GRATIS y certifícate¿Qué son los middleware y para qué se usan?
En Django, un middleware es una clase que se ubica entre el servidor web y la aplicación, y tiene la capacidad de procesar cada petición y respuesta que entra o sale del framework. Los middlewares permiten incorporar funcionalidades a nivel global que afectan a todas las solicitudes sin modificar el código de las vistas o modelos.
Los middlewares se utilizan para tareas como la gestión de autenticación y autorización, manejo de sesiones, manipulación de cookies, registro de actividades para auditoría y aplicación de políticas de seguridad como protección contra ataques XSS o CSRF. También son útiles para modificar el contenido de las respuestas o para implementar compresión y cacheado de contenido.
Cada middleware se define como una clase con métodos específicos que Django reconoce y ejecuta en puntos determinados del ciclo de vida de una solicitud. Al configurar los middlewares en la lista MIDDLEWARE
del archivo settings.py
, se establece el orden en el que se aplicarán, lo cual es crucial, ya que cada middleware puede depender del procesamiento realizado por los anteriores.
El uso de middlewares permite mantener un código modular y limpio, ya que encapsulan funcionalidades transversales sin necesidad de repetir código en cada vista. Esto facilita el mantenimiento y la escalabilidad de la aplicación, al poder añadir o modificar comportamientos globales simplemente ajustando los middlewares correspondientes.
Flujo de ejecución de un middleware
El flujo de ejecución de un middleware en Django es fundamental para comprender cómo se procesan las peticiones y respuestas en una aplicación. Los middlewares se aplican en un orden específico definido en la lista MIDDLEWARE
del archivo settings.py
. Este orden determina cómo se encadenan las clases de middleware al recibir una petición y al enviar una respuesta.
Cuando una petición llega al servidor, Django inicia el procesamiento pasando el objeto HttpRequest
a través de cada middleware en la lista, comenzando por el primero. Cada middleware puede modificar el objeto HttpRequest
o realizar acciones adicionales antes de pasarlo al siguiente middleware. Este proceso continúa hasta que la petición alcanza la vista correspondiente.
Después de que la vista genera una respuesta, representada por un objeto HttpResponse
, el flujo se invierte. La respuesta es devuelta a través de los middlewares en orden inverso, desde el último hasta el primero en la lista MIDDLEWARE
. Durante este recorrido, cada middleware tiene la oportunidad de modificar el objeto HttpResponse
o realizar operaciones como manipular cookies, agregar encabezados HTTP o comprimir el contenido.
Para ilustrar el flujo de ejecución, consideremos un ejemplo de middleware simple:
# middleware.py
class MiMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# Código de inicialización (se ejecuta una sola vez al iniciar el servidor)
def __call__(self, request):
# Código que se ejecuta antes de la vista
print("Antes de la vista")
response = self.get_response(request)
# Código que se ejecuta después de la vista
print("Después de la vista")
return response
En este ejemplo, el método __init__
se ejecuta una única vez al iniciar el servidor y recibe la función get_response
, que representa el siguiente middleware en la cadena o la vista si es el último. El método __call__
se ejecuta en cada petición, permitiendo interactuar con el objeto request
antes de que sea procesado por la vista y con el objeto response
después de la vista.
Si ocurre una excepción durante el procesamiento de la vista, es posible manejarla en el middleware capturando los errores dentro del método __call__
. Esto permite controlar fallos y proporcionar respuestas personalizadas al usuario sin interrumpir el flujo normal de la aplicación.
Es importante recordar que el orden de los middlewares en la lista MIDDLEWARE
es crucial, ya que determina el comportamiento global de la aplicación. Un middleware ubicado al principio de la lista procesará la petición antes que los demás, y durante el retorno de la respuesta, será el último en modificarla. Por ello, la posición de cada middleware debe planificarse cuidadosamente para garantizar que las operaciones se realicen en el orden deseado.
Creación y uso de un middleware
La creación de un middleware en Django permite incorporar funcionalidades globales que afectan a todas las solicitudes y respuestas de la aplicación. A continuación, se describen los pasos necesarios para crear y utilizar un middleware personalizado.
1. Crear el archivo de middleware:
Es recomendable organizar el código creando un archivo dedicado para los middlewares. Por ejemplo, dentro de una aplicación llamada miapp
, se puede crear el archivo miapp/middleware.py
.
# miapp/middleware.py
class RegistroIPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# Inicialización opcional
def __call__(self, request):
# Obtener la dirección IP del cliente
ip_cliente = request.META.get('REMOTE_ADDR')
print(f"Dirección IP del cliente: {ip_cliente}")
response = self.get_response(request)
return response
En este ejemplo, el middleware RegistroIPMiddleware
captura la dirección IP del cliente en cada petición y la imprime en la consola. El método __call__
es el punto de entrada que procesa la solicitud y produce la respuesta.
2. Registrar el middleware en settings.py
:
Para que el middleware sea reconocido por Django, es necesario añadirlo a la lista MIDDLEWARE
en el archivo settings.py
.
# settings.py
MIDDLEWARE = [
# Middlewares predeterminados de Django...
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# ...
# Registrar el middleware personalizado
'miapp.middleware.RegistroIPMiddleware',
]
Es importante colocar la ruta completa al middleware, indicando el paquete y el nombre de la clase. El orden en la lista influye en el flujo de ejecución, por lo que debe ubicarse en la posición adecuada según las necesidades.
3. Probar el middleware:
Ejecutar el servidor de desarrollo con python manage.py runserver
y acceder a la aplicación desde un navegador o utilizando herramientas como curl
. Al realizar solicitudes, se debería ver en la consola las direcciones IP registradas por el middleware.
4. Implementar funcionalidades adicionales:
El middleware puede ampliarse para realizar tareas más complejas. Por ejemplo, se puede registrar las IP en un archivo de logs o en una base de datos, aplicar restricciones de acceso o realizar análisis estadísticos. A continuación, se muestra cómo escribir en un archivo de log:
import logging
logger = logging.getLogger(__name__)
class RegistroIPMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
ip_cliente = request.META.get('REMOTE_ADDR')
ruta = request.path
logger.info(f"IP: {ip_cliente} - Ruta: {ruta}")
response = self.get_response(request)
return response
En este caso, se utiliza el módulo logging
de Python para registrar información en los logs del servidor, lo cual es útil para auditoría y monitoreo.
5. Considerar middlewares asíncronos:
En aplicaciones que requieren alto rendimiento, es posible implementar middlewares asíncronos utilizando async def __call__(self, request)
. Este enfoque aprovecha las capacidades asíncronas de Django para manejar operaciones de I/O.
class MiddlewareAsincrono:
def __init__(self, get_response):
self.get_response = get_response
async def __call__(self, request):
# Operaciones asíncronas antes de la vista
# ...
response = await self.get_response(request)
# Operaciones asíncronas después de la vista
# ...
return response
Es importante asegurarse de que todas las operaciones dentro del middleware sean compatibles con async/await
para evitar bloqueos.
6. Compartir datos entre middlewares y vistas:
Si es necesario pasar información del middleware a las vistas, se pueden añadir atributos al objeto request
. Por ejemplo:
class UsuarioMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Añadir información al objeto request
request.usuario_actual = obtener_usuario(request)
response = self.get_response(request)
return response
De esta forma, las vistas pueden acceder a request.usuario_actual
y utilizar la información proporcionada por el middleware.
7. Gestionar excepciones:
Para manejar errores que puedan ocurrir en la vista u otros middlewares, se puede capturar excepciones dentro de __call__
y reaccionar en consecuencia.
from django.shortcuts import render
class ManejoExcepcionesMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
try:
response = self.get_response(request)
except Exception as e:
# Manejar la excepción y generar una respuesta adecuada
response = render(request, 'error.html', {'mensaje': str(e)})
return response
Este middleware captura cualquier excepción no manejada y responde con una página de error personalizada, mejorando la experiencia de usuario y facilitando la depuración.
La creación y uso de middlewares personalizados permite extender y personalizar el comportamiento de una aplicación Django de manera global. Al entender cómo implementarlos y registrarlos correctamente, se obtiene un control granular sobre el procesamiento de peticiones y respuestas, lo que contribuye a desarrollar aplicaciones más robustas y versátiles.
Creación de middleware personalizado
La creación de un middleware personalizado en Django permite extender y adaptar el comportamiento del framework a las necesidades específicas de tu proyecto. Aunque ya conocemos cómo crear y registrar un middleware básico, ahora profundizaremos en técnicas avanzadas para desarrollar middlewares más sofisticados y optimizados.
1. Middleware que modifica la solicitud
Un middleware puede interceptar y modificar el objeto HttpRequest
antes de que llegue a la vista. Por ejemplo, podemos añadir atributos personalizados o transformar datos:
# miapp/middleware.py
class AñadirAtributoMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Añadimos un atributo personalizado al request
request.mi_atributo = 'Valor personalizado'
response = self.get_response(request)
return response
En este caso, cualquier vista podrá acceder a request.mi_atributo
, permitiendo compartir información de manera consistente a través de la aplicación.
2. Middleware que modifica la respuesta
También es posible alterar el objeto HttpResponse
antes de que se envíe al cliente. Esto es útil para modificar contenido o añadir encabezados HTTP:
# miapp/middleware.py
class ModificarRespuestaMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
# Modificamos el contenido de la respuesta
if response.status_code == 200 and 'text/html' in response['Content-Type']:
contenido = response.content.decode('utf-8')
contenido_modificado = contenido.replace('Django', 'Mi Proyecto')
response.content = contenido_modificado.encode('utf-8')
return response
Este middleware reemplaza todas las ocurrencias de "Django" por "Mi Proyecto" en las respuestas HTML exitosas, personalizando la salida sin alterar las vistas.
3. Middleware basado en clases asíncronas
Para programación asíncrona. Podemos crear un middleware asíncrono utilizando async def
:
# miapp/middleware.py
class MiddlewareAsíncrono:
def __init__(self, get_response):
self.get_response = get_response
async def __call__(self, request):
# Operaciones asíncronas antes de la vista
await self.operacion_asíncrona_previa(request)
response = await self.get_response(request)
# Operaciones asíncronas después de la vista
await self.operacion_asíncrona_posterior(response)
return response
async def operacion_asíncrona_previa(self, request):
# Simulación de una tarea asíncrona
pass
async def operacion_asíncrona_posterior(self, response):
# Simulación de otra tarea asíncrona
pass
Este enfoque es ideal para tareas que requieren operaciones de I/O no bloqueantes, como llamadas a APIs externas o acceso a bases de datos asíncronas.
4. Middleware con configuración dinámica
Podemos diseñar middlewares que se comporten de manera diferente según parámetros de configuración definidos en settings.py
:
# settings.py
MI_MIDDLEWARE_CONFIG = {
'habilitado': True,
'clave_secreta': 'abc123',
}
# miapp/middleware.py
from django.conf import settings
class MiddlewareConfigurado:
def __init__(self, get_response):
self.get_response = get_response
config = getattr(settings, 'MI_MIDDLEWARE_CONFIG', {})
self.habilitado = config.get('habilitado', False)
self.clave_secreta = config.get('clave_secreta', '')
def __call__(self, request):
if self.habilitado:
# Uso de la clave_secreta en alguna operación
pass
response = self.get_response(request)
return response
Esto permite modificar el comportamiento del middleware sin cambiar su código, simplemente ajustando la configuración de la aplicación.
5. Middleware para limitación de tasa (rate limiting)
Implementar un sistema de limitación de tasa puede proteger tu aplicación de abusos o ataques de denegación de servicio:
# miapp/middleware.py
import time
from django.core.cache import cache
from django.http import HttpResponseTooManyRequests
class LimitacionTasaMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.limite = 100 # Máximo de solicitudes permitidas
self.periodo = 60 # En segundos
def __call__(self, request):
ip = request.META.get('REMOTE_ADDR')
clave = f"limite:{ip}"
solicitudes = cache.get(clave, 0)
if solicitudes >= self.limite:
return HttpResponseTooManyRequests("Demasiadas solicitudes, por favor intenta más tarde.")
else:
cache.set(clave, solicitudes + 1, timeout=self.periodo)
response = self.get_response(request)
return response
Este middleware utiliza la caché de Django para contar las solicitudes de cada dirección IP y restringir el acceso si se supera el límite establecido.
6. Middleware para autenticación personalizada
Si necesitas un sistema de autenticación diferente al proporcionado por defecto, puedes crear un middleware que gestione este proceso:
# miapp/middleware.py
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login
class AutenticacionTokenMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
token = request.headers.get('Authorization')
if token:
usuario = self.autenticar_por_token(token)
if usuario:
login(request, usuario)
response = self.get_response(request)
return response
def autenticar_por_token(self, token):
try:
usuario = User.objects.get(auth_token=token)
return usuario
except User.DoesNotExist:
return None
Este middleware autentica usuarios basándose en un token proporcionado en los encabezados de la solicitud, lo cual es útil para APIs o aplicaciones móviles.
7. Middleware para compresión de respuestas
Para mejorar el rendimiento, es posible comprimir las respuestas antes de enviarlas al cliente:
# miapp/middleware.py
import gzip
from django.utils.decorators import decorator_from_middleware
class CompresionMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
if 'Content-Type' in response and 'text/' in response['Content-Type']:
contenido = response.content
contenido_comprimido = gzip.compress(contenido)
response.content = contenido_comprimido
response['Content-Encoding'] = 'gzip'
response['Content-Length'] = str(len(contenido_comprimido))
return response
Este middleware comprime el contenido de las respuestas de tipo texto, reduciendo el tiempo de carga y el consumo de ancho de banda.
8. Middleware que interactúa con señales
Los middlewares pueden trabajar en conjunto con el sistema de señales de Django para reaccionar ante ciertos eventos:
# miapp/middleware.py
from django.core.signals import request_finished
from django.dispatch import receiver
class MiddlewareConSeñales:
def __init__(self, get_response):
self.get_response = get_response
request_finished.connect(self.cuando_se_termina_la_solicitud)
def __call__(self, request):
response = self.get_response(request)
return response
def cuando_se_termina_la_solicitud(self, sender, **kwargs):
# Lógica a ejecutar cuando se termina la solicitud
pass
Este enfoque es útil para realizar acciones que deben ocurrir después de que se complete el ciclo de vida de la solicitud.
9. Middleware para registro avanzado de logs
Podemos implementar un sistema de logging avanzado que registre detalles adicionales de cada petición:
# miapp/middleware.py
import logging
import json
logger = logging.getLogger('registro_avanzado')
class RegistroAvanzadoMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
respuesta = self.get_response(request)
datos_log = {
'metodo': request.method,
'ruta': request.path,
'estado': respuesta.status_code,
'usuario': request.user.username if request.user.is_authenticated else 'Anónimo',
'params': request.GET.dict(),
}
logger.info(json.dumps(datos_log))
return respuesta
Este middleware crea un log en formato JSON con información detallada de cada interacción, facilitando el análisis y monitoreo del sistema.
10. Middleware para mantenimiento programado
Si necesitamos colocar el sitio en modo de mantenimiento temporalmente, podemos crear un middleware que redirija a una página específica:
# miapp/middleware.py
from django.shortcuts import render
from django.conf import settings
class MantenimientoMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.en_mantenimiento = getattr(settings, 'EN_MANTENIMIENTO', False)
def __call__(self, request):
if self.en_mantenimiento and not request.path.startswith('/admin/'):
return render(request, 'mantenimiento.html')
response = self.get_response(request)
return response
Al activar la variable EN_MANTENIMIENTO
en settings.py
, todas las solicitudes se servirán con la plantilla mantenimiento.html
, excepto las dirigidas al panel de administración.
Buenas prácticas al crear middlewares personalizados
- Modularidad: mantén el código del middleware bien organizado y separado por responsabilidades.
- Eficiencia: evita procesos pesados en el middleware para no afectar el rendimiento general.
- Seguridad: valida y sanitiza cualquier dato que el middleware procese para prevenir vulnerabilidades.
- Pruebas: implementa tests unitarios y de integración para asegurar el correcto funcionamiento del middleware.
- Documentación: comenta el código y explica la finalidad y funcionamiento del middleware para facilitar su mantenimiento.
La habilidad de crear middlewares personalizados en Django te permite incorporar lógica transversal y adaptar el comportamiento del framework a las necesidades específicas de tu proyecto. Aplicando estas técnicas avanzadas, lograrás desarrollar aplicaciones más eficientes, seguras y fáciles de mantener.
Todas las lecciones de Django
Accede a todas las lecciones de Django y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Django
Introducción Y Entorno
Instalación Y Configuración Django Con Venv
Introducción Y Entorno
Arquitectura De Un Proyecto Django
Introducción Y Entorno
Base De Datos Mysql En Django
Modelos Y Base De Datos
Creación De Modelos
Modelos Y Base De Datos
Asociaciones De Modelos
Modelos Y Base De Datos
Migraciones
Modelos Y Base De Datos
Operaciones Crud Y Consultas
Modelos Y Base De Datos
Enrutamiento Básico
Vistas Y Plantillas
Plantillas Con Django Template Language
Vistas Y Plantillas
Vistas Basadas En Funciones
Vistas Y Plantillas
Vistas Basadas En Clases
Vistas Y Plantillas
Middlewares
Vistas Y Plantillas
Form Vs Modelform
Formularios
Procesamiento De Formularios
Formularios
Subida De Archivos
Formularios
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué es un middleware y su papel en Django.
- Configurar y ordenar middlewares en el archivo settings.py.
- Implementar middlewares para autenticación, sesiones y seguridad.
- Crear y registrar middlewares personalizados.
- Modificar objetos HttpRequest y HttpResponse dentro de un middleware.
- Realizar operaciones asíncronas en middlewares para alto rendimiento.