Sala de Chat em Tempo Real com Flask e Redis

PythonBeginner
Pratique Agora

Introdução

Este projeto utiliza SSE (Server-Sent Events) e Redis para implementar uma sala de chat online. É necessário conhecimento prévio da sintaxe de Python e JavaScript, bem como uma compreensão básica do uso de Flask e Redis.

Nesta seção do experimento, aprenderemos e praticaremos os seguintes conceitos:

  • Comunicação web em tempo real
  • O funcionamento do SSE
  • Utilização do Redis

👀 Pré-visualização

Pré-visualização da interface da sala de chat online

🎯 Tarefas

Neste projeto, você aprenderá:

  • Como criar uma sala de chat online simples usando Flask e SSE
  • Como implementar a funcionalidade de login do usuário
  • Como usar o Redis para armazenamento e recuperação de mensagens

🏆 Conquistas

Após concluir este projeto, você será capaz de:

  • Configurar SSE para comunicação em tempo real em uma aplicação web
  • Usar o Redis para armazenar e recuperar mensagens em uma aplicação de sala de chat
  • Implementar a funcionalidade de login do usuário em Flask

Comunicação Web em Tempo Real

A Comunicação Web em Tempo Real (WebRTC) refere-se a um mecanismo que nos permite notificar instantaneamente os usuários sobre um evento em uma página web sem a necessidade de que eles atualizem a página. Existem muitos usos para WebRTC, como bate-papo em tempo real e mensagens instantâneas.

Existem várias maneiras de comunicação entre clientes web e servidores.

Fluxo HTTP Regular

  1. O cliente solicita uma página web do servidor.
  2. O servidor responde de acordo.
  3. O servidor envia a resposta de volta para o cliente.

Fluxo de requisição e resposta HTTP

Como as requisições HTTP são stateless (sem estado), o que significa que a conexão HTTP é encerrada após cada requisição, o servidor e o navegador não têm conhecimento um do outro até que a próxima requisição seja feita para atualizar as informações relevantes. Neste ponto, não é difícil pensar em uma solução simples onde o navegador pode fazer requisições periódicas para simular efeitos em tempo real. Isso é chamado de polling.

Fluxo de Polling

  1. O cliente envia uma requisição de conexão para o servidor usando HTTP regular.
  2. O cliente executa um script JavaScript de polling embutido na página web para enviar periodicamente requisições ao servidor (por exemplo, a cada 5 segundos) para recuperar informações.
  3. O servidor responde a cada requisição e envia de volta as informações correspondentes, assim como uma requisição HTTP normal.

Diagrama de comunicação por Polling

O polling nos permite obter informações quase em tempo real. No entanto, requisições frequentes do navegador para o servidor como resultado do polling podem levar a ineficiências de desempenho. Para mitigar esses problemas, foi proposto um método alternativo. Em vez de responder imediatamente às requisições do cliente, os servidores esperam até que haja uma mudança de dados (ou timeout) antes de retornar uma resposta. Essa abordagem maximiza a validade da conexão para reduzir o número de requisições no polling. Este método é chamado de long polling, ou Long-Polling.

Fluxo de Long-Polling

  1. O cliente solicita uma página web do servidor usando HTTP regular.
  2. O cliente executa um script JavaScript embutido na página web para enviar dados e solicitar informações ao servidor.
  3. Em vez de responder imediatamente à requisição do cliente, o servidor espera por uma atualização válida.
  4. Quando a informação é atualizada e válida, o servidor envia os dados para o cliente.
  5. Ao receber a notificação do servidor, o cliente envia imediatamente uma nova requisição para iniciar a próxima rodada de polling.

Diagrama de Fluxo de Long Polling

Os métodos mencionados acima são comumente usados para implementar comunicação web em tempo real. No entanto, após a introdução do HTML5, temos melhores opções disponíveis. No HTML5, podemos usar Server-Sent Events (SSE) ou WebSocket. SSE é especificamente projetado para envio de dados do servidor para o cliente, o que é frequentemente suficiente para cenários como transmissão de informações de partidas ou alterações de preços de ações.

