Hướng Dẫn Thực Hành

Sự kiện do máy chủ gửi để giải quyết thông báo CAPTCHA thời gian thực

Việc thăm dò res.php cứ sau 5 giây có hiệu quả, nhưng nó sẽ lãng phí các yêu cầu và tăng thêm độ trễ. Sự kiện do máy chủ gửi (SSE) cho phép máy chủ của bạn đẩy các giải pháp CAPTCHA tới các máy khách được kết nối ngay khi chúng đến — không lãng phí yêu cầu, phân phối dưới giây.

SSE phù hợp với quy trình làm việc của CAPTCHA như thế nào

[Client] ← SSE stream ← [Your Server] ← Callback ← [CaptchaAI]
   ↓                          ↑
   Submit task → [CaptchaAI] ──┘ (pingback URL points to your server)
  1. Máy khách kết nối với điểm cuối SSE của bạn (kết nối HTTP liên tục)
  2. Khách hàng gửi tác vụ CAPTCHA tới CaptchaAI với pingback trỏ đến máy chủ của bạn
  3. CaptchaAI giải quyết và gửi kết quả đến điểm cuối gọi lại của bạn
  4. Máy chủ của bạn đẩy kết quả qua luồng SSE tới máy khách

Triển khai đầy đủ — Python (Flask)

Máy chủ

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)

Máy khách trình duyệt

<!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>

Triển khai đầy đủ — JavaScript (Express)

Máy chủ

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 Bỏ phiếu

tính năng SSE WebSocket Bỏ phiếu
Hướng Máy chủ và Máy khách hai chiều Máy khách và máy chủ
Giao thức HTTP/1.1+ WS/WSS HTTP
Tự động kết nối lại Tích hợp sẵn Hướng dẫn sử dụng N/A
Hỗ trợ trình duyệt Tất cả đều hiện đại Tất cả đều hiện đại Tất cả
Độ phức tạp Thấp Trung bình Thấp
Yêu cầu lãng phí không có không có Nhiều
Tốt nhất cho kết quả CAPTCHA Quá mức cần thiết Có tác dụng nhưng lãng phí

SSE lý tưởng cho kết quả CAPTCHA vì dữ liệu chỉ truyền từ máy chủ đến máy khách.

Cân nhắc sản xuất

Mở rộng quy mô với nhiều phiên bản máy chủ

Các kết nối SSE có trạng thái — nếu máy chủ của bạn có nhiều phiên bản phía sau bộ cân bằng tải, lệnh gọi lại có thể gặp một phiên bản khác với phiên bản đang giữ kết nối SSE của máy khách.

Giải pháp: Sử dụng Redis Pub/Sub làm bus tin nhắn:

# 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"

Giới hạn kết nối

Các trình duyệt giới hạn kết nối SSE ở mức 6 trên mỗi tên miền (HTTP/1.1). Sử dụng HTTP/2 để có giới hạn cao hơn hoặc ghép nhiều kết quả tác vụ thông qua một kết nối SSE cho mỗi máy khách.

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
Kết nối SSE giảm sau mỗi 30 giây Hết thời gian chờ cân bằng Proxy/load Gửi ý kiến lưu giữ; tăng thời gian chờ proxy
Kết quả không đến Gọi lại đánh vào phiên bản máy chủ khác Thêm Redis Pub/Sub giữa trình xử lý gọi lại và SSE
Trình duyệt hiển thị lỗi trong bảng điều khiển Tiêu đề CORS bị thiếu Thêm tiêu đề Access-Control-Allow-Origin vào điểm cuối SSE
Nhiều kết nối lại Máy chủ gửi SSE không đúng định dạng Đảm bảo \n\n chấm dứt mỗi sự kiện; xác thực định dạng dữ liệu

Câu hỏi thường gặp

SSE có hoạt động sau Cloudflare không?

Có, nhưng Cloudflare có thể đệm phản hồi. Vô hiệu hóa bộ đệm phản hồi bằng tiêu đề X-Accel-Buffering: no hoặc sử dụng chế độ phát trực tuyến của Cloudflare.

Một máy chủ có thể xử lý bao nhiêu kết nối SSE đồng thời?

Node.js xử lý hơn 10.000 kết nối SSE đồng thời một cách dễ dàng vì mỗi kết nối đều là kết nối HTTP duy trì nhẹ. Python có luồng bị hạn chế hơn - sử dụng khung không đồng bộ (FastAPI với asyncio) để có tính đồng thời cao.

Tôi có nên sử dụng SSE cho các máy khách không có trình duyệt không?

Đối với các công cụ CLI hoặc dịch vụ phụ trợ, việc xử lý gọi lại trực tiếp hoặc các phương pháp tiếp cận dựa trên hàng đợi sẽ đơn giản hơn. SSE hữu ích nhất khi đẩy kết quả lên bảng điều khiển hoặc ứng dụng web dựa trên trình duyệt.

Các bước tiếp theo

Truyền phát các giải pháp CAPTCHA trong thời gian thực —lấy khóa API CaptchaAI của bạnvà kết nối SSE với đường dẫn gọi lại của bạn.

Hướng dẫn liên quan:

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