DevOps & Skalabilitas

Pemecahan CAPTCHA Berbasis Event dengan AWS SNS dan CaptchaAI

Polling untuk hasil CAPTCHA mengikat alur dan menciptakan ketergantungan erat antara scraper dan alur penyelesaian. AWS SNS (Layanan Pemberitahuan Sederhana) memisahkan kekhawatiran ini — CaptchaAI mengirimkan hasil ke panggilan balik Anda, yang dipublikasikan ke SNS, dan sejumlah konsumen hilir bereaksi secara independen.

Ikhtisar Arsitektur

[Scraper] → Submit CAPTCHA → [CaptchaAI API]
                                    ↓
                            Solve completes
                                    ↓
                            Callback → [API Gateway + Lambda]
                                    ↓
                            Publish → [SNS Topic]
                                    ↓
                    ┌───────────────┼───────────────┐
                    ↓               ↓               ↓
            [SQS Queue]      [Lambda Logger]   [Email Alert]
            (result store)   (audit trail)     (on failure)

SNS menyediakan penyebaran: satu hasil CAPTCHA memicu banyak konsumen tanpa diketahui oleh pengendali panggilan balik.

Langkah 1: Buat Topik SNS

AWS CLI

aws sns create-topic --name captcha-results --output text
# Returns: arn:aws:sns:us-east-1:123456789:captcha-results

Python (boto3)

import boto3

sns = boto3.client("sns", region_name="us-east-1")

response = sns.create_topic(Name="captcha-results")
topic_arn = response["TopicArn"]
print(f"Topic ARN: {topic_arn}")

Langkah 2: Bangun Penerima Panggilan Balik

Fungsi Lambda ini menerima hasil panggilan balik CaptchaAI dan mempublikasikannya ke SNS.

Python (Penanganan Lambda)

import json
import os
import boto3

sns = boto3.client("sns")
TOPIC_ARN = os.environ["SNS_TOPIC_ARN"]


def lambda_handler(event, context):
    """Receive CaptchaAI callback and publish to SNS."""
    # Parse query parameters from API Gateway
    params = event.get("queryStringParameters", {}) or {}
    task_id = params.get("id", "")
    solution = params.get("code", "")

    if not task_id or not solution:
        return {"statusCode": 400, "body": "Missing id or code"}

    # Publish to SNS
    message = {
        "task_id": task_id,
        "solution": solution,
        "status": "solved"
    }

    sns.publish(
        TopicArn=TOPIC_ARN,
        Message=json.dumps(message),
        Subject="captcha-solved",
        MessageAttributes={
            "task_id": {
                "DataType": "String",
                "StringValue": task_id
            }
        }
    )

    return {"statusCode": 200, "body": "OK"}

JavaScript (Penanganan Lambda)

const { SNSClient, PublishCommand } = require("@aws-sdk/client-sns");

const sns = new SNSClient({ region: "us-east-1" });
const TOPIC_ARN = process.env.SNS_TOPIC_ARN;

exports.handler = async (event) => {
  const params = event.queryStringParameters || {};
  const taskId = params.id;
  const solution = params.code;

  if (!taskId || !solution) {
    return { statusCode: 400, body: "Missing id or code" };
  }

  const message = {
    task_id: taskId,
    solution: solution,
    status: "solved",
  };

  await sns.send(
    new PublishCommand({
      TopicArn: TOPIC_ARN,
      Message: JSON.stringify(message),
      Subject: "captcha-solved",
      MessageAttributes: {
        task_id: { DataType: "String", StringValue: taskId },
      },
    })
  );

  return { statusCode: 200, body: "OK" };
};

Langkah 3: Kirim CAPTCHA dengan URL Panggilan Balik

Arahkan pingback CaptchaAI ke titik akhir API Gateway Anda:

Python

import os
import requests

API_KEY = os.environ["CAPTCHAAI_API_KEY"]
CALLBACK_URL = os.environ["CALLBACK_GATEWAY_URL"]  # API Gateway URL


def submit_captcha(sitekey, pageurl):
    """Submit CAPTCHA with SNS-backed callback."""
    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:
        return data["request"]  # task_id
    raise RuntimeError(f"Submit failed: {data.get('request')}")

Langkah 4: Berlangganan Konsumen

Antrean SQS (Penyimpanan Hasil)

# Subscribe an SQS queue to receive all results
sqs_arn = "arn:aws:sqs:us-east-1:123456789:captcha-results-queue"

sns.subscribe(
    TopicArn=topic_arn,
    Protocol="sqs",
    Endpoint=sqs_arn
)

Lambda (Pencatat Audit)

# Subscribe a Lambda for audit logging
lambda_arn = "arn:aws:lambda:us-east-1:123456789:function:captcha-audit-logger"

sns.subscribe(
    TopicArn=topic_arn,
    Protocol="lambda",
    Endpoint=lambda_arn
)

Email (Peringatan Kegagalan)

# Subscribe email for error notifications with filter
sns.subscribe(
    TopicArn=topic_arn,
    Protocol="email",
    Endpoint="ops@example.com"
)

Langkah 5: Konsumsi Hasil dari SQS

Scraper Anda membaca solusi dari SQS alih-alih melakukan polling CaptchaAI:

Python

import json
import boto3

sqs = boto3.client("sqs", region_name="us-east-1")
QUEUE_URL = os.environ["SQS_QUEUE_URL"]


def get_solved_captcha(timeout=30):
    """Wait for a CAPTCHA solution from the SQS queue."""
    response = sqs.receive_message(
        QueueUrl=QUEUE_URL,
        MaxNumberOfMessages=1,
        WaitTimeSeconds=min(timeout, 20)  # Long polling (max 20s)
    )

    messages = response.get("Messages", [])
    if not messages:
        return None

    msg = messages[0]
    # SNS wraps the message — unwrap it
    sns_envelope = json.loads(msg["Body"])
    result = json.loads(sns_envelope["Message"])

    # Delete message after processing
    sqs.delete_message(
        QueueUrl=QUEUE_URL,
        ReceiptHandle=msg["ReceiptHandle"]
    )

    return result

JavaScript

const {
  SQSClient,
  ReceiveMessageCommand,
  DeleteMessageCommand,
} = require("@aws-sdk/client-sqs");

const sqs = new SQSClient({ region: "us-east-1" });
const QUEUE_URL = process.env.SQS_QUEUE_URL;

async function getSolvedCaptcha(timeout = 30) {
  const response = await sqs.send(
    new ReceiveMessageCommand({
      QueueUrl: QUEUE_URL,
      MaxNumberOfMessages: 1,
      WaitTimeSeconds: Math.min(timeout, 20),
    })
  );

  const messages = response.Messages || [];
  if (messages.length === 0) return null;

  const msg = messages[0];
  const snsEnvelope = JSON.parse(msg.Body);
  const result = JSON.parse(snsEnvelope.Message);

  await sqs.send(
    new DeleteMessageCommand({
      QueueUrl: QUEUE_URL,
      ReceiptHandle: msg.ReceiptHandle,
    })
  );

  return result;
}

Penyaringan Pesan SNS

Memberikan hasil yang berbeda ke konsumen yang berbeda:

# Only send failures to the ops queue
sns.subscribe(
    TopicArn=topic_arn,
    Protocol="sqs",
    Endpoint=failure_queue_arn,
    Attributes={
        "FilterPolicy": json.dumps({
            "status": ["failed", "error"]
        })
    }
)

Pemecahan Masalah

Masalah Penyebab Solusi
Callback mengembalikan 403 Autentikasi API Gateway memblokir CaptchaAI Nonaktifkan autentikasi pada rute callback; gunakan validasi berbasis token
Pesan SQS tidak sampai Izin SNS → SQS tidak ada Tambahkan izin sns:Publish ke kebijakan antrean SQS
Hasil duplikat diproses SNS mengirimkan at-least-once Terapkan idempotency – periksa task_id sebelum diproses
Lambda cold start menunda callback Provisioned concurrency tidak disetel Aktifkan provisioned concurrency untuk callback Lambda

Pertanyaan Umum

Mengapa menggunakan SNS alih-alih memproses hasil secara langsung di callback Lambda?

SNS memisahkan pengendali panggilan balik dari logika hilir. Anda dapat menambahkan konsumen baru (logging, alerting, analitik) tanpa mengubah callback Lambda. Panggilan balik tetap sederhana dan cepat.

Berapa latensi tambahan dari lapisan SNS?

SNS menambahkan 10–50 ms per pesan. Karena penyelesaian CAPTCHA memerlukan waktu 5–30 detik, overhead ini dapat diabaikan.

Bisakah saya menggunakan SNS FIFO untuk pemrosesan pesanan?

Ya. Gunakan topik SNS FIFO dengan antrian SQS FIFO jika Anda memerlukan hasil yang diurutkan. Atur MessageGroupId ke ID tugas untuk pengurutan per tugas.

Artikel Terkait

  • Membangun Pipeline CAPTCHA Klien CaptchaAI
  • Keamanan Webhook: Memvalidasi Callback
  • Integrasi Serverless AWS Lambda + CaptchaAI

Langkah Selanjutnya

Bangun penyelesaian CAPTCHA berbasis event — dapatkan kunci API CaptchaAI Anda dan hubungkan ke alur event AWS Anda.

Komentar dinonaktifkan untuk artikel ini.