Eğitimler

Gerçek Zamanlı CAPTCHA Çözme Bildirimleri için Sunucu Tarafından Gönderilen Etkinlikler

res.php'yi her 5 saniyede bir yoklamak işe yarar, ancak isteklerin boşa gitmesine ve gecikmenin artmasına neden olur. Sunucu Tarafından Gönderilen Etkinlikler (SSE), sunucunuzun CAPTCHA çözümlerini bağlı istemcilere geldikleri anda iletmesine olanak tanır; boşa giden istekler sıfır, saniyeden kısa teslimat.

SSE, CAPTCHA İş Akışına Nasıl Uyuyor?

[Client] ← SSE stream ← [Your Server] ← Callback ← [CaptchaAI]
   ↓                          ↑
   Submit task → [CaptchaAI] ──┘ (pingback URL points to your server)
  1. İstemci SSE uç noktanıza bağlanır (kalıcı HTTP bağlantısı)
  2. İstemci, pingback sunucunuzu işaret ederek CaptchaAI'ye bir CAPTCHA görevi gönderir
  3. CaptchaAI çözer ve sonucu geri arama uç noktanıza gönderir
  4. Sunucunuz sonucu SSE akışı aracılığıyla istemciye iletir

Tam Uygulama – Python (Flask)

Sunucu

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ı İstemcisi

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

Tam Uygulama – JavaScript (Express)

Sunucu

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, WebSocket ve Yoklama

Özellik SSE WebSocket Oylama
Yön Sunucu – İstemci Çift yönlü İstemci – Sunucu
Protokol HTTP/1.1+ WS/WSS HTTP
Otomatik yeniden bağlan Yerleşik Manuel N/A
Tarayıcı desteği Hepsi modern Hepsi modern Hepsi
Karmaşıklık Düşük Orta Düşük
Boşa giden istekler Yok Yok Birçok
CAPTCHA sonuçları için en iyisi Evet Aşırı öldürme Çalışıyor ama israf

SSE, CAPTCHA sonuçları için idealdir çünkü veriler yalnızca sunucudan istemciye akar.

Üretimle İlgili Hususlar

Birden Çok Sunucu Örneğiyle Ölçeklendirme

SSE bağlantıları durum bilgilidir; sunucunuzun bir yük dengeleyicinin arkasında birden fazla örneği varsa, geri arama, istemcinin SSE bağlantısını tutandan farklı bir örneğe çarpabilir.

Çözüm: Redis Pub/Sub'yi mesaj veri yolu olarak kullanı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"

Bağlantı Sınırları

Tarayıcılar SSE bağlantılarını etki alanı başına 6 bağlantıyla (HTTP/1.1) sınırlandırır. Daha yüksek sınırlar için HTTP/2 kullanın veya istemci başına tek bir SSE bağlantısı aracılığıyla birden fazla görev sonucunu çoğaltın.

Sorun giderme

Sorun Sebep Düzeltme
SSE bağlantısı her 30 saniyede bir düşüyor Proxy/load dengeleyici zaman aşımı Canlı tutan yorumlar gönderin; proxy zaman aşımını artır
Sonuçlar gelmiyor Farklı sunucu örneğine isabet eden geri arama Geri arama ve SSE işleyicileri arasına Redis Pub/Sub ekleyin
Tarayıcı konsolda hatalar gösteriyor CORS başlıkları eksik SSE uç noktasına Access-Control-Allow-Origin başlığını ekleyin
Çoklu yeniden bağlantı Sunucu hatalı biçimlendirilmiş SSE gönderiyor \n\n'nin her olayı sonlandırdığından emin olun; veri formatını doğrula

SSS

SSE Cloudflare'in arkasında çalışıyor mu?

Evet, ancak Cloudflare yanıtları arabelleğe alabilir. X-Accel-Buffering: no başlığıyla yanıt arabelleğe almayı devre dışı bırakın veya Cloudflare'in akış modunu kullanın.

Bir sunucu kaç eşzamanlı SSE bağlantısını yönetebilir?

Node.js, her biri hafif ve canlı tutulan bir HTTP bağlantısı olduğundan, 10.000'den fazla eşzamanlı SSE bağlantısını kolayca yönetir. İş parçacığı içeren Python daha sınırlıdır; yüksek eşzamanlılık için eşzamansız bir çerçeve (asyncio ile FastAPI) kullanın.

Tarayıcı olmayan istemciler için SSE kullanmalı mıyım?

CLI araçları veya arka uç hizmetleri için doğrudan geri arama yönetimi veya kuyruk tabanlı yaklaşımlar daha basittir. SSE, sonuçları tarayıcı tabanlı kontrol panellerine veya web uygulamalarına aktarırken en kullanışlıdır.

Sonraki Adımlar

CAPTCHA çözümlerini gerçek zamanlı olarak yayınlayın —CaptchaAI API anahtarınızı alınve SSE'yi geri arama hattınıza bağlayın.

İlgili kılavuzlar:

Bu makale için yorumlar devre dışı bırakılmıştır.