Middleware personalizado en Django

Intermedio
Django
Django
Actualizado: 18/04/2026

Estructura de un middleware

Un middleware moderno en Django es una clase callable que envuelve la vista:

# middlewares.py
import time
import logging

logger = logging.getLogger(__name__)

class MiddlewareBase:
    def __init__(self, get_response):
        """
        Se ejecuta una sola vez al inicializar el servidor.
        Ideal para configuraciones costosas.
        """
        self.get_response = get_response
        # Configuración inicial aquí

    def __call__(self, request):
        """Se ejecuta en cada petición."""
        # Código antes de la vista
        respuesta = self.get_response(request)
        # Código después de la vista
        return respuesta

Diagrama conceptual de Middleware personalizado en Django

Middleware de logging de peticiones

class LogPeticionesMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.logger = logging.getLogger('peticiones')

    def __call__(self, request):
        inicio = time.time()

        respuesta = self.get_response(request)

        duracion = (time.time() - inicio) * 1000  # ms
        self.logger.info(
            '%s %s %s %dms %s',
            request.method,
            request.path,
            respuesta.status_code,
            duracion,
            request.user.username if request.user.is_authenticated else 'anónimo'
        )

        # Añadir cabecera de tiempo de respuesta
        respuesta['X-Response-Time'] = f'{duracion:.2f}ms'
        return respuesta

Middleware de limitación de tasa

from django.core.cache import cache
from django.http import JsonResponse

class LimitarTasaMiddleware:
    """Limita el número de peticiones por IP."""
    LIMITE_PETICIONES = 100
    VENTANA_SEGUNDOS = 60

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        ip = self.obtener_ip(request)
        clave = f'rate_limit:{ip}'
        contador = cache.get(clave, 0)

        if contador >= self.LIMITE_PETICIONES:
            return JsonResponse(
                {'error': 'Demasiadas peticiones. Intenta de nuevo en 1 minuto.'},
                status=429
            )

        # Incrementar contador
        if contador == 0:
            cache.set(clave, 1, self.VENTANA_SEGUNDOS)
        else:
            cache.incr(clave)

        respuesta = self.get_response(request)
        respuesta['X-RateLimit-Remaining'] = self.LIMITE_PETICIONES - contador - 1
        return respuesta

    def obtener_ip(self, request):
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            return x_forwarded_for.split(',')[0].strip()
        return request.META.get('REMOTE_ADDR', '0.0.0.0')

Middleware con process_exception

Para manejo de excepciones, implementa el método process_exception:

import traceback

class ManejoExcepcionesMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response
        self.logger = logging.getLogger('excepciones')

    def __call__(self, request):
        return self.get_response(request)

    def process_exception(self, request, exception):
        """
        Se llama cuando una vista lanza una excepción no capturada.
        Devolver None permite que Django maneje la excepción normalmente.
        Devolver HttpResponse intercepta la excepción.
        """
        self.logger.error(
            'Excepción en %s %s: %s\n%s',
            request.method,
            request.path,
            str(exception),
            traceback.format_exc()
        )

        if isinstance(exception, PermissionError):
            from django.http import JsonResponse
            return JsonResponse({'error': 'Sin permiso'}, status=403)

        return None  # Django maneja el resto

Middleware de cabeceras de seguridad

class CabecerasSeguridad:
    """Añade cabeceras de seguridad HTTP a todas las respuestas."""
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        respuesta = self.get_response(request)
        respuesta['X-Content-Type-Options'] = 'nosniff'
        respuesta['X-Frame-Options'] = 'DENY'
        respuesta['Referrer-Policy'] = 'strict-origin-when-cross-origin'
        respuesta['Permissions-Policy'] = 'camera=(), microphone=(), geolocation=()'
        if not respuesta.get('Cache-Control'):
            respuesta['Cache-Control'] = 'no-cache, no-store, must-revalidate'
        return respuesta

Registrar middleware

El orden en MIDDLEWARE es fundamental. Los middleware se ejecutan de arriba a abajo en la petición y de abajo a arriba en la respuesta:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'miapp.middlewares.LimitarTasaMiddleware',      # Antes de procesar la sesión
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'miapp.middlewares.LogPeticionesMiddleware',    # Después de autenticación
    'miapp.middlewares.CabecerasSeguridad',
]
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Django

Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Entender el ciclo de vida de las peticiones a través del stack de middleware. Crear un middleware personalizado con la estructura init y call. Interceptar peticiones con lógica antes de llegar a la vista. Procesar respuestas antes de enviarlas al cliente. Manejar excepciones con process_exception en el middleware.