Cookies, sesiones y persistencia
Introducción
HTTP (Protocolo de Transferencia de Hipertexto) se diseñó para admitir un modelo sin estado de solicitud-respuesta para transferir datos de un servidor a un cliente. Su primera versión, la 1.0, solo admitía una relación 1:1 entre solicitudes y conexiones (es decir, se admitía un par solicitud-respuesta por conexión).
La versión 1.1 amplió esa proporción a N:1, es decir, varias solicitudes por conexión. Esto se hizo para abordar la creciente complejidad de las páginas web, incluidos los numerosos objetos y elementos que deben transferirse del servidor al cliente.
Con la adopción de la versión 2.0, HTTP siguió admitiendo un modelo de múltiples solicitudes por conexión. Sus cambios más radicales afectan al intercambio de cabeceras y al paso de una transferencia basada en texto a una transferencia binaria.
En algún momento, HTTP dejó de ser un simple mecanismo para transferir texto e imágenes de un servidor a un cliente y se convirtió en una plataforma para aplicaciones. La omnipresencia del navegador, su carácter multiplataforma y la facilidad con la que se podían desplegar aplicaciones sin incurrir en el elevado coste de mantener múltiples sistemas operativos y entornos resultaban, sin duda, atractivas. Por desgracia, HTTP no se diseñó como protocolo de transporte para aplicaciones, sino para transferir documentos. Incluso los usos modernos de HTTP, como las API, presuponen una carga útil similar a un documento. Un buen ejemplo es JSON, un formato de datos de pares clave-valor que se transfiere como texto. Aunque tanto los documentos como los protocolos de aplicaciones suelen basarse en texto, ahí termina el parecido.
Las aplicaciones tradicionales necesitan algún mecanismo para mantener su estado, mientras que los documentos no. Las aplicaciones se basan en flujos y procesos lógicos, y ambos requieren que la aplicación sepa en qué punto se encuentra el usuario en cada momento, lo que exige mantener un estado. A pesar de que HTTP carece de estado por naturaleza, se ha convertido de facto en el protocolo de transporte de aplicaciones de la web. Mediante lo que sin duda constituye uno de los trucos más aceptados y útiles de la historia de la tecnología, se dotó a HTTP de los medios necesarios para realizar un seguimiento del estado durante el uso de una aplicación. Ese «truco» es donde entran en juego las sesiones y las cookies.
Sesiones
Transformación de lo que no tiene estado en lo que sí lo tiene
Las sesiones son el mecanismo mediante el cual los servidores web y de aplicaciones mantienen el estado. Estos sencillos bloques de memoria están asociados a cada conexión TCP establecida con un servidor web o de aplicaciones y sirven para almacenar en memoria información de las aplicaciones basadas en HTTP.
Cuando un usuario se conecta a un servidor por primera vez, se crea una sesión que queda asociada a esa conexión. A continuación, los desarrolladores utilizan esa sesión para almacenar pequeños fragmentos de datos relevantes para la aplicación. Estos datos pueden abarcar desde información importante, como el identificador de un cliente, hasta datos menos relevantes, como sus preferencias sobre la visualización de la página principal del sitio.
El mejor ejemplo de la utilidad de las sesiones son los carritos de la compra, ya que casi todos hemos comprado por Internet alguna vez. Los artículos de un carrito se conservan durante una «sesión» porque cada uno de ellos está representado de algún modo en la sesión del servidor. Otro buen ejemplo son las aplicaciones de configuración o personalización de productos mediante asistentes. Estas «miniaplicaciones» permiten explorar un conjunto de opciones y seleccionarlas; al final, el coste estimado de todos los extras añadidos suele resultar sorprendente. A medida que se avanza por cada «pantalla de opciones», las demás opciones elegidas se almacenan en la sesión para poder recuperarlas, añadirlas o eliminarlas fácilmente.
Las aplicaciones modernas están diseñadas para no mantener estado, pero es posible que sus arquitecturas no se ajusten a ese principio. Los métodos modernos de escalado suelen basarse en patrones arquitectónicos como el particionamiento, que requiere enrutar las solicitudes en función de algún dato indexable, como el nombre de usuario o el número de cuenta. Esto exige un enfoque que, en cierto modo, mantiene el estado, ya que los datos indexables se incluyen en cada solicitud para garantizar un enrutamiento y un comportamiento adecuados de la aplicación. En este sentido, las aplicaciones y API modernas «sin estado» suelen requerir cuidados y mantenimiento similares a los de sus predecesoras con estado.
El problema es que las sesiones están vinculadas a las conexiones, y las conexiones que permanecen inactivas durante demasiado tiempo caducan. Además, la definición de «demasiado tiempo» para las conexiones es bastante distinta de la que se aplica a las sesiones. Por ejemplo, la configuración predeterminada de algunos servidores web consiste en cerrar una conexión cuando lleva 15 segundos inactiva, es decir, sin que se haya realizado ninguna otra solicitud. En cambio, en esos servidores web, una sesión permanece en memoria de forma predeterminada durante 300 segundos, o 5 minutos. Evidentemente, ambas cosas son incompatibles, porque, una vez que la conexión caduca, ¿de qué sirve la sesión si está asociada a ella?
Podría pensar que bastaría con aumentar el tiempo de espera de la conexión para que coincidiera con el de la sesión y así resolver esta disparidad. Sin embargo, aumentar el tiempo de espera implica que podría consumir memoria para mantener una conexión que quizá se utilice o quizá no. Esto puede reducir la capacidad total de usuarios simultáneos de su servidor y, en última instancia, perjudicar su rendimiento. Y, desde luego, tampoco conviene reducir el tiempo de espera de la sesión para que coincida con el de la conexión, ya que la mayoría de las personas tardan más de cinco minutos en explorar las opciones o personalizar su nuevo juguete.
Nota importante: Aunque HTTP/2 resuelve algunos de estos problemas, introduce otros relacionados con el mantenimiento del estado. Aunque la especificación no lo exige, los principales navegadores solo permiten HTTP/2 sobre TLS/SSL. Ambos protocolos requieren conexiones persistentes para evitar el coste de rendimiento que supone la renegociación, lo que a su vez requiere conocer el estado de la sesión, es decir, un comportamiento con estado.
Así, el resultado son sesiones que permanecen en la memoria del servidor incluso después de que sus conexiones asociadas se hayan cerrado por inactividad, consumiendo recursos valiosos y pudiendo frustrar a los usuarios a los que la aplicación simplemente no les funciona.
Por suerte, este problema se resuelve mediante el uso de cookies.
Cookies
El rastro de migas conduce a casa
Las cookies son pequeños fragmentos de datos que el navegador almacena en el cliente. Pueden almacenar, y de hecho almacenan, todo tipo de información interesante sobre usted, sus aplicaciones y los sitios que visita. El término «cookie» deriva de «magic cookie», un conocido concepto de la informática UNIX que inspiró tanto la idea como el nombre. Las cookies se crean y se comparten entre el navegador y el servidor mediante la cabecera HTTP Cookie.
Cookie: JSESSIONID=9597856473431 Cache-Control: no-cache Host: 127.0.0.2:8080 Connection: Keep-Alive
El navegador sabe automáticamente que debe guardar la cookie del encabezado HTTP en un archivo de su ordenador y realiza un seguimiento de las cookies por dominio. El navegador siempre envía al servidor las cookies de cada dominio en los encabezados HTTP, por lo que los desarrolladores de aplicaciones web pueden recuperar esos valores simplemente solicitándolos en el lado del servidor de la aplicación.
El problema de la duración de la sesión o conexión se resuelve mediante una cookie. Casi todas las aplicaciones web modernas generan un «ID de sesión» y lo envían como una cookie. Esto permite que la aplicación encuentre la sesión en el servidor incluso después de que se cierre la conexión desde la que se creó. Mediante este intercambio de ID de sesión, se mantiene el estado incluso con un protocolo sin estado como HTTP. Pero ¿qué ocurre cuando el uso de una aplicación web supera la capacidad de un único servidor web o de aplicaciones? Por lo general, se incorpora un equilibrador de carga o, en las arquitecturas actuales, un controlador de entrega de aplicaciones (ADC), para escalar la aplicación y garantizar que todos los usuarios estén satisfechos con su disponibilidad y rendimiento.
En las aplicaciones modernas, es posible que se sigan utilizando cookies, pero otras cabeceras HTTP adquieren una importancia fundamental. Las claves de API para la autenticación y la autorización suelen transmitirse mediante una cabecera HTTP, al igual que otras cabeceras personalizadas que contienen los datos necesarios para el enrutamiento y el escalado adecuado de los servicios de backend. Utilizar la «cookie» tradicional para transportar estos datos u otra cabecera HTTP es menos importante que reconocer su relevancia para la arquitectura global.
El problema es que los algoritmos de balanceo de carga suelen centrarse únicamente en distribuir las solicitudes entre los servidores. Las técnicas de balanceo de carga se basan en algoritmos estándar del sector, como la asignación por turnos (round robin), el menor número de conexiones o el tiempo de respuesta más rápido. Ninguno de ellos mantiene el estado, por lo que es posible que cada solicitud que un mismo usuario envíe a una aplicación se dirija a un servidor diferente. Esto hace que todo el trabajo realizado para implementar el estado en HTTP resulte inútil, ya que los datos almacenados en la sesión de un servidor rara vez se comparten con los demás servidores del «pool».
Aquí es donde resulta útil el concepto de persistencia.
Persistencia
El vínculo que une
La persistencia —también conocida como afinidad— es una técnica implementada por los ADC para garantizar que las solicitudes de un mismo usuario se distribuyan siempre al servidor en el que se inició la sesión. Algunos productos y servicios de balanceo de carga denominan esta técnica «sesiones persistentes», un nombre totalmente apropiado.
La persistencia se utiliza desde hace mucho tiempo en el balanceo de carga de sitios compatibles con SSL/TLS, ya que, una vez completado el proceso de negociación —que requiere un uso intensivo de recursos informáticos— y realizado el intercambio de claves, reiniciar el proceso reduciría considerablemente el rendimiento. Por ello, los ADC implementaron la persistencia de sesiones SSL para garantizar que los usuarios fueran dirigidos siempre al mismo servidor al que se conectaron inicialmente.
Con el paso de los años, las implementaciones de los navegadores han hecho necesario desarrollar una técnica para evitar la costosa renegociación de esas sesiones. Esta técnica se denomina persistencia basada en cookies.
En lugar de depender del ID de sesión SSL/TLS, el equilibrador de carga insertaba una cookie para identificar de forma unívoca la sesión la primera vez que un cliente accedía al sitio y, en las solicitudes posteriores, consultaba dicha cookie para mantener la conexión con el servidor correspondiente.
Desde entonces, el concepto de persistencia basada en cookies se ha aplicado a las sesiones de las aplicaciones, utilizando la información del ID de sesión generada por los servidores web y de aplicaciones para garantizar que las solicitudes de los usuarios se dirijan siempre al mismo servidor durante una misma sesión. Sin esta capacidad, las aplicaciones que requieren equilibrio de carga tendrían que buscar otra forma de compartir la información de sesión o recurrir a aumentar los tiempos de espera de las sesiones y las conexiones hasta tal punto que el número de servidores necesarios para dar servicio a su base de usuarios pronto resultaría inmanejable.
Aunque la forma más habitual de persistencia se implementa mediante identificadores de sesión transmitidos en la cabecera HTTP, los ADC actuales también pueden mantener la persistencia basándose en otros datos. Cualquier dato que pueda almacenarse en una cookie u obtenerse de las cabeceras IP, TCP o HTTP puede utilizarse para mantener la persistencia de una sesión. De hecho, un ADC inteligente puede utilizar cualquier dato de un mensaje de aplicación que identifique de forma inequívoca al usuario para mantener una conexión persistente entre el navegador y un servidor.
Conclusión
Puede que HTTP sea un protocolo sin estado, pero hemos conseguido incorporar el estado a la fuerza en este protocolo omnipresente. Mediante la persistencia y los controladores de entrega de aplicaciones, es posible diseñar aplicaciones web de alto rendimiento y alta disponibilidad sin romper la integración, un tanto frágil, de las cookies y las sesiones necesarias para mantener el estado.
Estas características son las que dotan de estado a HTTP, aunque su implementación y ejecución siguen careciendo de él. Sin cookies, sesiones y persistencia, seguramente habríamos encontrado un protocolo con estado sobre el que desarrollar nuestras aplicaciones. En su lugar, las características y funcionalidades de los controladores de entrega de aplicaciones actúan como intermediarias entre los navegadores (clientes) y los servidores para proporcionar esta capacidad, ampliando la utilidad de HTTP más allá de las páginas web estáticas y las aplicaciones tradicionales, hasta las arquitecturas modernas basadas en microservicios y la gran protagonista de la economía digital: la API.
PUBLICADO EL 19 DE ENERO DE 2018
