Menangani Cloudflare Challenge di Adaptor AmiApp dengan FlareSolverr #14
wawiwuwawu
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Menangani Cloudflare Challenge di Adaptor AmiApp dengan FlareSolverr
Dokumentasi pengembangan AmiApp — catatan teknis atas perubahan besar arsitektur adaptor.
Latar Belakang
Saat melanjutkan pengembangan aplikasi, login tiba-tiba gagal padahal tidak ada perubahan kode di sisi aplikasi maupun adaptor. Dari log server terlihat respons yang dikembalikan portal bukan JSON seperti biasa, melainkan halaman HTML "Just a moment...". Portal Amikom Purwokerto mengaktifkan Cloudflare Bot Protection berupa JavaScript Managed Challenge (dengan Turnstile di beberapa titik).
Mekanismenya: request hanya dilayani setelah browser menjalankan JavaScript tantangan dan menerima cookie clearance (
cf_clearance). Request HTTP langsung tanpa cookie tersebut selalu dijawab halaman tantangan. Akibatnya seluruh alur autentikasi dan scraping di adaptor berhenti bekerja sekaligus.Solusi yang dipilih adalah FlareSolverr: proxy server berbasis Chromium headless yang membuka halaman seperti browser asli, menyelesaikan tantangan Cloudflare, lalu mengembalikan hasil beserta cookie clearance. Integrasi ini menuntut perombakan besar pada arsitektur adaptor. Dokumen ini merangkum empat masalah utama yang muncul beserta solusinya.
Arsitektur Solusi
Adaptor membaca dua variabel baru dari
.env:FLARESOLVERR_URL(endpoint layanan, misalhttp://localhost:8191/v1) danFLARESOLVERR_TIMEOUT(default 60000 ms). JikaFLARESOLVERR_URLdiatur, autentikasi dan scraping dialihkan melalui FlareSolverr; jika tidak, perilaku lama tetap berjalan.Alur login bekerja dalam satu sesi browser persisten per pengguna (
session_${pengguna}): pertama request GET halaman login untuk menyelesaikan tantangan dan mengambil cookie clearance, lalu POST kredensial untuk mendapatkan session resmi portal. Sesi ini bertahan selama pengguna aktif dan dihancurkan lewatsessions.destroysaat logout. Detail internal endpoint disengaja tidak dibahas di dokumen publik ini.Masalah 1: Request Paralel Menimpa Navigasi Browser
FlareSolverr mengeksekusi satu tab Chromium per session ID. Aplikasi mobile mengirim beberapa request hampir bersamaan setelah login (dashboard, notifikasi, agenda). Tanpa pengaturan, navigasi antar request saling menimpa dan hasil scraping menjadi salah.
Solusi: mutex per sesi (
acquireFsLockdisrc/lib/client.ts). Setiap request untuk session yang sama diantrekan sekuensial; lock dilepas di blokfinallyagar antre tidak macet saat terjadi error.Masalah 2: Crash FlareSolverr pada POST Tanpa Body
FlareSolverr melempar
IndexError: string index out of rangeketika menerima perintahrequest.postdenganpostDatakosong (""). Adaptor memang kerap mengirim POST tanpa payload.Solusi: inject
postData = "_=1"(cache-buster standar) untuk semua POST tanpa body. FlareSolverr tidak crash dan server tujuan tetap menerima method POST yang valid.Masalah 3: Request via Browser Headless Lambat
Request yang dieksekusi FlareSolverr jauh lebih lambat daripada HTTP langsung. Jika semua trafik dipaksa lewat browser, latensi aplikasi meningkat tanpa perlu.
Solusi: strategi direct-first dengan fallback otomatis. Cookie
cf_clearancedan User-Agent dari sesi FlareSolverr disimpan di memori (session store). Request dicoba dulu via HTTP langsung (~100 ms) membawa cookie tersebut. Bila respons terdeteksi sebagai tantangan baru, adaptor otomatis mengeksekusi ulang request tersebut melalui FlareSolverr — termasuk warm-upGET /mainjika tantangan muncul di tengah POST. Hasil warm-up dicache 30 detik (FS_WARMUP_TTL = 30000) agar sesi hangat tidak dibuang sia-sia.Deteksi tantangan dilakukan
isCloudflareChallenge(src/lib/client.ts): memeriksa status 403/503 serta penanda khas Cloudflare pada body (_cf_chl_opt,just a moment,/cdn-cgi/,turnstile,attention required,access denied). Header AJAX sepertiX-Requested-With: XMLHttpRequestjuga diteruskan ke payload FlareSolverr agar request ke controller tetap dikenali sebagai AJAX.Masalah 4: JSON Terbungkus Tag
<pre>oleh ChromiumSebagian endpoint portal mengembalikan JSON dengan mimetype JSON murni. Ketika halaman semacam itu dibuka Chromium FlareSolverr, browser membungkus teksnya menjadi HTML:
<html><body><pre style="word-wrap: break-word;">[...]</pre></body></html>. Akibatnya:JSON.parse()langsung melempar SyntaxError → error 500 di adaptor.startsWith('<')menganggapnya halaman error → data hilang padahal sebenarnya ada.Solusi: helper
extractJsonString(src/lib/parser.ts) mengambil isi string JSON murni dari dalam tag<pre>/<body>sebelum parsing. Seluruh service (23 endpoint JSON) dibungkussafeParseJson<T>(body, fallback)— versi aman yang menangkap SyntaxError dan mengembalikan fallback ([]/{}) bila format tak dikenal. Efek samping positifnya: adaptor kini tahan terhadap halaman maintenance maupun HTML error apa pun.Hasil
Setelah perombakan:
Status saat ini: sebagian besar endpoint sudah stabil dan dipakai aplikasi. Alur upload belum diuji penuh karena membutuhkan data nyata pada sistem produksi kampus — pengujian semacam ini akan dilakukan bertahap dengan data yang tepat, bukan data sembarang.
Kesimpulan
Beberapa hal yang bisa dibawa dari kasus ini:
All reactions