Tutoriais

Eventos enviados pelo servidor para notificações de resolução CAPTCHA em tempo real

A pesquisa de res.php a cada 5 segundos funciona, mas desperdiça solicitações e adiciona latência. Os eventos enviados pelo servidor (SSE) permitem que seu servidor envie soluções CAPTCHA para clientes conectados no instante em que eles chegam – zero solicitações desperdiçadas, entrega em menos de um segundo.

Como o SSE se adapta ao fluxo de trabalho CAPTCHA

[Client] ← SSE stream ← [Your Server] ← Callback ← [CaptchaAI]
   ↓                          ↑
   Submit task → [CaptchaAI] ──┘ (pingback URL points to your server)
  1. O cliente se conecta ao seu endpoint SSE (conexão HTTP persistente)
  2. O cliente envia uma tarefa CAPTCHA para CaptchaAI com pingback apontando para seu servidor
  3. CaptchaAI resolve e envia o resultado para seu endpoint de retorno de chamada
  4. Seu servidor envia o resultado através do fluxo SSE para o cliente

Implementação Completa - Python (Flask)

Servidor

import os
import queue
import threading
import requests
from flask import Flask, Response, request, jsonify

app = Flask(__name__)

API_KEY = os.environ["CAPTCHAAI_API_KEY"]

# Per-client event queues: client_id -> Queue
client_queues = {}
queues_lock = threading.Lock()


@app.route("/events/<client_id>")
def sse_stream(client_id):
    """SSE endpoint — clients connect here for real-time results."""
    q = queue.Queue()

    with queues_lock:
        client_queues[client_id] = q

    def generate():
        try:
            while True:
                # Block until a result arrives (timeout for keepalive)
                try:
                    data = q.get(timeout=30)
                    yield f"event: captcha-solved\ndata: {data}\n\n"
                except queue.Empty:
                    # Send keepalive comment to prevent connection timeout
                    yield ": keepalive\n\n"
        finally:
            with queues_lock:
                client_queues.pop(client_id, None)

    return Response(
        generate(),
        mimetype="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "X-Accel-Buffering": "no"  # Disable nginx buffering
        }
    )


@app.route("/submit", methods=["POST"])
def submit_captcha():
    """Submit a CAPTCHA task with callback to this server."""
    data = request.json
    client_id = data["client_id"]
    sitekey = data["sitekey"]
    pageurl = data["pageurl"]

    callback_url = f"{request.host_url}callback?client_id={client_id}"

    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "pingback": callback_url,
        "json": 1
    })
    result = resp.json()

    if result.get("status") == 1:
        return jsonify({"task_id": result["request"]})
    return jsonify({"error": result.get("request")}), 400


@app.route("/callback")
def captcha_callback():
    """Receive CaptchaAI callback and push to SSE stream."""
    client_id = request.args.get("client_id")
    task_id = request.args.get("id")
    solution = request.args.get("code")

    import json
    message = json.dumps({
        "task_id": task_id,
        "solution": solution
    })

    with queues_lock:
        q = client_queues.get(client_id)
        if q:
            q.put(message)

    return "OK", 200


if __name__ == "__main__":
    app.run(port=5000, threaded=True)

Cliente do navegador

<!DOCTYPE html>
<html>
<body>
  <button onclick="submitCaptcha()">Solve CAPTCHA</button>
  <div id="results"></div>

  <script>
    const clientId = crypto.randomUUID();
    const resultsDiv = document.getElementById("results");

    // Connect SSE stream
    const eventSource = new EventSource(`/events/${clientId}`);

    eventSource.addEventListener("captcha-solved", (event) => {
      const data = JSON.parse(event.data);
      resultsDiv.innerHTML += `<p>Task ${data.task_id}: ${data.solution.substring(0, 30)}...</p>`;
    });

    eventSource.onerror = () => {
      console.log("SSE connection lost, reconnecting...");
    };

    async function submitCaptcha() {
      const response = await fetch("/submit", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          client_id: clientId,
          sitekey: "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
          pageurl: "https://example.com"
        })
      });
      const result = await response.json();
      resultsDiv.innerHTML += `<p>Submitted: ${result.task_id}</p>`;
    }
  </script>
</body>
</html>

Implementação Completa - JavaScript (Express)

Servidor

const express = require("express");
const axios = require("axios");

const app = express();
app.use(express.json());

const API_KEY = process.env.CAPTCHAAI_API_KEY;
const BASE_URL = process.env.BASE_URL || "http://localhost:3000";

// Per-client SSE connections: clientId -> Response object
const clients = new Map();

// SSE endpoint
app.get("/events/:clientId", (req, res) => {
  const clientId = req.params.clientId;

  res.writeHead(200, {
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
    "X-Accel-Buffering": "no",
  });

  clients.set(clientId, res);

  // Keepalive every 30 seconds
  const keepalive = setInterval(() => {
    res.write(": keepalive\n\n");
  }, 30000);

  req.on("close", () => {
    clearInterval(keepalive);
    clients.delete(clientId);
  });
});

