======================= Capítulo 17: Middleware =======================
En ocasiones, necesitarás ejecutar una pieza de código en todas las peticiones que maneja Django. Éste código puede necesitar modificar la petición antes de que la vista se encargue de ella, puede necesitar registrar información sobre la petición para propósitos de debugging, y así sucesivamente.
Tu puedes hacer esto con el framework middleware de Django, que es un conjunto de acoples dentro del procesamiento de petición/respuesta de Django. Es un sistema de "plug-in" liviano y de bajo nivel capaz de alterar de forma global tanto la entrada como la salida de Django.
Cada componente middleware es responsable de hacer alguna función específica. Si estas leyendo este libro de forma lineal (disculpen, posmodernistas), has visto middleware varias veces ya:
- Todas las herramientas de usuario y sesión que vimos en el
:doc:`capítulo 14<chapter14>` son posibles gracias a unas pequeñas piezas de
middleware (más específicamente, el middleware hace que
request.session
yrequest.user
estén disponibles para ti en las vistas. - La cache global del sitio discutida en él :doc:`capítulo 15<chapter15>` es solo una pieza de middleware que desvía la llamada a tu función de vista si la respuesta para esa vista ya fue almacenada en la cache.
- Todas las aplicaciones del paquete
contrib
comoflatpages
,redirects
, ycsrf
del :doc:`capítulo 16<chapter16>` hacen su magia a través de componentes middleware.
En este capítulo nos sumergiremos en las profundidades del middleware y conoceremos exactamente cómo funciona, y te explicaremos cómo puedes escribir tu propio middleware.
Un componente middleware es simplemente una clase Python que se ajusta a una cierta API. Antes de entrar en los aspectos formales de los que es esa API, miremos un ejemplo muy sencillo.
Sitios de tráfico alto a menudo necesitan implementar Django detrás de un proxy
de balanceo de carga (mira él :doc:`capítulo 12<chapter12>`). Esto puede causar
unas pequeñas complicaciones, una de las cuales es que la IP remota de cada
petición (request.META["REMOTE_IP"]) será la del balanceador de carga, no
la IP real que realiza la petición. Los balanceadores de carga manejan esto
estableciendo una cabecera especial, X-Forwarded-For
, con el valor real de
la dirección IP que realiza la petición.
Así que aquí está una pequeña parte de middleware que le permite a los sitios que se ejecutan detrás de un proxy ver la dirección IP correcta en request.META["REMOTE_ADDR"]:
class SetRemoteAddrFromForwardedFor(object):
def process_request(self, request):
try:
real_ip = request.META['HTTP_X_FORWARDED_FOR']
except KeyError:
pass
else:
# HTTP_X_FORWARDED_FOR puede ser una lista de IPs separadas por comas.
# Toma la primera IP.
real_ip = real_ip.split(",")[0]
Nota:
(Nota: Aunque las cabeceras HTTP son llamadas X-Forwarded-For
, Django hace
que estén disponibles como request.META['HTTP_X_FORWARDED_FOR']
. Con la
excepción de content-length
y content-type
, cualquier cabecera HTTP en
la petición es convertida en una clave request.META
convirtiendo todos los
caracteres a mayúsculas, remplazando cualquier guion con guiones bajos y
agregando el prefijo HTTP_
al nombre.
Si el middleware está instalado (mira la siguiente sección), el valor de
de todas las peticiones X-Forwarded-For
será automáticamente insertado en
request.META['REMOTE_ADDR']
. Esto significa que tus aplicaciones Django no
necesitan conocer si están detrás de un proxy de balanceo de carga o no, pueden
simplemente acceder a request.META['REMOTE_ADDR']
, y eso funcionará si se
usa un proxy o no.
De hecho, es una necesidad tan común, que esta pieza de middleware ya viene
incorporada en Django. Está ubicada en django.middleware.http
, y puedes leer
más sobre ella en la siguiente sección.
Si has leído este libro completamente hasta aquí, ya has visto varios ejemplos de instalación de middleware; muchos de los ejemplos en los capítulos previos han requerido cierto middleware. Para completar, a continuación se muestra la manera de instalar middleware.
Para activar algún componente del middleware, solo agrégalo a la tupla
MIDDLEWARE_CLASSES
en tu archivo de configuración. En MIDDLEWARE_CLASSES
,
cada componente middleware se representa con una cadena: la ruta Python
completa al nombre de la clase middleware. Por ejemplo, aquí se muestra la tupla
MIDDLEWARE_CLASSES
por omisión creada por django-admin.py startproject
:
MIDDLEWARE_CLASSES = ( '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', )
Una instalación Django no requiere ningún middleware -- La tupla MIDDLEWARE_CLASSES
puede estar vacía, si tu quieres, pero te recomendamos que actives CommonMiddleware
,
la cual explicaremos en breve.
El orden es importante. En las fases de petición y vista, Django aplica el
middleware en el orden que figura en MIDDLEWARE_CLASSES
, y en las fases de
respuesta y excepción, Django aplica el middleware en el orden inverso. Es decir,
Django trata MIDDLEWARE_CLASSES
como una especie de "wrapper" alrededor de
la función de vista: en la petición recorre hacia abajo la lista hasta la vista,
y en la respuesta la recorre hacia arriba. Mira la siguiente figura para un
repaso de las fases y el orden que sigue el middleware en una petición.
Como puedes observar, durante la fase de petición (request), antes de llamar a la
vista, Django aplica el Middleware en el orden definido en la tupla
MIDDLEWARE_CLASSES
. De arriba hacia abajo. Mediante dos ganchos disponibles:
process_request()
process_view()
Durante la fase de respuesta, después de llamar a la vista, el middleware es aplicado en orden inverso de abajo hacia arriba, Usando tres ganchos disponibles:
process_exception()
(únicamente si la vista lanza una excepción)process_template_response()
(únicamente para la respuesta de la plantilla)process_response()
Puedes pensar en este proceso, como si fuera una cebolla, donde cada clase del
middleware es una `capa
que envuelve la vista.
Ahora que sabes qué es un middleware y cómo instalarlo, echemos un vistazo a todos los métodos disponibles que las clases middleware pueden definir.
Utiliza __init__()
para realizar una configuración a nivel de sistema de una
determinada clase middleware.
Por razones de rendimiento, cada clase middleware activada es instanciada sólo
una vez por proceso servidor. Esto significa que __init__()
es llamada
sólo una vez -- al iniciar el servidor, no para peticiones individuales.
Una razón común para implementar un método __init__()
es para verificar si
el middleware es en realidad necesario. Si __init__()
emite
django.core.exceptions.MiddlewareNotUsed
, entonces Django removerá el
middleware de la pila de middleware. Tú podrías usar esta característica para
verificar si existe una pieza de software que la clase middleware requiere, o
verificar si el servidor esta ejecutándose en modo debug, o cualquier otra
situación similar.
Si una clase middleware define un método __init__()
, éste no debe tomar
argumentos más allá del estándar self
.
Éste método es llamado tan pronto como la petición ha sido recibida -- antes de
que Django haya analizado sintácticamente la URL para determinar cuál vista
ejecutar. Si se le pasa el objeto HttpRequest
, el cual puedes modificar a tu
voluntad, process_request()
debe retornar ya sea None
o un objeto
HttpResponse
.
- Si devuelve
None
, Django continuará procesando esta petición, ejecutando cualquier otro middleware y la vista apropiada. - Si devuelve un objeto
HttpResponse
, Django no se encargará de llamar a cualquier otro middleware (de ningún tipo) o a la vista apropiada. Django inmediatamente devolverá ése objetoHttpResponse
.
Éste método es llamado después de la llamada al pre-procesador de petición y después de que Django haya determinado qué vista ejecutar, pero antes de que ésa vista sea realmente ejecutada.
Los argumentos que se pasan a esta vista son mostrados en la Tabla 15-1.
Argumento | Explicación |
---|---|
request |
El objeto HttpRequest . |
view |
La función Python que Django llamará para manejar esta petición. Este es en realidad el objeto función en sí, no el nombre de la función como string. |
args |
La lista de argumentos posicionales que serán pasados
a la vista, no incluye el argumento request
(el cual es siempre el primer argumento de una vista). |
kwargs |
El diccionario de palabras clave argumento que será pasado a la vista. |
Así como el método process_request()
, process_view()
debe retornar ya sea
None
o un objeto HttpResponse
.
- Si devuelve
None
, Django continuará procesando esta petición, ejecutando cualquier otro middleware y la vista apropiada. - Si devuelve un objeto
HttpResponse
, Django no se encargará de llamar a cualquier otro middleware (de ningún tipo) o a la vista apropiada. Django inmediatamente devolverá ése objetoHttpResponse
.
Éste método es llamado después de que la función de vista es llamada y la respuesta generada. Aquí, el procesador puede modificar el contenido de una respuesta; un caso de uso obvio es la compresión de contenido, como por ejemplo la compresión con gzip del HTML de la respuesta.
Los parámetros deben ser bastante auto-explicativos: request
es el objeto
petición, y response
es el objeto respuesta retornados por la vista.
A diferencia de los pre-procesadores de petición y vista, los cuales pueden
retornar None
, process_response()
debe retornar un objeto
HttpResponse
. Esa respuesta puede ser la respuesta original pasada a la
función (posiblemente modificada) o una totalmente nueva.
Éste método es llamado sólo si ocurre algún error y la vista emite una excepción sin capturar. Puedes usar este método para enviar notificaciones de error, volcar información postmórtem a un registro, o incluso tratar de recuperarse del error automáticamente.
Los parámetros para esta función son el mismo objeto request
con el que
hemos venido tratando hasta aquí, y excepción
, el cual es el objeto
Exception
real emitido por la función de vista.
process_exception()
debe retornar ya sea None
o un objeto HttpResponse
.
- Si devuelve
None
, Django continuará procesando esta petición con el manejador de excepción incorporado en el framework. - Si devuelve un objeto
HttpResponse
, Django usará esa respuesta en vez del manejador de excepción incorporado en el framework.
Nota:
Django trae incorporado una serie de clases middleware (que se discuten en la sección siguiente) que hacen de buenos ejemplos. La lectura de su código debería darte una buena idea de la potencia del middleware.
También puedes encontrar una serie de ejemplos contribuidos por la comunidad en el wiki de Django: http://code.djangoproject.com/wiki/ContributedMiddleware
Django viene con algunos middleware incorporados para lidiar con problemas comunes, los cuales discutiremos en las secciones que siguen.
Clase middleware: django.contrib.auth.middleware.AuthenticationMiddleware
.
Este middleware permite el soporte para autenticación. Agrega el atributo
request.user
, que representa el usuario actual registrado, a todo objeto
HttpRequest
que se recibe.
Mira él :doc:`capítulo 12<chapter12>` para los detalles completos.
Clase middleware: django.middleware.common.CommonMiddleware
.
Este middleware agrega algunas conveniencias para los perfeccionistas:
Prohíbe el acceso a los agentes de usuario especificados en la configuración
DISALLOWED_USER_AGENTS
: Si se especifica, esta configuración debería ser una lista de objetos de expresiones regulares compiladas que se comparan con el encabezado user-agent de cada petición que se recibe. Aquí está un pequeño ejemplo de un archivo de configuración:import re DISALLOWED_USER_AGENTS = ( re.compile(r'^OmniExplorer_Bot'), re.compile(r'^Googlebot') )
Nota el
import re
, ya queDISALLOWED_USER_AGENTS
requiere que sus valores sean expresiones regulares compiladas (es decir, el resultado dere.compile()
). El archivo de configuración es un archivo común de Python, por lo tanto es perfectamente adecuado incluir sentenciasimport
en él.Realiza re-escritura de URL basado en las configuraciones
APPEND_SLASH
yPREPEND_WWW
: SiAPPEND_SLASH
es igual aTrue
, las URLs que no poseen una barra al final serán redirigidas a la misma URL con una barra al final, a menos que el último componente en el path contenga un punto. De esta manerafoo.com/bar
es redirigido afoo.com/bar/
, perofoo.com/bar/file.txt
es pasado a través sin cambios.Si
PREPEND_WWW
es igual aTrue
, las URLs que no poseen el prefijo "www." serán redirigidas a la misma URL con el prefijo "www.".Ambas opciones tienen por objeto normalizar URLs. La filosofía es que cada URL debería existir en un -- y sólo un -- lugar. Técnicamente la URL
example.com/bar
es distinta deexample.com/bar/
, la cual a su vez es distinta dewww.example.com/bar/
. Un motor de búsqueda indexador trataría de forma separada estas URLs, lo cual es perjudicial para la valoración de tu sitio en el motor de búsqueda, por lo tanto es una buena práctica normalizar las URLs.Maneja ETags basado en la configuración
USE_ETAGS
:ETags
son una optimización a nivel HTTP para almacenar condicionalmente las páginas en la caché. SiUSE_ETAGS
es igual aTrue
, Django calculará una ETag para cada petición mediante la generación de un hash MD5 del contenido de la página, y se hará cargo de enviar respuestasNot Modified
, si es apropiado.Nota también que existe un middleware de
GET
condicional, que veremos en breve, el cual maneja ETags y hace algo más.
Clase middleware: django.middleware.gzip.GZipMiddleware
.
Este middleware comprime automáticamente el contenido para aquellos navegadores que comprenden la compresión gzip (todos los navegadores modernos). Esto puede reducir mucho la cantidad de ancho de banda que consume un servidor Web. La desventaja es que esto toma un poco de tiempo de procesamiento para comprimir las páginas.
Nosotros por lo general preferimos velocidad sobre ancho de banda, pero si tu prefieres lo contrario, solo habilita este middleware.
Clase middleware: django.middleware.http.ConditionalGetMiddleware
.
Este middleware provee soporte para operaciones GET
condicionales. Si la
respuesta contiene un encabezado Last-Modified
o ETag
, y la petición
contiene If-None-Match
o If-Modified-Since
, la respuesta es reemplazada
por una respuesta 304 ("Not modified"). El soporte para ETag
depende de la
configuración USE_ETAGS
y espera que el encabezado ETag
de la respuesta
ya este previamente fijado. Como se señaló anteriormente, el encabezado ETag
es fijado por el middleware Common.
También elimina el contenido de cualquier respuesta a una petición HEAD
y
fija los encabezados de respuesta Date
y Content-Length
para todas las
peticiones.
Clase middleware: django.middleware.http.SetRemoteAddrFromForwardedFor
.
Este es el ejemplo que examinamos en la sección anterior "Qué es middleware".
Este establece el valor de request.META['REMOTE_ADDR']
basándose en el
valor de request.META['HTTP_X_FORWARDED_FOR']
, si este último está fijado.
Esto es útil si estas parado detrás de un proxy inverso que provoca que cada
petición REMOTE_ADDR
sea fijada a 127.0.0.1
.
Warning
Este middleware no permite validar HTTP_X_FORWARDED_FOR
.
Si no estás detrás de un proxy inverso que establece
HTTP_X_FORWARDED_FOR
automáticamente, no uses este middleware.
Cualquiera puede inventar el valor de HTTP_X_FORWARDED_FOR
, y ya que
este establece REMOTE_ADDR
basándose en HTTP_X_FORWARDED_FOR
,
significa que cualquiera puede falsear su dirección IP.
Solo usa este middleware cuando confíes absolutamente en el valor de
HTTP_X_FORWARDED_FOR
.
Clase middleware: django.contrib.sessions.middleware.SessionMiddleware
.
Este middleware habilita el soporte para sesiones. Mira el :doc:`capítulo 14<chapter14>` para más detalles.
Clase middleware: django.middleware.cache.CacheMiddleware
.
Este middleware almacena en la cache cada página impulsada por Django. Este se analizó en detalle en él :doc:`capítulo 15<chapter15>`.
Clase middleware: django.middleware.transaction.TransactionMiddleware
.
Este middleware asocia un COMMIT
o ROLLBACK
de la base de datos con una
fase de petición/respuesta. Si una vista de función se ejecuta con éxito, se
emite un COMMIT
. Si la vista provoca una excepción, se emite un
ROLLBACK
.
El orden de este middleware en la pila es importante. Los módulos middleware que se ejecutan fuera de este, se ejecutan con commit-on-save -- el comportamiento por omisión de Django. Los módulos middleware que se ejecutan dentro de este (próximos al final de la pila) estarán bajo el mismo control de transacción que las vistas de función.
Mira el Apéndice B para obtener más información sobre las transacciones de base de datos.
Clase middleware: django.middleware.doc.XViewMiddleware
.
Este middleware envía cabeceras HTTP X-View
personalizadas a peticiones HEAD
que provienen de direcciones IP definidas en la configuración INTERNAL_IPS
.
Esto es usado por el sistema automático de documentación de Django.
Los desarrolladores Web y los diseñadores de esquemas de bases de datos no siempre tienen el lujo de comenzar desde cero. En el :doc:`próximo capítulo<chapter19>`, vamos a cubrir el modo de integrarse con sistemas existentes, tales como esquemas de bases de datos que has heredado de la década de los 80.