Fluxo de Server-Sent Events

  1. O cliente solicita uma página web do servidor usando HTTP regular.
  2. O cliente estabelece uma conexão com o servidor usando JavaScript embutido na página web.
  3. Quando o servidor tem atualizações, ele envia um evento para o cliente.

Diagrama de fluxo de comunicação SSE

Se SSE não atender às nossas necessidades, podemos usar WebSocket em vez disso. Com WebSocket, um canal de comunicação full-duplex é estabelecido entre o navegador e o servidor, permitindo a troca de mensagens bidirecional em tempo real, semelhante ao uso de sockets TCP.

Comparação Simples entre SSE e WebSocket

  • WebSocket é um canal de comunicação full-duplex que suporta comunicação bidirecional e possui recursos mais avançados. SSE é um canal unidirecional, onde o servidor só pode enviar dados para o navegador.
  • WebSocket é um novo protocolo e requer suporte do lado do servidor, enquanto SSE é implantado em cima do protocolo HTTP e é suportado pelo software de servidor existente.
  • SSE é um protocolo leve e relativamente mais simples, enquanto WebSocket é um protocolo mais pesado e relativamente mais complexo.

Com a compreensão desses mecanismos para implementar comunicação web em tempo real, agora usaremos SSE para implementar uma sala de chat online simples.

Implementando uma Sala de Chat Online Baseada em SSE

Existem várias maneiras de enviar mensagens em uma sala de chat online, e neste curso, usaremos Server-Sent Events (SSE) para conseguir isso. Para facilitar a recepção de mensagens, aproveitaremos a funcionalidade pub/sub (publicar/inscrever-se) do Redis para receber e enviar mensagens. No lado do servidor web, usaremos Flask para a implementação.

Como o SSE Funciona

Nas lições anteriores, aprendemos que SSE é baseado em HTTP. Então, como o navegador sabe que este é um fluxo de eventos enviados pelo servidor? É realmente muito simples – basta definir o cabeçalho Content-Type da requisição HTTP para text/event-stream. SSE essencialmente envolve o navegador enviando uma requisição HTTP para o servidor, e então o servidor continuamente enviando informações para o navegador de maneira unidirecional. O formato dessas informações também é muito direto, com o prefixo "data:" seguido pelo conteúdo da mensagem e terminando com "\n\n".

Funcionalidade Pub/Sub do Redis

Redis é um banco de dados in-memory (na memória) popular que pode ser usado para caching (armazenamento em cache), queuing (enfileiramento) e outros serviços. Neste curso, usaremos a funcionalidade de publicar/inscrever-se do Redis. Simplificando, o recurso de inscrição nos permite nos inscrever em vários canais e, sempre que novas mensagens são publicadas nesses canais, as recebemos automaticamente. Quando o servidor recebe uma mensagem enviada pelo navegador via uma requisição POST, ele publica essas mensagens em canais específicos. Posteriormente, os clientes que se inscreveram nesses canais receberão automaticamente essas mensagens, que serão então enviadas aos clientes através do SSE.

Implementação da Função

Após a análise acima, todo o processo da sala de chat já está claro. Agora, vamos começar a implementar a funcionalidade desta sala de chat.

Crie um arquivo chamado app.py no diretório ~/project e insira o seguinte código-fonte:

import datetime
import flask
import redis

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


## Função da rota inicial
@app.route("/")
def home():
    ## Se o usuário não estiver logado, redireciona para a página de login
    if "user" not in flask.session:
        return flask.redirect("/login")
    user = flask.session["user"]
    return flask.render_template("index.html", user=user)


## Gerador de mensagens
def event_stream():
    ## Cria um sistema de publicar-inscrever-se
    pubsub = r.pubsub()
    ## Usa o método subscribe do sistema de publicar-inscrever-se para se inscrever em um canal
    pubsub.subscribe("chat")
    for message in pubsub.listen():
        data = message["data"]
        if type(data) == bytes:
            yield "data: {}\n\n".format(data.decode())


