HTTP (HyperText Transfer Protocol) se diseñó para dar soporte a un modelo de transferencia de datos sin estado, de solicitud-respuesta, desde un servidor a un cliente. Su primera versión, la versión 1.0, daba soporte a una relación puramente 1:1 entre solicitud y conexión (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, muchas solicitudes por conexión. Esto se hizo para hacer frente a la creciente complejidad de las páginas web, incluyendo los numerosos objetos y elementos que deben ser transferirse del servidor al cliente.
Con la adopción de la versión 2.0, HTTP siguió dando soporte a un modelo de muchas solicitudes por conexión. Los cambios más radicales implican el intercambio de encabezados y el paso de la transferencia basada en texto a la binaria.
Llegado un momento, HTTP se convirtió en algo más que un simple mecanismo para transferir texto e imágenes de un servidor a un cliente; se convirtió en una plataforma para aplicaciones. La ubicuidad del navegador, la naturaleza multiplataforma y la facilidad con la que las aplicaciones podían implementarse sin el elevado coste de tener que ser compatibles con múltiples sistemas operativos y entornos constituyeron atractivos muy reales. Por desgracia, HTTP no estaba diseñado para ser un protocolo de transporte de aplicaciones. Se diseñó para transferir documentos. Incluso los usos modernos de HTTP, como el de las API, asumen una carga útil similar a la de los documentos. Un buen ejemplo de esto es JSON, un formato de datos de par clave-valor transferido como texto. Aunque tanto los documentos como los protocolos de aplicaciones están normalmente basados en texto, el parecido termina ahí.
Las aplicaciones tradicionales tienen que contar con alguna forma de mantener su estado, mientras que los documentos no. Las aplicaciones se basan en flujos y procesos lógicos, y ambos necesitan que la aplicación sepa dónde se encuentra el usuario en un momento dado, y eso requiere estado. A pesar de la naturaleza intrínseca sin estado de HTTP, este se ha convertido en el protocolo de transporte de aplicaciones de facto de la web. En lo que es sin duda uno de los avances más aceptados y útiles de la historia técnica, HTTP recibió los medios con los que rastrear el estado de una aplicación a lo largo de su uso. En este avance o «hack» entran en juego las sesiones y las cookies.
Las sesiones son la forma en la que los servidores web y de aplicaciones mantienen el estado. Estos sencillos pedacitos de memoria se asocian con cada conexión TCP que se realiza a un servidor web o de aplicaciones, y sirven como almacenamiento en memoria de la información en las aplicaciones basadas en HTTP.
Cuando un usuario se conecta a un servidor por primera vez, se crea una sesión y se asocia con esa conexión. Los desarrolladores utilizan esa sesión como lugar para almacenar datos relevantes para la aplicación. Estos datos pueden abarcar desde información importante, como la identificación del cliente, hasta datos menos importantes, como la forma en que le gusta ver la página principal del sitio.
El mejor ejemplo de la utilidad de las sesiones son los carritos de compra, porque casi todos hemos comprado en línea alguna vez. Los artículos de un carrito de la compra permanecen durante el transcurso de una «sesión», porque cada artículo del carrito de compra está representado de alguna manera en la sesión en el servidor. Otro buen ejemplo son las aplicaciones de configuración o personalización de productos tipo asistente. Estas «miniaplicaciones» le permiten navegar por un conjunto de opciones y seleccionarlas; al final, suele ser sorprendente el coste estimado de todas las pequeñas cosas que ha ido añadiendo. Al hacer clic en 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 tener estado, pero es posible que sus arquitecturas no sigan este principio. Los métodos modernos de escalado a menudo se basan en patrones de arquitectura como la fragmentación, que requiere el enrutamiento de las solicitudes en base a determinados datos indexables, como el nombre de usuario o el número de cuenta. Para ello es necesario adoptar un enfoque con estado, en el sentido de que los datos indexables se adjuntan a cada solicitud para garantizar un enrutamiento y un comportamiento de la aplicación adecuados. En este sentido, las aplicaciones y API modernas «sin estado» a menudo requieren un cuidado y aprovisionamiento 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 se cierran. Además, la definición de «demasiado tiempo» para las conexiones es muy diferente que para las sesiones. La configuración predeterminada de algunos servidores web, por ejemplo, es cerrar una conexión una vez que ha estado inactiva (es decir, cuando se han realizado más solicitudes) durante 15 segundos. Por el contrario, una sesión en esos servidores web, de forma predeterminada, permanecerá en la memoria durante 300 segundos, es decir, 5 minutos. Evidentemente, ambas cosas están reñidas, porque una vez que la conexión se termina, ¿de qué sirve la sesión si está asociada a la conexión?
Podría pensar que solo hay que incrementar el parámetro de tiempo de espera de la conexión para que coincida con el de la sesión y así solucionar esta diferencia. El incremento del tiempo de espera significa que, en potencia, se va a consumir memoria para mantener una conexión que puede o no ser utilizada. Esto puede disminuir la capacidad total de usuarios concurrentes del servidor, así como, en última instancia, impedir su rendimiento. Y, evidentemente, disminuir el tiempo de espera de la sesión para que coincida con el tiempo de espera de la conexión no es una opción, porque la mayoría de la gente invierte más de cinco minutos en comprar o personalizar su nuevo juguete.
Important note: While HTTP/2 addresses some of these issues, it introduces others related to maintaining state. While not required by the specification, major browsers only allow HTTP/2 over TLS/SSL. Both protocols require persistence to avoid the performance cost of renegotiation, which in turn requires session awareness, a.k.a stateful behavior.
Por tanto, lo que se obtiene es que las sesiones permanezcan en la memoria del servidor incluso después de que sus conexiones asociadas hayan finalizado por inactividad, lo que consume valiosos recursos y molesta potencialmente a los usuarios que encuentran que su aplicación simplemente no funciona.
Por suerte, este problema se soluciona utilizando cookies.
Las cookies son fragmentos de datos almacenados en el cliente por el navegador. Las cookies pueden almacenar, y de hecho lo hacen, todo tipo de datos interesantes sobre usted, sus aplicaciones y los sitios que visita. El término «cookie» deriva de «magic cookie», un concepto bien conocido en la informática UNIX que inspiró tanto la idea como el nombre. Las cookies se crean y comparten entre el navegador y el servidor a través del encabezado 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 almacenar la cookie del encabezado HTTP en un archivo en el ordenador, y mantiene un seguimiento de las cookies por dominio. Las cookies de un dominio determinado siempre se transmiten al servidor mediante el navegador en los encabezados HTTP, por lo que los desarrolladores de aplicaciones web pueden recuperar esos valores con tan solo pedirlos en el lado del servidor de la aplicación.
El problema de la duración de la sesión/conexión se resuelve mediante una cookie. Casi todas las aplicaciones web modernas generan un «ID de sesión» y lo pasan como una cookie. Esto permite a la aplicación encontrar la sesión en el servidor incluso después de que se termine la conexión desde la que se creó la sesión. A través de este intercambio de ID de sesión, el estado se mantiene incluso para 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? Normalmente, se introduce un equilibrador de carga, o en las arquitecturas actuales un controlador de entrega de aplicaciones (ADC), para escalar la aplicación de forma que todos los usuarios estén satisfechos con la disponibilidad y el rendimiento.
En las aplicaciones modernas, las cookies pueden seguir utilizándose, pero existen otros encabezados HTTP que pasan a ser fundamentales. Las claves de API para la autenticación y la autorización con frecuencia se transportan mediante de un encabezado HTTP, así como otros encabezados personalizados que llevan los datos necesarios para el enrutamiento y el escalado adecuados de los servicios de back-end. El uso de la «cookie» tradicional para transportar estos datos o algún otro encabezado HTTP es menos importante que reconocer su relevancia en la arquitectura general.
El problema es que, en general, los algoritmos de equilibrio de carga solo se ocupan de distribuir las solicitudes entre los servidores. Las técnicas de equilibrio de carga se basan en algoritmos estándar del sector, como el «round robin», el menor número de conexiones o el tiempo de respuesta más rápido. Ninguno de ellos tiene estado, y es posible que un usuario tenga cada solicitud que ha realizado a una aplicación distribuida a un servidor diferente. Esto supone que todo el trabajo realizado para implementar el estado para HTTP es inútil, porque los datos almacenados en la sesión de un servidor rara vez se comparten con otros servidores del «pool».
Aquí es donde el concepto de persistencia entra en juego.
La persistencia (también conocida como «pegajosidad») es una técnica implementada por los controladores de entrega de aplicaciones (ADC) para garantizar que las solicitudes de un mismo usuario se distribuyan siempre al servidor en el que se iniciaron. Algunos productos y servicios de equilibrio de carga describen esta técnica como «sesiones pegajosas», que es un apelativo totalmente apropiado.
La persistencia se ha utilizado durante mucho tiempo para equilibrar la carga de los sitios habilitados para SSL/TLS, ya que una vez que se ha completado el proceso de negociación (que requiere un uso intensivo de los recursos informáticos) y se han intercambiado las claves, se reduciría significativamente el rendimiento si se iniciara de nuevo el proceso. Por ello, los ADC implementaron la persistencia de la sesión SSL para garantizar la dirección de los usuarios siempre al servidor al que se conectaron por primera vez.
A lo largo de los años, las implementaciones de los navegadores han hecho necesario el desarrollo de una técnica para evitar la costosa renegociación de esas sesiones. Esa técnica se llama persistencia basada en cookies.
En lugar de depender del identificador de sesión SSL/TLS, el equilibrador de carga inserta una cookie para identificar de forma exclusiva la sesión la primera vez que un cliente accede al sitio y, a continuación, hace referencia a esa cookie en las siguientes solicitudes para mantener la conexión con el servidor correspondiente.
El concepto de persistencia basada en cookies se ha aplicado desde entonces a las sesiones de las aplicaciones, utilizando la información de identificación 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 la misma sesión. Sin esta capacidad, las aplicaciones que requieren equilibrio de carga tendrían que encontrar otra forma de compartir la información de la sesión o recurrir a aumentar los tiempos de espera de las sesiones y las conexiones hasta el punto de que el número de servidores necesarios para dar soporte a su base de usuarios enseguida sería imposible de gestionar.
Aunque la forma más común de persistencia se implementa utilizando ID de sesión que se pasan en el encabezado HTTP, hoy en día los ADC pueden utilizar la persistencia también para otros datos. Cualquier dato que pueda ser almacenado en una cookie o que pueda proceder de los encabezados IP, TCP o HTTP puede utilizarse para la persistencia de una sesión. De hecho, cualquier dato dentro de un mensaje de aplicación que identifique de forma exclusiva al usuario puede ser utilizado por parte de un ADC inteligente para la persistencia de una conexión entre el navegador y el servidor.
Puede que HTTP sea un protocolo sin estado, pero se ha conseguido forzar la inclusión del estado en el omnipresente protocolo. Gracias a la persistencia y a los controladores de entrega de aplicaciones, es posible diseñar aplicaciones web de alta disponibilidad y rendimiento sin afectar a la frágil integración de las cookies y las sesiones necesarias para mantener el estado.
Estas características son las que dan estado a HTTP, aunque su implementación y ejecución siguen sin tener estado. Sin cookies, sesiones y persistencia, seguramente habríamos dado con un protocolo con estado sobre el que construir nuestras aplicaciones. En cambio, las características y la funcionalidad que aportan los controladores de entrega de aplicaciones median entre los navegadores (clientes) y los servidores para proporcionar esta funcionalidad, ampliando la utilidad de HTTP más allá de las páginas web estáticas y las aplicaciones tradicionales hasta las modernas arquitecturas basadas en microservicios y la tan valorada economía digital, la API.