Hướng Dẫn Thực Hành

Xử lý lỗi URL gọi lại CaptchaAI: Thử lại và các mẫu chữ cái chết

Lệnh gọi lại (pingback) loại bỏ việc thăm dò ý kiến, nhưng chúng đưa ra một chế độ lỗi mới: điều gì xảy ra khi máy chủ của bạn ngừng hoạt động, trả về lỗi hoặc hết thời gian chờ khi CaptchaAI cố gắng cung cấp kết quả? Hướng dẫn này bao gồm các mẫu để xử lý lỗi gọi lại mà không làm mất giải pháp CAPTCHA.

Điều gì có thể sai

Chế độ lỗi Triệu chứng kết quả
Máy chủ ngừng hoạt động CaptchaAI bị từ chối kết nối Giải pháp chưa được giao
Máy chủ trả về 5xx CaptchaAI nhận được phản hồi lỗi Có thể không thử lại (tùy thuộc vào việc triển khai)
Hết thời gian chờ mạng Kết nối CaptchaAI bị treo Giải pháp có thể bị mất
Trình xử lý gặp sự cố Yêu cầu được chấp nhận nhưng kết quả không được lưu trữ Giải pháp âm thầm bị bỏ rơi

Giải pháp: không bao giờ chỉ dựa vào lệnh gọi lại. Luôn có một dự phòng.

Mẫu 1: Gọi lại + Thăm dò dự phòng

Cách tiếp cận đáng tin cậy nhất — chấp nhận lệnh gọi lại khi chúng đến nhưng thăm dò bất kỳ nhiệm vụ nào không nhận được lệnh gọi lại trong thời gian chờ.

Python

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);

Mẫu 2: Hàng đợi thư chết

Khi trình xử lý gọi lại của bạn xử lý một kết quả nhưng gặp lỗi (cơ sở dữ liệu ngừng hoạt động, xác thực không thành công), hãy chuyển vấn đề sang hàng đợi thư chết thay vì mất dữ liệu.

Python

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);

Mẫu 3: Trình xử lý gọi lại bình thường

Cuộc gọi lại có thể được gửi nhiều lần. Làm cho trình xử lý của bạn trở nên bình thường:

@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

Ma trận quyết định: Nên sử dụng mẫu nào

Kịch bản Mẫu đẹp nhất
Khối lượng thấp, thỉnh thoảng ngừng hoạt động Gọi lại + Thăm dò dự phòng
Khối lượng lớn, có thể mất cơ sở dữ liệu Hàng đợi thư chết
Nhiều người tiêu dùng có thể xử lý cùng một kết quả Trình xử lý bình thường
Hệ thống sản xuất với SLA Cả ba kết hợp

Khắc phục sự cố

Vấn đề Nguyên nhân Cách xử lý
Trình thăm dò dự phòng tìm thấy các nhiệm vụ đã được phân phối Cuộc đua giữa cuộc gọi lại và cuộc thăm dò ý kiến Thêm kiểm tra idempotency - bỏ qua nếu đã có kết quả
DLQ phát triển mà không được xử lý Bộ xử lý lại không chạy hoặc bị lỗi Kiểm tra nhật ký bộ xử lý lại; đảm bảo vấn đề cơ bản (DB) được khắc phục
Gọi lại trả về 200 nhưng kết quả bị mất Trình xử lý gặp sự cố sau khi phản hồi được gửi Xử lý trước khi phản hồi hoặc sử dụng mẫu DLQ
Quá nhiều yêu cầu thăm dò dự phòng Quá nhiều nhiệm vụ cũ Tăng ngưỡng thời gian chờ gọi lại; kiểm tra thời gian hoạt động của máy chủ

Câu hỏi thường gặp

Tôi có nên luôn trả lại 200 cho các lệnh gọi lại CaptchaAI không?

Vâng. Việc trả lại mã lỗi (4xx/5xx) không giúp ích được gì — CaptchaAI không thể thử lại lệnh gọi lại. Luôn chấp nhận phân phối (200 OK) và xử lý lỗi nội bộ bằng DLQ hoặc bỏ phiếu dự phòng.

Tôi nên đợi bao lâu trước khi bỏ phiếu dự phòng?

Đợi ít nhất 120 giây sau khi gửi. Hầu hết CAPTCHA đều giải quyết trong vòng 10–60 giây, cộng với độ trễ mạng khi gửi lệnh gọi lại. Hai phút là đủ thời gian để cuộc gọi lại đến.

Tôi có thể tắt tính năng gọi lại và chỉ thăm dò ý kiến không?

Có — chỉ đơn giản là không bao gồm tham số pingback. Nhưng các lệnh gọi lại làm giảm đáng kể các lệnh gọi API trên quy mô lớn (2 lệnh gọi cho mỗi tác vụ thay vì hơn 10 yêu cầu thăm dò ý kiến).

bài viết liên quan

Các bước tiếp theo

Xây dựng quy trình xử lý gọi lại CAPTCHA đáng tin cậy —lấy khóa API CaptchaAI của bạnvà thực hiện các mô hình kiên cường này.

Hướng dẫn liên quan:

Os comentários estão desativados para este artigo.