/** * Arcrun#92 重現腳本 — 「回應太大」到底會讓使用者看到什麼訊息 * * 為什麼要有這支:http_request 零件的接收緩衝區是 64 KB。回應超過這個大小時, * 舊版會硬把資料寫進零件記憶體(寫爆)或讓 host 丟例外回 1,零件對外只講得出一句 * "HTTP request failed"。使用者照那句去查連線/防火牆/URL,方向全錯。 * 這支腳本把那個情境真的做出來,讓「修之前 / 修之後」的訊息可以並排比。 * * 用法(本機,不碰任何線上實例): * * # 修之後(工作區現在的 wasm) * node scripts/repro-oversize-response.mjs 200000 * * # 修之前(把 main 上的舊 wasm 取出來當對照組;舊 wasm 不做容量握手 → 走舊路徑) * git show origin/main:.component-builds/http_request/component.wasm > /tmp/old-http_request.wasm * node scripts/repro-oversize-response.mjs 200000 /tmp/old-http_request.wasm * * # 對照:沒超過上限時兩者都應該正常 * node scripts/repro-oversize-response.mjs 1024 * * Node < 22.18 請加 --experimental-strip-types(本檔會 import 一支 .ts)。 */ import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, resolve } from 'node:path'; const here = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(here, '..'); const { createWasiShim } = await import( resolve(repoRoot, 'cypher-executor/src/lib/wasi-shim.ts') ); const responseBytes = Number(process.argv[2] ?? 200_000); const wasmPath = process.argv[3] ? resolve(process.argv[3]) : resolve(repoRoot, '.component-builds/http_request/component.wasm'); // 假裝遠端回了一包很大的 JSON(2xx,host function 照原樣把 body 交給零件) const filler = 'x'.repeat(Math.max(0, responseBytes - 14)); const remoteBody = JSON.stringify({ items: filler }); const shim = createWasiShim( JSON.stringify({ url: 'https://example.com/big-list', method: 'GET' }), { http_request: async () => remoteBody }, ); const instance = await WebAssembly.instantiate( await WebAssembly.compile(readFileSync(wasmPath)), shim.imports, ); shim.setMemory(instance.exports.memory); let crashed = null; try { await shim.run(instance); } catch (e) { crashed = e instanceof Error ? e.message : String(e); } const stdout = shim.getStdout().trim(); const stderr = shim.getStderr().trim(); console.log(`wasm : ${wasmPath}`); console.log(`模擬回應大小 : ${remoteBody.length} bytes(零件緩衝區上限 65536 bytes)`); console.log(''); // 以下複刻 .component-builds/http_request/src/index.ts 的收尾, // 印出「使用者真的會拿到的那一包」 if (stderr) console.log(`零件 stderr : ${stderr.slice(0, 300)}`); if (crashed) console.log(`WASM 執行中止 : ${crashed}`); if (!stdout) { console.log('使用者看到 : HTTP 500 {"success":false,"error":"WASM component produced no output"}'); process.exit(0); } let parsed; try { parsed = JSON.parse(stdout); } catch (e) { console.log(`使用者看到 : HTTP 500 {"success":false,"error":"${e.message}"}`); process.exit(0); } console.log(`success : ${parsed.success}`); console.log(`error : ${parsed.error ?? '(無)'}`); if (parsed.success) { const body = parsed.data?.body ?? ''; console.log(`data.body 長度 : ${String(body).length} bytes`); }