Salón de chat en tiempo real con Flask y Redis

PythonBeginner
Practicar Ahora

Introducción

Este proyecto utiliza SSE (Server-Sent Events) y Redis para implementar un salón de chat en línea. Se requiere un conocimiento previo de la sintaxis de Python y JavaScript, así como una comprensión básica del uso de Flask y Redis.

En esta sección del experimento, aprenderemos y practicaremos los siguientes conceptos:

  • Comunicación web en tiempo real
  • Funcionamiento de SSE
  • Uso de Redis

👀 Vista previa

Vista previa de la interfaz del salón de chat en línea

🎯 Tareas

En este proyecto, aprenderás:

  • Cómo crear un salón de chat en línea simple utilizando Flask y SSE
  • Cómo implementar la funcionalidad de inicio de sesión de usuario
  • Cómo utilizar Redis para el almacenamiento y recuperación de mensajes

🏆 Logros

Después de completar este proyecto, podrás:

  • Configurar SSE para la comunicación en tiempo real en una aplicación web
  • Utilizar Redis para almacenar y recuperar mensajes en una aplicación de salón de chat
  • Implementar la funcionalidad de inicio de sesión de usuario en Flask

Comunicación web en tiempo real

La Comunicación web en tiempo real (WebRTC) se refiere a un mecanismo que nos permite notificar instantáneamente a los usuarios de un evento en una página web sin necesidad de que ellos actualicen la página. Hay muchos usos para la WebRTC, como el chat en tiempo real y los mensajes instantáneos.

Hay varios modos de comunicación entre clientes web y servidores.

Flujo HTTP regular

  1. El cliente solicita una página web al servidor.
  2. El servidor responde en consecuencia.
  3. El servidor envía la respuesta de vuelta al cliente.

Flujo de solicitud y respuesta HTTP

Como las solicitudes HTTP son sin estado, es decir, la conexión HTTP se termina después de cada solicitud, el servidor y el navegador no tienen conocimiento mutuo hasta que se realiza la siguiente solicitud para actualizar la información relevante. En este punto, no es difícil pensar en una solución simple donde el navegador puede realizar solicitudes periódicas para simular efectos en tiempo real. Esto se llama sondeo (polling).

Flujo de sondeo

  1. El cliente envía una solicitud de conexión al servidor utilizando HTTP regular.
  2. El cliente ejecuta un script de sondeo de JavaScript incrustado en la página web para enviar periódicamente solicitudes al servidor (por ejemplo, cada 5 segundos) para recuperar información.
  3. El servidor responde a cada solicitud y envía de vuelta la información correspondiente, al igual que una solicitud HTTP normal.

Diagrama de comunicación de sondeo

El sondeo nos permite obtener información casi en tiempo real. Sin embargo, las solicitudes frecuentes del navegador al servidor como resultado del sondeo pueden llevar a ineficiencias de rendimiento. Para mitigar estos problemas, se propuso un método alternativo. En lugar de responder inmediatamente a las solicitudes del cliente, los servidores esperan hasta que hay un cambio de datos (o un tiempo de espera) antes de devolver una respuesta. Este enfoque maximiza la validez de la conexión para reducir el número de solicitudes en el sondeo. Este método se llama sondeo prolongado, o Long-Polling.

Flujo de sondeo prolongado

  1. El cliente solicita una página web al servidor utilizando HTTP regular.
  2. El cliente ejecuta un script de JavaScript incrustado en la página web para enviar datos y solicitar información al servidor.
  3. En lugar de responder inmediatamente a la solicitud del cliente, el servidor espera una actualización válida.
  4. Cuando la información se actualiza y es válida, el servidor envía los datos al cliente.
  5. Al recibir la notificación del servidor, el cliente envía inmediatamente una nueva solicitud para iniciar la siguiente ronda de sondeo.

Diagrama de flujo de sondeo prolongado

Los métodos mencionados anteriormente se utilizan comúnmente para implementar la comunicación web en tiempo real. Sin embargo, después de la introducción de HTML5, tenemos opciones mejores disponibles. En HTML5, podemos utilizar Server-Sent Events (SSE) o WebSocket. SSE está diseñado específicamente para la transmisión de datos del servidor al cliente, lo que a menudo es suficiente para escenarios como la difusión de información de partidos o cambios en los precios de las acciones.

Flujo de Server-Sent Events

  1. El cliente solicita una página web al servidor utilizando HTTP regular.
  2. El cliente establece una conexión con el servidor utilizando JavaScript incrustado en la página web.
  3. Cuando el servidor tiene actualizaciones, envía un evento al cliente.

