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)
- 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)
- Khách hàng gửi tác vụ CAPTCHA tới CaptchaAI với
pingbacktrỏ đến máy chủ của bạn - CaptchaAI giải quyết và gửi kết quả đến điểm cuối gọi lại của bạn
- 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 | Có | 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: