الدروس التطبيقية

معالجة أخطاء عنوان URL لرد الاتصال CaptchaAI: إعادة المحاولة وأنماط الأحرف الميتة

تعمل عمليات الاسترجاعات (pingbacks) على إلغاء الاستقصاء، ولكنها تقدم وضع فشل جديدًا: ماذا يحدث عندما يكون الخادم الخاص بك معطلاً، أو يُرجع خطأ، أو تنتهي المهلة عندما يحاول CaptchaAI تسليم النتيجة؟ يغطي هذا البرنامج التعليمي أنماط التعامل مع حالات فشل رد الاتصال دون فقدان حلول اختبار CAPTCHA.

ما يمكن أن يحدث خطأ

وضع الفشل أعراض النتيجة
الخادم أسفل تم رفض الاتصال بـ CaptchaAI لم يتم تسليم الحل
يعود الخادم 5xx يتلقى CaptchaAI استجابة للخطأ لا يجوز إعادة المحاولة (يعتمد على التنفيذ)
مهلة الشبكة توقف اتصال CaptchaAI من المحتمل أن يكون الحل مفقودًا
تعطل المعالج تم قبول الطلب ولكن لم يتم تخزين النتيجة الحل انخفض بصمت

الحل: لا تعتمد مطلقًا على عمليات الاسترجاعات. دائما لديك احتياطي.

النمط 1: رد الاتصال + الاقتراع الاحتياطي

الطريقة الأكثر موثوقية هي قبول عمليات الاسترجاعات عند وصولها، ولكن قم بالاستقصاء عن أي مهام لا تتلقى عمليات الاسترجاعات خلال المهلة المحددة.

بايثون

import os
import time
import threading
import requests
from flask import Flask, request

app = Flask(__name__)
API_KEY = os.environ["CAPTCHAAI_API_KEY"]

# Track task state
pending_tasks = {}  # task_id -> {"submitted_at": timestamp, "status": "pending"}
results = {}
lock = threading.Lock()


def submit_captcha(sitekey, pageurl, callback_url):
    """Submit with callback, but track for fallback polling."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "pingback": callback_url,
        "json": 1
    })
    data = resp.json()

    if data.get("status") == 1:
        task_id = data["request"]
        with lock:
            pending_tasks[task_id] = {
                "submitted_at": time.time(),
                "status": "pending"
            }
        return task_id
    return None


@app.route("/callback")
def captcha_callback():
    """Primary result delivery — CaptchaAI sends results here."""
    task_id = request.args.get("id")
    solution = request.args.get("code")

    with lock:
        results[task_id] = solution
        pending_tasks.pop(task_id, None)

    return "OK", 200


def fallback_poller():
    """Poll for any tasks that missed their callback."""
    while True:
        time.sleep(30)  # Check every 30 seconds

        with lock:
            stale_tasks = [
                tid for tid, info in pending_tasks.items()
                if time.time() - info["submitted_at"] > 120  # 2 min callback timeout
                and info["status"] == "pending"
            ]

        for task_id in stale_tasks:
            resp = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": API_KEY,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = resp.json()

            if data.get("status") == 1:
                with lock:
                    results[task_id] = data["request"]
                    pending_tasks.pop(task_id, None)
                print(f"Fallback poll recovered: {task_id}")
            elif data.get("request") != "CAPCHA_NOT_READY":
                # Permanent error — remove from pending
                with lock:
                    pending_tasks.pop(task_id, None)
                print(f"Task failed: {task_id} — {data.get('request')}")


# Start fallback poller in background
poller_thread = threading.Thread(target=fallback_poller, daemon=True)
poller_thread.start()

JavaScript

const express = require("express");
const axios = require("axios");

const app = express();
const API_KEY = process.env.CAPTCHAAI_API_KEY;

const pendingTasks = new Map(); // taskId -> { submittedAt, status }
const results = new Map();

async function submitCaptcha(sitekey, pageurl, callbackUrl) {
  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) {
    const taskId = resp.data.request;
    pendingTasks.set(taskId, {
      submittedAt: Date.now(),
      status: "pending",
    });
    return taskId;
  }
  return null;
}

// Primary callback endpoint
app.get("/callback", (req, res) => {
  const taskId = req.query.id;
  const solution = req.query.code;

  results.set(taskId, solution);
  pendingTasks.delete(taskId);

  res.sendStatus(200);
});

// Fallback poller
setInterval(async () => {
  const now = Date.now();
  const staleTasks = [];

  for (const [taskId, info] of pendingTasks) {
    if (now - info.submittedAt > 120000 && info.status === "pending") {
      staleTasks.push(taskId);
    }
  }

  for (const taskId of staleTasks) {
    try {
      const resp = await axios.get("https://ocr.captchaai.com/res.php", {
        params: { key: API_KEY, action: "get", id: taskId, json: 1 },
      });

      if (resp.data.status === 1) {
        results.set(taskId, resp.data.request);
        pendingTasks.delete(taskId);
        console.log(`Fallback recovered: ${taskId}`);
      } else if (resp.data.request !== "CAPCHA_NOT_READY") {
        pendingTasks.delete(taskId);
        console.log(`Task failed: ${taskId} — ${resp.data.request}`);
      }
    } catch (err) {
      console.error(`Poll error for ${taskId}: ${err.message}`);
    }
  }
}, 30000);

app.listen(3000);

النمط 2: قائمة انتظار الرسائل الميتة

عندما يقوم معالج رد الاتصال الخاص بك بمعالجة نتيجة ولكنه يواجه خطأ (قاعدة البيانات معطلة، فشل التحقق من الصحة)، انقل المشكلة إلى قائمة انتظار الأحرف الميتة بدلاً من فقدان البيانات.

بايثون

import json
import os
import time
from pathlib import Path

DEAD_LETTER_DIR = Path("dead_letter")
DEAD_LETTER_DIR.mkdir(exist_ok=True)


@app.route("/callback")
def captcha_callback_with_dlq():
    task_id = request.args.get("id")
    solution = request.args.get("code")

    try:
        # Attempt normal processing
        store_result(task_id, solution)
        return "OK", 200
    except Exception as e:
        # Processing failed — save to dead-letter queue
        dead_letter = {
            "task_id": task_id,
            "solution": solution,
            "error": str(e),
            "received_at": time.time()
        }
        dlq_path = DEAD_LETTER_DIR / f"{task_id}.json"
        dlq_path.write_text(json.dumps(dead_letter))

        print(f"DLQ: {task_id} — {e}")
        return "OK", 200  # Still return 200 to CaptchaAI


def reprocess_dead_letters():
    """Retry processing dead-letter items."""
    for dlq_file in DEAD_LETTER_DIR.glob("*.json"):
        item = json.loads(dlq_file.read_text())

        try:
            store_result(item["task_id"], item["solution"])
            dlq_file.unlink()  # Remove after successful processing
            print(f"DLQ reprocessed: {item['task_id']}")
        except Exception:
            pass  # Leave in DLQ for next retry

JavaScript

const fs = require("fs");
const path = require("path");

const DLQ_DIR = path.join(__dirname, "dead_letter");
if (!fs.existsSync(DLQ_DIR)) fs.mkdirSync(DLQ_DIR);

app.get("/callback-dlq", (req, res) => {
  const taskId = req.query.id;
  const solution = req.query.code;

  try {
    storeResult(taskId, solution);
    res.sendStatus(200);
  } catch (err) {
    // Save to dead-letter queue
    const deadLetter = {
      task_id: taskId,
      solution: solution,
      error: err.message,
      received_at: Date.now(),
    };

    fs.writeFileSync(
      path.join(DLQ_DIR, `${taskId}.json`),
      JSON.stringify(deadLetter)
    );

    console.log(`DLQ: ${taskId} — ${err.message}`);
    res.sendStatus(200); // Still acknowledge to CaptchaAI
  }
});

function reprocessDeadLetters() {
  const files = fs.readdirSync(DLQ_DIR).filter((f) => f.endsWith(".json"));

  for (const file of files) {
    const filePath = path.join(DLQ_DIR, file);
    const item = JSON.parse(fs.readFileSync(filePath, "utf8"));

    try {
      storeResult(item.task_id, item.solution);
      fs.unlinkSync(filePath);
      console.log(`DLQ reprocessed: ${item.task_id}`);
    } catch (err) {
      // Leave in DLQ
    }
  }
}

// Retry DLQ every 5 minutes
setInterval(reprocessDeadLetters, 300000);

النمط 3: معالج رد الاتصال Idempotent

قد يتم تسليم عمليات الاسترجاعات أكثر من مرة. اجعل معالجك عاجزًا:

@app.route("/callback")
def idempotent_callback():
    task_id = request.args.get("id")
    solution = request.args.get("code")

    with lock:
        # Only process if not already handled
        if task_id in results:
            return "OK", 200  # Already processed — skip silently

        results[task_id] = solution
        pending_tasks.pop(task_id, None)

    return "OK", 200

مصفوفة القرار: أي نمط يجب استخدامه

السيناريو أفضل نمط
انخفاض حجم الصوت، والتوقف في بعض الأحيان رد الاتصال + الاقتراع الاحتياطي
حجم كبير، احتمالية انقطاع قاعدة البيانات قائمة انتظار الرسائل الميتة
قد يقوم العديد من المستهلكين بمعالجة نفس النتيجة معالج عاجز
نظام الإنتاج مع اتفاقيات مستوى الخدمة الثلاثة مجتمعة

استكشاف الأخطاء وإصلاحها

المشكلة السبب الإجراء
يُنشأ الرمز المميز لكن الجهة المستهدفة ترفضه مفتاح الموقع أو الصفحة أو سياق الجلسة لا يتطابق التقط المعلمات من جديد وأعد استخدام الرمز داخل جلسة HTTP أو المتصفح نفسها
تنتهي عملية الاستطلاع بمهلة الفاصل الزمني أو وقت الانتظار أو معالجة الأخطاء صارمة أكثر من اللازم استطلع كل 5 إلى 10 ثوانٍ وافصل بين انتهاء المهلة والأخطاء الفعلية وسجّل السبب
ينجح المثال محليًا لكنه يفشل داخل سير العمل رد النداء أو حقل النموذج أو حقن الرمز مفقود في السلسلة الفعلية تحقق من المسار الكامل بين أداة الحل والطلب النهائي إلى الموقع المستهدف

الخطوات التالية

أدلة ذات صلة

التعليقات غير مفعّلة لهذا المقال.