Diagrama de flujo de comunicación de SSE

Si SSE no satisface nuestras necesidades, podemos utilizar WebSocket en su lugar. Con WebSocket, se establece un canal de comunicación de doble sentido entre el navegador y el servidor, lo que permite el intercambio de mensajes bidireccionales en tiempo real, similar a utilizar sockets TCP.

Comparación simple entre SSE y WebSocket

  • WebSocket es un canal de comunicación de doble sentido que admite la comunicación bidireccional y tiene características más avanzadas. SSE es un canal unidireccional, donde el servidor solo puede enviar datos al navegador.
  • WebSocket es un nuevo protocolo y requiere soporte del lado del servidor, mientras que SSE se implementa sobre el protocolo HTTP y está soportado por el software del servidor existente.
  • SSE es un protocolo ligero y relativamente simple, mientras que WebSocket es un protocolo más pesado y relativamente más complejo.

Con la comprensión de estos mecanismos para implementar la comunicación web en tiempo real, ahora utilizaremos SSE para implementar un salón de chat en línea simple.

Implementación de un salón de chat en línea basado en SSE

Hay varias maneras de enviar mensajes en un salón de chat en línea, y en este curso, usaremos Server-Sent Events (SSE) para lograr esto. Para facilitar la recepción de mensajes, aprovecharemos la funcionalidad de publicación/suscripción (pub/sub) de Redis para recibir y enviar mensajes. En el lado del servidor web, usaremos Flask para la implementación.

Cómo funciona SSE

En lecciones anteriores, aprendimos que SSE se basa en HTTP. Entonces, ¿cómo sabe el navegador que este es un flujo de eventos enviados por el servidor? En realidad es bastante simple: simplemente establece el encabezado Content-Type de la solicitud HTTP en text/event-stream. SSE esencialmente implica que el navegador envía una solicitud HTTP al servidor, y luego el servidor empuja información al navegador de manera continua y unidireccional. El formato de esta información también es muy sencillo, con el prefijo "data:" seguido del contenido del mensaje, y terminando con "\n\n".

Funcionalidad de publicación/suscripción de Redis

Redis es una base de datos en memoria popular que se puede utilizar para el almacenamiento en caché, la cola y otros servicios. En este curso, usaremos la funcionalidad de publicación/suscripción de Redis. En pocas palabras, la característica de suscripción nos permite suscribirnos a varios canales, y cada vez que se publican nuevos mensajes en estos canales, los recibimos automáticamente. Cuando el servidor recibe un mensaje enviado por el navegador a través de una solicitud POST, publica estos mensajes en canales específicos. Posteriormente, los clientes que se han suscrito a estos canales recibirán automáticamente estos mensajes, los cuales luego se empujarán a los clientes a través de SSE.

Implementación de funciones

Después del análisis anterior, todo el proceso del salón de chat ya está claro. Ahora comencemos a implementar la funcionalidad de este salón de chat.

Crea un archivo llamado app.py en el directorio ~/project y escribe el siguiente código fuente:

import datetime
import flask
import redis

app = flask.Flask("labex-sse-chat")
app.secret_key = "labex"
app.config["DEBUG"] = True
r = redis.StrictRedis()


## Función de la ruta principal
@app.route("/")
def home():
    ## Si el usuario no está logueado, redirige a la página de inicio de sesión
    if "user" not in flask.session:
        return flask.redirect("/login")
    user = flask.session["user"]
    return flask.render_template("index.html", user=user)


## Generador de mensajes
def event_stream():
    ## Crea un sistema de publicación-suscripción
    pubsub = r.pubsub()
    ## Utiliza el método subscribe del sistema de publicación-suscripción para suscribirse a un canal
    pubsub.subscribe("chat")
    for message in pubsub.listen():
        data = message["data"]
        if type(data) == bytes:
            yield "data: {}\n\n".format(data.decode())


## Función de inicio de sesión, se requiere inicio de sesión para la primera visita
@app.route("/login", methods=["GET", "POST"])
def login():
    if flask.request.method == "POST":
        ## Almacena el nombre de usuario en el diccionario de sesión y luego redirige a la página principal
        flask.session["user"] = flask.request.form["user"]
        return flask.redirect("/")
    return flask.render_template("login.html")