## Função de login, login é necessário para a primeira visita
@app.route("/login", methods=["GET", "POST"])
def login():
    if flask.request.method == "POST":
        ## Armazena o nome de usuário no dicionário de sessão e, em seguida, redireciona para a página inicial
        flask.session["user"] = flask.request.form["user"]
        return flask.redirect("/")
    return flask.render_template("login.html")


## Recebe dados enviados por JavaScript usando o 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)


## Interface de fluxo de eventos
@app.route("/stream")
def stream():
    ## O objeto de retorno desta função de rota deve ser do tipo text/event-stream
    return flask.Response(event_stream(), mimetype="text/event-stream")


## Executa a aplicação Flask
app.run()

No código acima, usamos o recurso de sessão do Flask para armazenar informações de login do usuário, o recurso de publicar-inscrever-se do Redis para receber e enviar mensagens e SSE para implementar o envio de mensagens.

Aqui:

  • A função event_stream é um gerador de mensagens que recupera continuamente mensagens do Redis e as envia para o cliente.
  • A função stream é uma interface de fluxo de eventos que retorna um objeto do tipo text/event-stream, que é o fluxo de eventos SSE.
  • A função post é uma interface que recebe dados enviados por JavaScript usando o método POST. Ela publica os dados recebidos no canal chat no Redis.
  • A função login é uma função de login que é necessária para a primeira visita. Após um login bem-sucedido, o nome de usuário é armazenado no dicionário de sessão e, em seguida, redirecionado para a página inicial.
  • A função home é a função da rota da página inicial. Se o usuário não estiver logado, ele será redirecionado para a página de login. Se o usuário estiver logado, ele renderizará o template index.html.

Implementando o Template login.html

Primeiramente, crie um diretório templates em ~/project para armazenar os arquivos HTML necessários. No diretório templates, crie um arquivo login.html e escreva o seguinte código:

<!doctype html>
<title>Online Chat Login</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">
    User Name: <input name="user" />
    <input type="submit" value="login" />
  </form>
</body>

No arquivo login.html, utilizamos a funcionalidade de template do Flask e usamos {{ user }} para representar o nome do usuário, que é recuperado de flask.session.

Implementando o Template index.html

Em seguida, vamos criar um arquivo index.html no diretório templates para implementar a página da sala de chat. Escreva o seguinte código nele:

<!doctype html>
<title>Online Chat</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>Hi, {{ user }}!</b></p>
<p>Message: <input id="in" /></p>
<pre id="out"></pre>
<script>
  function sse() {
    // Connect to server's event stream
    var source = new EventSource("/stream");
    var out = document.getElementById("out");
    source.onmessage = function (e) {
      out.innerHTML = e.data + "\n" + out.innerHTML;
    };
  }
  // POST message to server
  $("#in").keyup(function (e) {
    if (e.keyCode == 13) {
      $.post("/post", { message: $(this).val() });
      $(this).val("");
    }
  });
  sse();
</script>

No arquivo index.html, usamos o método $.post do jQuery para enviar mensagens para o servidor e usamos EventSource para receber mensagens do tipo server-push.

Executando e Testando

Como o Redis está sendo usado, você precisa iniciar o serviço Redis no seu ambiente e baixar o módulo redis necessário para conectar o Python ao servidor Redis:

pip install redis
sudo service redis-server start

Em seguida, você pode executar nossa sala de chat:

cd ~/project
python app.py

Então, você pode acessar http://localhost:5000 no seu navegador, inserir um nome de usuário aleatório e entrar na sala de chat.

Você também pode abrir outra janela do navegador no modo privado para entrar na sala de chat e, em seguida, pode conversar em ambas as janelas.

Captura de tela da interface da sala de chat

Resumo

Este projeto utiliza SSE para implementar recursos de comunicação web em tempo real e se baseia em Flask e Redis para criar uma sala de chat online. O objetivo é que todos obtenham uma compreensão do processo de comunicação sob o protocolo HTTP através desta seção do experimento.

✨ Verificar Solução e Praticar✨ Verificar Solução e Praticar✨ Verificar Solução e Praticar✨ Verificar Solução e Praticar✨ Verificar Solução e Praticar✨ Verificar Solução e Praticar