Polling res.php setiap 5 detik berfungsi, tetapi menyia-nyiakan request dan menambah latensi. Peristiwa Terkirim Server (SSE) memungkinkan server Anda mengirimkan solusi CAPTCHA ke klien yang terhubung begitu klien tiba – tanpa request yang sia-sia, pengiriman dalam hitungan sepersekian detik.
Bagaimana SSE Sesuai dengan Alur Kerja CAPTCHA
[Client] ← SSE stream ← [Your Server] ← Callback ← [CaptchaAI]
↓ ↑
Submit task → [CaptchaAI] ──┘ (pingback URL points to your server)
- Klien terhubung ke endpoint SSE Anda (koneksi HTTP persisten)
- Klien mengirimkan tugas CAPTCHA ke CaptchaAI dengan
pingbackmenunjuk ke server Anda - CaptchaAI memecahkan dan mengirimkan hasilnya ke endpoint callback Anda
- Server Anda mengirimkan hasilnya melalui aliran SSE ke klien
Implementasi Penuh – Python (Flask)
pelayan
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)
Klien Peramban
<!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>
Implementasi Penuh – JavaScript (Ekspres)
pelayan
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 Polling
| Fitur | SSE | Soket Web | pemungutan suara |
|---|---|---|---|
| Arah | Server -> Klien | Dua arah | Klien -> Server |
| Protokol | HTTP/1.1+ | WS/WSS | HTTP |
| Sambungkan kembali secara otomatis | Bawaan | petunjuk | N/A |
| Dukungan peramban | Semuanya modern | Semuanya modern | Semua |
| Kompleksitas | Rendah | Sedang | Rendah |
| Request yang sia-sia | Tidak ada | Tidak ada | Banyak |
| Terbaik untuk hasil CAPTCHA | Iya | Berlebihan | Bekerja tapi boros |
SSE ideal untuk hasil CAPTCHA karena data hanya mengalir dari server ke klien.
Pertimbangan Produksi
Penskalaan dengan Beberapa Instans Server
Koneksi SSE bersifat stateful — jika server Anda memiliki beberapa instance di belakang penyeimbang beban, callback mungkin mengenai instance yang berbeda dari instance yang menyimpan koneksi SSE klien.
Solusi: Gunakan Redis Pub/Sub sebagai bus pesan:
# 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"
Batas Koneksi
Browser membatasi koneksi SSE hingga 6 per domain (HTTP/1.1). Gunakan HTTP/2 untuk batas yang lebih tinggi, atau gandakan beberapa hasil tugas melalui satu koneksi SSE per klien.
Pemecahan Masalah
| Masalah | Penyebab | Solusi |
|---|---|---|
| Koneksi SSE terputus setiap 30 detik | Timeout penyeimbang Proxy/load | Kirim komentar tetap hidup; menambah timeout proxy |
| Hasil tidak sampai | Callback mengenai contoh server yang berbeda | Tambahkan Redis Pub/Sub antara callback dan penangan SSE |
| Browser menunjukkan kesalahan di konsol | Header CORS hilang | Tambahkan header Access-Control-Allow-Origin ke endpoint SSE |
| Beberapa koneksi ulang | Server mengirimkan SSE yang salah format | Pastikan \n\n menghentikan setiap acara; memvalidasi format data |
Pertanyaan Umum
Apakah SSE bekerja di belakang Cloudflare?
Ya, tapi Cloudflare mungkin melakukan buffer terhadap respons. Nonaktifkan buffering respons dengan header X-Accel-Buffering: no, atau gunakan mode streaming Cloudflare.
Berapa banyak koneksi SSE bersamaan yang dapat ditangani oleh satu server?
Node.js menangani 10.000+ koneksi SSE secara bersamaan dengan mudah karena masing-masing koneksi merupakan koneksi HTTP ringan yang tetap aktif. Python dengan threading lebih terbatas – gunakan kerangka async (FastAPI dengan asyncio) untuk konkurensi tinggi.
Haruskah saya menggunakan SSE untuk klien non-browser?
Untuk alat CLI atau layanan backend, penanganan callback langsung atau pendekatan berbasis antrean lebih sederhana. SSE paling berguna saat mendorong hasil ke dashboard berbasis browser atau aplikasi web.
Langkah Selanjutnya
Streaming solusi CAPTCHA secara real-time —dapatkan kunci API CaptchaAI Andadan sambungkan SSE ke saluran callback Anda.
Panduan terkait:
- URL Callback dan Panduan Webhook
- Pola Notifikasi Tugas Pingback
- Keamanan Webhook: Memvalidasi Callback