// Submit CAPTCHA
app.post("/submit", async (req, res) => {
  const { client_id, sitekey, pageurl } = req.body;
  const callbackUrl = `${BASE_URL}/callback?client_id=${client_id}`;

  try {
    const resp = await axios.post("https://ocr.captchaai.com/in.php", null, {
      params: {
        key: API_KEY,
        method: "userrecaptcha",
        googlekey: sitekey,
        pageurl: pageurl,
        pingback: callbackUrl,
        json: 1,
      },
    });

    if (resp.data.status === 1) {
      return res.json({ task_id: resp.data.request });
    }
    res.status(400).json({ error: resp.data.request });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// CaptchaAI callback → push to SSE
app.get("/callback", (req, res) => {
  const clientId = req.query.client_id;
  const taskId = req.query.id;
  const solution = req.query.code;

  const clientRes = clients.get(clientId);
  if (clientRes) {
    const data = JSON.stringify({ task_id: taskId, solution: solution });
    clientRes.write(`event: captcha-solved\ndata: ${data}\n\n`);
  }

  res.sendStatus(200);
});

app.listen(3000, () => console.log("SSE server running on :3000"));

SSE vs WebSocket vs Pesquisa

Recurso SSE WebSocket Votação
Direção Servidor → Cliente Bidirecional Cliente → Servidor
Protocolo HTTP/1.1+ WS/WSS HTTP
Reconectar automaticamente Integrado Manuais N/A
Suporte ao navegador Tudo moderno Tudo moderno Todos
Complexidade Baixo Médio Baixo
Solicitações desperdiçadas Nenhum Nenhum Muitos
mais adequado para resultados CAPTCHA Sim Exagero Funciona, mas é um desperdício

O SSE é ideal para resultados CAPTCHA porque os dados fluem apenas de servidor para cliente.

Considerações de produção

Dimensionamento com múltiplas instâncias de servidor

As conexões SSE são stateful – se o seu servidor tiver várias instâncias atrás de um balanceador de carga, o retorno de chamada poderá atingir uma instância diferente daquela que contém a conexão SSE do cliente.

Solução: use o Redis Pub/Sub como um barramento de mensagens:

# Callback handler publishes to Redis
import redis
r = redis.Redis()
r.publish(f"captcha:{client_id}", json.dumps(message))

# SSE handler subscribes to Redis
pubsub = r.pubsub()
pubsub.subscribe(f"captcha:{client_id}")
for msg in pubsub.listen():
    if msg["type"] == "message":
        yield f"data: {msg['data'].decode()}\n\n"

Limites de conexão

Os navegadores limitam as conexões SSE a 6 por domínio (HTTP/1.1). Use HTTP/2 para limites mais altos ou multiplexe resultados de múltiplas tarefas por meio de uma única conexão SSE por cliente.

Solução de problemas

Problema Causa Correção
A conexão SSE cai a cada 30s Tempo limite do balanceador Proxy/load Envie comentários de manutenção de atividade; aumentar o tempo limite do proxy
Os resultados não chegam Retorno de chamada atingindo instância de servidor diferente Adicionar Redis Pub/Sub entre retorno de chamada e manipuladores SSE
Navegador mostra erros no console Cabeçalhos CORS ausentes Adicione o cabeçalho Access-Control-Allow-Origin ao endpoint SSE
Múltiplas reconexões Servidor enviando SSE malformado Certifique-se de que \n\n encerre cada evento; validar formato de dados

Perguntas frequentes

O SSE funciona por trás do Cloudflare?

Sim, mas a Cloudflare pode armazenar respostas em buffer. Desative o buffer de resposta com o cabeçalho X-Accel-Buffering: no ou use o modo de streaming da Cloudflare.

Quantas conexões SSE simultâneas um servidor pode manipular?

O Node.js lida facilmente com mais de 10.000 conexões SSE simultâneas, já que cada uma é uma conexão HTTP leve e mantida ativa. Python com threading é mais limitado – use uma estrutura assíncrona (FastAPI com asyncio) para alta simultaneidade.

Devo usar o SSE para clientes que não são navegadores?

Para ferramentas CLI ou serviços de back-end, o tratamento direto de retorno de chamada ou abordagens baseadas em fila são mais simples. O SSE é mais útil ao enviar resultados para painéis baseados em navegador ou aplicativos da web.

Próximas etapas

Transmita soluções CAPTCHA em tempo real -obtenha sua chave API CaptchaAIe conecte o SSE ao seu pipeline de retorno de chamada.

Guias relacionados:

Os comentários estão desativados para este artigo.