## Recibe los datos enviados por JavaScript utilizando el método POST
@app.route("/post", methods=["POST"])
def post():
    message = flask.request.form["message"]
    user = flask.session.get("user", "anonymous")
    now = datetime.datetime.now().replace(microsecond=0).time()
    r.publish("chat", "[{}] {}: {}\n".format(now.isoformat(), user, message))
    return flask.Response(status=204)


## Interfaz de flujo de eventos
@app.route("/stream")
def stream():
    ## El objeto de retorno de esta función de ruta debe ser de tipo text/event-stream
    return flask.Response(event_stream(), mimetype="text/event-stream")


## Ejecuta la aplicación Flask
app.run()

En el código anterior, usamos la característica de sesión de Flask para almacenar la información de inicio de sesión del usuario, la característica de publicación-suscripción de Redis para recibir y enviar mensajes, y SSE para implementar la transmisión de mensajes.

Aquí:

  • La función event_stream es un generador de mensajes que recupera mensajes de Redis de manera continua y los empuja al cliente.
  • La función stream es una interfaz de flujo de eventos que devuelve un objeto de tipo text/event-stream, que es el flujo de eventos de SSE.
  • La función post es una interfaz que recibe los datos enviados por JavaScript utilizando el método POST. Publica los datos recibidos en el canal chat de Redis.
  • La función login es una función de inicio de sesión que se requiere para la primera visita. Después de un inicio de sesión exitoso, el nombre de usuario se almacena en el diccionario de sesión y luego se redirige a la página principal.
  • La función home es la función de ruta de la página principal. Si el usuario no está logueado, redirigirá a la página de inicio de sesión. Si el usuario está logueado, renderizará la plantilla index.html.

Implementación de la plantilla login.html

Primero, crea un directorio templates bajo ~/project para almacenar los archivos HTML necesarios. En el directorio templates, crea un archivo login.html y escribe el siguiente código:

<!doctype html>
<title>Inicio de sesión del chat en línea</title>
<style>
  body {
    max-width: 500px;
    margin: auto;
    padding: 1em;
    background: black;
    color: #fff;
    font:
      16px/1.6 menlo,
      monospace;
  }
</style>

<body>
  <form action="" method="post">
    Nombre de usuario: <input name="user" />
    <input type="submit" value="iniciar sesión" />
  </form>
</body>

En el archivo login.html, utilizamos la funcionalidad de plantillas de Flask y usamos {{ user }} para representar el nombre del usuario, que se obtiene de flask.session.

Implementación de la plantilla index.html

A continuación, creemos un archivo index.html en el directorio templates para implementar la página del salón de chat. Escribe el siguiente código en él:

<!doctype html>
<title>Chat en línea</title>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<style>
  body {
    max-width: 500px;
    margin: auto;
    padding: 1em;
    background: black;
    color: #fff;
    font:
      16px/1.6 menlo,
      monospace;
  }
</style>
<p><b>Hola, {{ user }}!</b></p>
<p>Mensaje: <input id="in" /></p>
<pre id="out"></pre>
<script>
  function sse() {
    // Conectarse al flujo de eventos del servidor
    var source = new EventSource("/stream");
    var out = document.getElementById("out");
    source.onmessage = function (e) {
      out.innerHTML = e.data + "\n" + out.innerHTML;
    };
  }
  // Enviar mensaje al servidor
  $("#in").keyup(function (e) {
    if (e.keyCode == 13) {
      $.post("/post", { message: $(this).val() });
      $(this).val("");
    }
  });
  sse();
</script>

En el archivo index.html, usamos el método $.post de jQuery para enviar mensajes al servidor y usamos EventSource para recibir mensajes de notificación del servidor.

Ejecución y pruebas

Como se está usando Redis, debes iniciar el servicio de Redis en tu entorno y descargar el módulo de redis necesario para conectar Python al servidor de Redis:

pip install redis
sudo service redis-server start

A continuación, puedes ejecutar nuestro salón de chat:

cd ~/project
python app.py

Luego, puedes acceder a http://localhost:5000 en tu navegador, ingresar un nombre de usuario aleatorio y entrar al salón de chat.

También puedes abrir otra ventana del navegador en modo privado para entrar al salón de chat, y luego podrás chatear en ambas ventanas.

Captura de pantalla de la interfaz del salón de chat

Resumen

Este proyecto utiliza SSE para implementar características de comunicación web en tiempo real y se basa en Flask y Redis para crear un salón de chat en línea. El objetivo es que todos logren comprender el proceso de comunicación bajo el protocolo HTTP a través de esta sección del experimento.

✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar