Cara Memecahkan CAPTCHA di AWS Lambda Tanpa Kehabisan Waktu

Pemecahan captcha di AWS Lambda gagal di dua tempat, dan tidak satu pun dari keduanya adalah kode Anda. API Gateway berhenti menunggu fungsi itu setelah 29 detik, jadi reCAPTCHA yang memakan 40 detik mengembalikan 504 ke pemanggil sementara fungsinya masih bekerja. Lalu 127.0.0.1 di dalam sandbox Lambda adalah sandbox itu sendiri, jadi klien yang diarahkan ke loopback tidak menemukan apa pun yang mendengarkan. CapSkip berjalan di mesin milik Anda, yang dalam susunan ini tidak pernah menjadi mesin yang menjalankan fungsi Anda. Perbaiki alamatnya lebih dulu, lalu pindahkan pemecahan CAPTCHA keluar dari jalur request.
Apa yang Anda butuhkan
- CapSkip yang berjalan di mesin Windows yang Anda kendalikan. Ia adalah aplikasi desktop dan tidak berjalan di dalam Lambda. Fungsi Anda di sini hanyalah klien, tidak lebih.
- Runtime Lambda Python 3.10 atau lebih baru, dengan paket CapSkip di dalam deployment package atau di sebuah layer.
- Mode Server dinyalakan. Mode Local menjawab di 127.0.0.1 untuk perangkat itu saja, yang tidak berguna bagi fungsi yang berjalan di AWS. Mode Server mendengarkan di alamat jaringan atau IP publik Anda sehingga fungsi itu dapat menjangkaunya melalui API yang sama, dan keduanya ada di pengaturan koneksi. IP publik statis dianjurkan, dengan aturan firewall untuk satu alamat yang akan dipakai AWS saat tiba.
- Sebuah cara untuk menjangkau alamat itu dari fungsi tersebut. Langkah 2 membahas dua bentuknya, karena fungsi yang dilampirkan ke VPC berperilaku berbeda dari fungsi yang tidak.
Kenapa timeout 29 detik API Gateway menentukan desainnya
Pemecahan captcha di AWS Lambda harus muat di dalam tiga batas. Tuliskan ketiganya sebelum Anda menulis kode apa pun, karena bersama-sama ketiganya menggugurkan desain yang paling kelihatan jelas.
| Limit | Nilai | Bisakah dinaikkan |
|---|---|---|
| Timeout integrasi API Gateway | 29 detik secara default | Pada REST API Regional dan privat, lewat permintaan kuota. AWS memperingatkan bahwa kenaikan itu bisa memakan kuota throttle akun Anda |
| Timeout fungsi Lambda | 3 detik secara default, maksimum 900 detik untuk fungsi standar | Bisa, sampai langit-langit 15 menit itu |
| Timeout polling CapSkip | 300 detik untuk reCAPTCHA, Turnstile dan GeeTest, 120 untuk gambar dan ALTCHA | Bisa, keduanya adalah opsi constructor |
Jadi panggilan API yang sinkron tidak bisa menutupi reCAPTCHA yang lambat. Fungsinya punya ruang untuk itu sedangkan gateway di depannya tidak, dan pemanggil melihat 504 sementara pemecahan masih berjalan dan masih ditagih.
Solusi sementara yang biasanya diambil orang berikutnya justru lebih buruk. Kembali lebih awal lalu menyelesaikan pemecahan di background thread tidak berhasil, karena setelah handler kembali, Lambda membekukan execution environment. AWS mengatakannya dengan gamblang: proses latar atau callback yang belum selesai saat fungsi berakhir akan dilanjutkan kembali jika Lambda memakai ulang environment itu. Dilanjutkan kembali, bukan berjalan terus. Thread Anda bangun beberapa menit kemudian, di tengah polling untuk CAPTCHA yang tokennya sudah lama kedaluwarsa, di dalam invocation yang tidak ada hubungannya sama sekali. Tidak ada yang error. Pekerjaannya hanya mendarat di tempat yang salah. AWS menjabarkan siklus hidupnya di panduan execution environment-nya.
Langkah 1: kemas SDK dan konfigurasikan fungsinya
Instal ke sebuah folder lalu zip bersama handler Anda, atau instal ke folder bernama python, zip folder itu, dan pasang sebagai layer. Kunci platform dan interpreter ke apa yang dijalankan fungsi, bukan ke apa yang dijalankan laptop Anda, atau import-nya gagal saat cold start tanpa apa pun yang berguna di log. Tanpa flag itu pip memilih wheel untuk Python lokal Anda, dan wheel yang dibangun untuk interpreter yang lebih baru tidak akan dimuat di runtime tersebut.
# pip install capskip pip install capskip --target package/ \ --platform manylinux2014_x86_64 --implementation cp \ --python-version 3.12 --only-binary=:all: cp lambda_function.py package/ cd package && zip -r ../function.zip . > /dev/null && cd .. aws lambda update-function-code \ --function-name solve-captcha --zip-file fileb://function.zip
Lalu setel timeout dan detail koneksi sebagai konfigurasi, bukan di dalam kode, supaya paket yang sama bisa berjalan terhadap pemecah CAPTCHA uji maupun produksi.
# Timeout in seconds, and the Server mode address
aws lambda update-function-configuration \
--function-name solve-captcha \
--timeout 330 \
--environment "Variables={CAPSKIP_HOST=203.0.113.10,CAPSKIP_PORT=8080}"Setel timeout fungsi sedikit di atas timeout polling milik klien, bukan di bawahnya. Kalau di bawah, Lambda yang lebih dulu mematikan invocation, dan yang Anda dapat hanyalah task timeout polos di CloudWatch, bukan TimeoutException yang seharusnya memberi tahu apa yang terjadi.
Langkah 2: beri fungsi itu sebuah NAT gateway dan Elastic IP
Ini langkah yang menentukan apakah semuanya bisa berjalan, dan jawabannya bergantung pada satu pengaturan yang mungkin tidak Anda anggap sebagai urusan jaringan.
| Konfigurasi fungsi | Apa yang bisa dijangkaunya | Apa yang Anda izinkan di firewall |
|---|---|---|
| Tidak dilampirkan ke VPC | Internet publik, langsung | Tidak ada yang berguna. Lalu lintas keluar berasal dari alamat milik AWS yang berubah, jadi tidak ada satu IP pun yang bisa dimasukkan ke daftar izin |
| Dilampirkan ke VPC, tanpa NAT gateway | Hanya apa yang ada di dalam VPC itu. Pemecah CAPTCHA Anda tidak termasuk | Tidak ada. Koneksinya kehabisan waktu, bukan ditolak |
| Dilampirkan ke VPC, dirutekan lewat NAT gateway | Internet publik, dari satu alamat | Elastic IP milik NAT gateway, dan itulah bentuk yang Anda inginkan |
Lambda mendokumentasikan dua baris pertama secara langsung: fungsi punya akses internet publik secara default, dan melampirkannya ke VPC membatasinya pada sumber daya di dalam VPC itu sampai subnet milik fungsi tersebut punya rute keluar. Rute itu adalah NAT gateway yang duduk di subnet publik, seperti dijelaskan di panduan akses internet Lambda. Efek sampingnya justru bagian yang berguna. Sebuah NAT gateway memegang satu Elastic IP, jadi setiap pemecahan tiba di mesin Anda dari satu alamat yang stabil dan aturan firewall Anda cukup satu baris.
Lampirkan fungsi itu ke subnet privat, bukan ke subnet publik. Inilah jebakan yang menghasilkan hang padahal NAT gateway sudah terpasang: fungsi yang dilampirkan ke subnet publik tidak punya akses internet, apa pun isi route table-nya, jadi paketnya memang tidak ke mana-mana. Panduan yang sama mengulanginya dua kali.
NAT gateway adalah bentuk paling sederhana yang memberi Anda satu alamat sumber yang stabil, bukan satu-satunya bentuk. Site-to-Site VPN atau Direct Connect dari VPC yang sama bisa menjangkau pemecah CAPTCHA di jaringan Anda sendiri tanpa membuka portnya ke internet sama sekali, dan keduanya sepadan dengan usaha penyiapannya kalau mesin itu berada di tempat yang sebaiknya tidak Anda buka portnya. Bagaimanapun caranya, jaga port pemecah CAPTCHA tetap tertutup bagi yang lain. Mode Server tetap perangkat keras Anda dan tetap tanpa kuota: ia hanya mengubah di mana pemecah CAPTCHA mendengarkan supaya sesuatu selain desktop yang sama bisa memanggilnya.
Langkah 3: pindahkan pemecahan CAPTCHA keluar dari jalur request
Mengingat batas di atas, sebuah job captcha di AWS Lambda seharusnya berada di queue, bukan di request. Handler yang menjawab API Gateway sebaiknya bukan handler yang memecahkan: terima job-nya, taruh di queue, lalu jawab seketika. Fungsi kedua membaca queue itu dan mengerjakannya dengan timeout yang cocok untuk CAPTCHA, bukan untuk sebuah request web.
import json, os, uuid, boto3
sqs = boto3.client("sqs")
QUEUE_URL = os.environ["QUEUE_URL"]
def lambda_handler(event, context):
"""API Gateway calls this. It never solves anything."""
body = json.loads(event["body"])
job_id = str(uuid.uuid4())
sqs.send_message(
QueueUrl=QUEUE_URL,
MessageBody=json.dumps({
"job_id": job_id,
"sitekey": body["sitekey"],
"pageurl": body["pageurl"],
}),
)
return {"statusCode": 202,
"body": json.dumps({"job_id": job_id})}Id job itu ada supaya pemanggil punya sesuatu untuk ditanyakan nanti. Rancang consumer-nya untuk menyelesaikan sendiri pekerjaannya, bukan untuk menyerahkan token kembali, karena token yang menunggu satu perjalanan HTTP kedua biasanya kedaluwarsa di jalan.
Pakai queue, bukan asynchronous invoke. Lambda mengulang invocation asynchronous yang gagal sebanyak dua kali secara default, dan pemecahan CAPTCHA adalah hal yang salah untuk diulang membabi buta: percobaan kedua berangkat dari sitekey yang konteks halamannya sudah berubah, dan Anda tetap menanggung biaya pemecahan itu bagaimanapun juga. Queue tidak menghapus pengulangan, ia membuatnya terlihat dan terbatas. Anda mendapat visibility timeout yang Anda kendalikan, redrive policy, dan dead letter queue tempat job yang terus gagal mendarat di suatu tempat yang bisa Anda periksa. AWS menganjurkan maximum receive count minimal lima, yang menyisakan ruang untuk satu percobaan ulang yang ter-throttle sebelum pesannya diparkir.
Setel visibility timeout queue minimal enam kali timeout fungsi consumer, sesuai anjuran AWS dengan alasan throttling yang sama. Urutannya bukan pilihan: Lambda memvalidasi event source mapping dan menolaknya kalau timeout fungsi lebih besar daripada visibility timeout. Dengan fungsi 330 detik di atas, itu berarti visibility timeout sekitar 1980 detik.
Contoh lengkap yang berfungsi
Consumer-nya. Ia membangun klien sekali saja, di luar handler, sehingga environment yang sudah hangat memakainya ulang alih-alih menyambung ulang pada setiap pesan.
# pip install capskip
import json, os
from urllib.parse import urlencode
from urllib.request import urlopen
from capskip import (CapSkip, ApiException, NetworkException,
TimeoutException, ValidationException)
# Built at cold start and reused while the environment stays warm.
solver = CapSkip(
host=os.environ["CAPSKIP_HOST"], # Server mode address
port=int(os.environ.get("CAPSKIP_PORT", 8080)),
recaptchaTimeout=300,
)
def lambda_handler(event, context):
failures = []
for record in event["Records"]:
job = json.loads(record["body"])
try:
result = solver.recaptcha(
sitekey=job["sitekey"],
url=job["pageurl"],
)
except NetworkException:
# No route to the solver. Retry this one message.
failures.append({"itemIdentifier": record["messageId"]})
continue
except (ApiException, TimeoutException, ValidationException) as exc:
print("giving up on this job:", exc)
continue
# Use the token here. It is short lived, so do not park it.
urlopen(job["pageurl"], data=urlencode(
{"g-recaptcha-response": result["code"]}).encode())
# Needs ReportBatchItemFailures on the event source mapping.
return {"batchItemFailures": failures}Laporkan pesan yang gagal alih-alih melempar exception. Melempar exception menggagalkan seluruh batch, dan SQS lalu mengembalikan setiap pesan di dalamnya ke queue, termasuk yang sudah Anda pecahkan, dan itulah masalah pemecahan ganda yang diperingatkan tabel di bawah. Partial batch response hanya mengulang record yang gagal, dan ia butuh setelan report batch item failures pada event source mapping supaya dihormati.
Ulangi NetworkException dan telan tiga yang lain. Tidak adanya rute ke pemecah CAPTCHA berarti job itu masih bisa berhasil nanti, sedangkan CAPTCHA yang tidak bisa dipecahkan, timeout atau parameter yang salah akan gagal dengan cara yang sama pada setiap percobaan, dan mengulangnya hanya menghabiskan waktu yang sama lagi. Keempat exception SDK diturunkan dari CapSkipError kalau Anda lebih suka menangkap satu hal saja.
Pengiriman itu adalah inti dari seluruh desain ini: lakukan hal yang menjadi tujuan token itu di dalam invocation yang sama. Token reCAPTCHA berlaku sekitar dua menit, jadi menulisnya ke database supaya diambil langkah berikutnya biasanya berarti mengambil sesuatu yang sudah kedaluwarsa. Rincian jendela itu ada di panduan pemecah reCAPTCHA v2, dan endpoint mentah di balik setiap panggilan SDK didokumentasikan di referensi API.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| 504 dari API Gateway setelah 29 detik, sementara CloudWatch menunjukkan fungsinya masih berjalan | Timeout integrasi, bukan timeout fungsi | Jawab request-nya seketika dan pecahkan di queue |
| Task timed out after 3.00 seconds | Timeout fungsi default, yang tidak ada yang mengubahnya sampai ia menggigit | Naikkan di atas timeout polling milik klien |
| NetworkException yang menyebut 127.0.0.1 | Loopback di dalam sandbox hanya menjangkau sandbox itu, dan CapSkip tidak ada di sana | Ubah ke mode Server dan setel variabel environment host |
| Koneksi yang hang sampai fungsinya kehabisan waktu | Fungsi itu dilampirkan ke VPC tanpa rute keluar, jadi paketnya tidak ke mana-mana alih-alih ditolak | Tambahkan NAT gateway. Melepaskannya dari VPC juga memulihkan akses internet, tetapi firewall Anda lalu tidak bisa mengizinkan satu alamat saja |
| Hang yang sama padahal NAT gateway sudah terpasang | Fungsi itu dilampirkan ke subnet publik, bukan ke subnet privat | Lampirkan ke subnet privat, yaitu subnet yang dirutekan ke NAT gateway |
| Berhasil dari laptop Anda tetapi tidak dari fungsinya | Alamat rumah Anda diizinkan lewat firewall sedangkan alamat AWS tidak | Izinkan Elastic IP milik NAT gateway |
| Setiap job dalam satu batch dipecahkan dua kali | Satu record melempar exception, jadi SQS mengembalikan seluruh batch, termasuk record yang sudah berhasil | Laporkan record yang gagal alih-alih melempar exception, dan nyalakan report batch item failures |
| Lambda menolak membuat event source mapping | Timeout fungsi lebih besar daripada visibility timeout queue, dan Lambda memvalidasinya | Naikkan visibility timeout menjadi minimal enam kali timeout fungsi |
| Pemecahan yang selesai selama invocation yang tidak berkaitan | Sebuah background thread dibekukan saat handler kembali lalu dicairkan pada panggilan berikutnya | Selesaikan pemecahannya sebelum kembali. Tidak ada fire and forget di sini |
| TimeoutException yang menyebut 300 detik | CapSkip tidak menjawab dalam timeout polling reCAPTCHA | Pastikan solver berjalan dan tidak jenuh. Menaikkan batas atas hanya menunda jawaban yang sama |
| CAPCHA_NOT_READY di dalam loop polling buatan sendiri | Jawabannya belum siap, yang merupakan keadaan antara yang normal dan bukan sebuah error | Biarkan SDK yang melakukan polling, atau baca panduan untuk kode tersebut |
| Unable to import module lambda_function, no module named capskip | Paketnya diinstal untuk arsitektur atau interpreter yang salah, atau letaknya di path yang salah di dalam layer | Instal dengan flag platform, implementation dan python version, dan taruh isi layer di bawah python pada akar zip |
FAQ
Bisakah CapSkip sendiri berjalan di dalam Lambda?
Tidak, dan memang tidak perlu. CapSkip adalah aplikasi Windows yang berjalan di perangkat keras milik Anda, dan SDK di dalam fungsi Anda hanyalah klien tipis untuknya lewat HTTP. Nyalakan mode Server, arahkan fungsi itu ke alamat tersebut, dan fungsi tersebut memanggilnya persis seperti skrip di meja yang sama. Pemecahannya tetap berlangsung di mesin Anda, dan itu juga sebabnya jumlah pemecahan tidak dihitung oleh siapa pun.
Apakah saya masih bisa memecahkan CAPTCHA di belakang API Gateway?
Kadang bisa, dan itu bergantung pada tipenya, bukan pada konfigurasi Anda. CAPTCHA gambar atau proof of work ALTCHA sering selesai dalam satu atau dua detik, yang muat di dalam 29 detik dengan sisa ruang yang lega. reCAPTCHA atau halaman challenge Turnstile sering kali tidak, dan ketika tidak, pemanggil mendapat 504 sementara pekerjaannya berlanjut dan terus menagih. Kalau seluruh produk Anda hanyalah satu endpoint sinkron, ajukan permintaan kenaikan timeout integrasi untuk REST API Anda dan ukur berapa lama lalu lintas Anda sendiri sebenarnya berjalan. Bentuk queue tetap bentuk yang tidak mengejutkan Anda pada pukul tiga dini hari.
Bagaimana caranya agar hanya fungsi saya yang bisa menjangkau pemecah CAPTCHA?
Lampirkan fungsi itu ke VPC, rutekan lalu lintas keluarnya lewat NAT gateway, dan izinkan Elastic IP milik gateway tersebut di firewall Anda. Itulah bentuk paling sederhana yang memberi Anda satu alamat sumber yang stabil, karena fungsi di luar VPC berangkat dari alamat milik AWS yang berubah tanpa sepengetahuan Anda. Site-to-Site VPN atau Direct Connect melakukan pekerjaan yang sama tanpa membuka port ke internet sama sekali. Jaga port pemecah CAPTCHA tetap tertutup bagi yang lain, dan perlakukan API key sebagai kunci kedua, bukan sebagai satu-satunya kunci.
Apakah pemecahan yang lama membuat fungsinya mahal?
Lambda menagih durasi waktu nyata, jadi fungsi yang hanya duduk menunggu jawaban dibayar dengan tarif yang sama dengan fungsi yang sedang berhitung. Itu argumen kedua untuk queue: consumer-nya tidak butuh memory size besar, karena ia menunggu jaringan dan bukan menghitung, dan tidak ada yang tertahan di hulu selama ia menunggu. Pemecahannya sendiri tidak memakan biaya per CAPTCHA, karena ia terjadi di mesin Anda sendiri. Pertukaran yang sama muncul di platform hosting lain, dan panduan Azure Functions membahas padanannya di sana.
Versi singkatnya
Pemecahan captcha di AWS Lambda butuh tiga keputusan dan semuanya diambil sebelum Anda menulis handler. Ubah CapSkip ke mode Server, karena loopback di dalam sandbox Lambda tidak menjangkau apa pun. Lampirkan fungsi itu ke VPC dan rutekan lewat NAT gateway supaya firewall Anda punya satu Elastic IP untuk diizinkan. Lalu berhenti memecahkan CAPTCHA di jalur request: API Gateway memberi Anda 29 detik, reCAPTCHA yang lambat butuh lebih, dan kembali lebih awal tidak menolong karena environment ikut membeku begitu handler Anda membeku. Antrekan job-nya, pecahkan di consumer yang timeout-nya berada di atas timeout klien, lalu pakai tokennya di dalam invocation yang sama yang menghasilkannya.
Setiap metode yang disediakan paket Python, beserta opsi yang diterima masing-masing, dicantumkan di halaman pemecah CAPTCHA Python.
Satu poin terakhir tentang sisi ekonominya, karena itulah yang membuat desain queue terasa nyaman. Job yang dibuang hanya memakan milidetik Lambda dan tidak lebih: pekerjaan bypass captcha berlangsung di perangkat keras yang sudah Anda bayar, jadi mengulang sebuah job atau membuang token yang sudah kedaluwarsa tidak pernah muncul di tagihan siapa pun.
