CAPTCHA çözümünüz hazır olduğu anda tarayıcıdaki kullanıcı bunu görsün istiyorsanız, doğru araç Server-Sent Events (SSE). Sunucunuz res.php'yi saniyede bir sorgulamak yerine, CaptchaAI'nin callback'iyle gelen çözümü açık bir HTTP bağlantısı üzerinden istemciye tek yönlü olarak iletir — boşa giden istek sıfır, teslimat saniyenin altında.
Bu kalıp özellikle tarayıcı tabanlı bir panele sonuç akıtmanız gereken senaryolarda işe yarar. Örneğin bir e-ticaret ekibinin ödeme akışı QA testlerinde onlarca eşzamanlı oturumun CAPTCHA durumunu tek bir kontrol panelinde canlı izlemesi ya da bir freelance otomasyon geliştiricisinin müşteriye teslim ettiği panelde çözümlerin anlık düşmesi gibi. Aşağıda mimariyi, Python (Flask) ve Node.js (Express) için tam çalışan sunucu kodunu ve üretime çıkarken dikkat etmeniz gereken noktaları bulacaksınız.
Neden periyodik sorgulama yerine SSE?
Sonucu almak için üç yol vardır: res.php'yi periyodik sorgulama, çift yönlü WebSocket bağlantısı ya da tek yönlü SSE akışı. CAPTCHA çözüm bildiriminde veri yalnızca sunucudan istemciye aktığından — istemcinin geri bir şey göndermesi gerekmez — SSE bu iş için en yalın seçenektir. WebSocket çift yönlü olduğundan burada gereğinden fazla altyapı getirir; periyodik sorgulama ise çözüm gelene kadar boşa istek harcar.
| Özellik | SSE | WebSocket | Sorgulama |
|---|---|---|---|
| Yön | Sunucu – İstemci | Çift yönlü | İstemci – Sunucu |
| Protokol | HTTP/1.1+ | WS/WSS | HTTP |
| Otomatik yeniden bağlanma | Yerleşik | Manuel | Yok |
| Tarayıcı desteği | Tüm modern tarayıcılar | Tüm modern tarayıcılar | Hepsi |
| Karmaşıklık | Düşük | Orta | Düşük |
| Boşa giden istekler | Yok | Yok | Çok |
| CAPTCHA sonuçları için | İdeal | Fazla karmaşık | Çalışır ama israf |
SSE'nin yerleşik yeniden bağlanma özelliği, tarayıcının bağlantı koptuğunda EventSource üzerinden otomatik yeniden bağlanmasını sağlar; bu da uzun süreli paneller için ekstra kod yazmadan dayanıklılık kazandırır.
Hangisini seçeceğinize hızlıca karar vermek için:
- Yalnızca sunucudan istemciye tek yönlü bildirim gerekiyorsa: SSE.
- İstemcinin de sunucuya sürekli veri göndermesi gereken çift yönlü akış varsa: WebSocket.
- Tek seferlik, basit bir sonuç kontrolü yetiyorsa:
res.phpsorgulaması yine de yeterlidir.
Kararsız kaldığınızda soruyu tersten sorun: istemcinin sunucuya bir şey göndermesi gerekiyor mu? Yanıt hayırsa SSE daha az kod ve daha az bakım demektir.
SSE mimarisi: çözüm istemciye nasıl akar?
Akışın tamamı dört adımdan oluşur. Sunucunuz hem SSE uç noktasını hem de CaptchaAI callback'ini barındırır; CaptchaAI görev bittiğinde pingback adresinize istek atar, siz de bu sonucu açık SSE bağlantısı üzerinden istemciye geçirirsiniz.
[Client] ← SSE stream ← [Your Server] ← Callback ← [CaptchaAI]
↓ ↑
Submit task → [CaptchaAI] ──┘ (pingback URL points to your server)
- İstemci SSE uç noktanıza bağlanır (kalıcı HTTP bağlantısı)
- İstemci,
pingbacksunucunuzu işaret ederek CaptchaAI'ye bir CAPTCHA görevi gönderir - CaptchaAI çözer ve sonucu callback uç noktanıza iletir
- Sunucunuz sonucu SSE akışı aracılığıyla istemciye geçirir
Burada pingback parametresi kritik: res.php'yi sorgulamak yerine CaptchaAI'nin sizi tetiklemesini sağlar. Görev bittiğinde sonuç, gönderim sırasında verdiğiniz callback adresine tek bir HTTP isteğiyle düşer.
Python (Flask) ile tam uygulama
Sunucu
Sunucu tarafında her istemci için ayrı bir olay kuyruğu tutar, callback geldiğinde ilgili kuyruğa yazar ve SSE üreticisi bu kuyruktan okuyarak istemciye aktarır. X-Accel-Buffering: no başlığı nginx arabelleğe almasını kapatır; 30 saniyelik keepalive yorumu ise proxy zaman aşımını önler.
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)
Tarayıcı istemcisi
İstemci tarafı tek bir EventSource bağlantısıyla captcha-solved olaylarını dinler. Görev gönderimi ayrı bir POST /submit isteğiyle yapılır; sonuç hazır olduğunda SSE olayı kendiliğinden tetiklenir.
<!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>
Örnekteki method: "userrecaptcha" reCAPTCHA içindir; Cloudflare Turnstile veya diğer desteklenen türler için yalnızca method ve ilgili parametreleri değiştirin — SSE katmanı gönderilen türden bağımsız çalışır.
Node.js (Express) ile tam uygulama
Sunucu
Node.js tarafında istemci Response nesnelerini bir Map içinde tutmak yeterlidir; her SSE bağlantısı hafif olduğundan tek bir örnek yüksek eşzamanlılığı rahatça kaldırır. Yapı Python sürümüyle birebir aynı: /events/:clientId akışı açar, /submit görevi gönderir, /callback sonucu ilgili istemciye yazar.
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"));
Üretim ortamı: ölçeklendirme ve bağlantı yönetimi
Yük dengeleyici arkasında ölçeklendirme
SSE bağlantıları durum bilgilidir; sunucunuz bir yük dengeleyicinin arkasında birden fazla örnek çalıştırıyorsa, CaptchaAI'nin callback'i istemcinin SSE bağlantısını tutan örnekten farklı bir örneğe düşebilir. Bu durumda sonuç doğru istemciye ulaşmaz.
Çözüm: Redis Pub/Sub'ı örnekler arası mesaj veri yolu olarak kullanın. Callback'i alan örnek sonucu Redis'e yayınlar, istemciyi tutan örnek ise aynı kanala abone olarak sonucu akışa geçirir:
# 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"
Tarayıcı bağlantı sınırları
Tarayıcılar HTTP/1.1 üzerinde alan başına yalnızca 6 eşzamanlı SSE bağlantısına izin verir. Aynı kullanıcıda çok sayıda paralel görev izleyecekseniz iki seçeneğiniz var: HTTP/2'ye geçerek sınırı yükseltin ya da istemci başına tek bir SSE bağlantısı açıp birden fazla görev sonucunu bu tek akış üzerinden çoğaltın. İkincisi hem tarayıcı sınırını aşmanızı hem de sunucu tarafında bağlantı sayısını düşürmenizi sağlar.
Üretime çıkmadan önce kontrol listesi
Panel canlıya alınmadan önce şu dört noktayı doğrulayın:
- SSE uç noktasında
X-Accel-Buffering: noveCache-Control: no-cachebaşlıklarının ayarlı olduğunu. - 30 saniyelik keepalive yorumlarının proxy zaman aşımının altında kaldığını.
- Birden fazla örnek çalıştırıyorsanız callback ile SSE işleyicileri arasına Redis Pub/Sub koyduğunuzu.
- Tarayıcıdan gelen istekler için
Access-Control-Allow-Originbaşlığının doğru alan adını döndürdüğünü.
Sık karşılaşılan sorunlar ve çözümleri
| Sorun | Neden | Çözüm |
|---|---|---|
| SSE bağlantısı her 30 saniyede bir düşüyor | Proxy/yük dengeleyici zaman aşımı | Keepalive yorumları gönderin; proxy zaman aşımını artırın |
| Sonuçlar gelmiyor | Callback farklı sunucu örneğine düşüyor | Callback ile SSE işleyicileri arasına Redis Pub/Sub ekleyin |
| Tarayıcı konsolunda hata görünüyor | CORS başlıkları eksik | SSE uç noktasına Access-Control-Allow-Origin başlığını ekleyin |
| Sürekli yeniden bağlanma | Sunucu hatalı biçimli SSE gönderiyor | Her olayın \n\n ile bittiğinden emin olun; veri biçimini doğrulayın |
Sık sorulan sorular
SSE mi WebSocket mi kullanmalıyım?
CAPTCHA sonuçlarında veri yalnızca sunucudan istemciye aktığı için SSE yeterli ve daha yalındır. WebSocket'i ancak istemcinin de sunucuya sürekli veri göndermesi gereken çift yönlü senaryolarda tercih edin; tek yönlü bildirimde fazladan karmaşıklık getirir.
CaptchaAI bu akışta hangi CAPTCHA türlerini destekliyor?
SSE katmanı türden bağımsızdır; gönderim tarafında reCAPTCHA v2/v3, Cloudflare Turnstile ve GeeTest v3 gibi desteklenen türleri kullanabilirsiniz. hCaptcha ve FunCaptcha desteklenmediği için bu türleri bu akışa dahil edemezsiniz; CaptchaFox, Friendly Captcha ve Lemin ise yalnızca beta aşamasındadır.
SSE bağlantısı koparsa çözüm kaybolur mu?
Tarayıcının EventSource nesnesi bağlantı koptuğunda otomatik yeniden bağlanır. Kısa kesintilerde çözüm sunucu tarafındaki kuyrukta ya da Redis kanalında beklediğinden yeniden bağlanan istemciye iletilebilir; kalıcılık kritikse callback sonucunu bir kuyruğa yazıp yeniden bağlanmada tekrar yayınlayın.
Ölçeklenirken neden Redis Pub/Sub gerekiyor?
Tek sunucu örneğinde gerekmez. Birden fazla örnek çalıştırdığınızda callback'i alan örnekle istemciyi tutan örnek farklı olabilir; Redis Pub/Sub bu iki örnek arasında mesajı taşıyarak sonucun doğru istemciye ulaşmasını sağlar.
SSE tarayıcı dışı istemciler için de uygun mu?
CLI araçları veya arka uç servisleri için doğrudan callback işleme ya da kuyruk tabanlı yaklaşımlar daha yalındır. SSE'nin asıl avantajı, sonuçları tarayıcı tabanlı panellere ve web uygulamalarına canlı aktarırken ortaya çıkar.
Sonraki adımlar
Thread tabanlı CaptchaAI planları USD sabit fiyatlıdır; TL kur oynaklığından etkilenmeden öngörülebilir maliyetle başlamak için BASIC ($15/ay, 5 thread) yeterlidir. CAPTCHA çözümlerini gerçek zamanlı yayınlamaya başlamak için CaptchaAI API anahtarınızı alın ve SSE'yi callback hattınıza bağlayın.
İlgili rehberler: