Am găsit de ce `json_decode` întorcea null pe un payload de 2 MB

这篇内容暂时只有 Română 版本,以下是原文。

Rulez un serviciu mic pe PHP 8.2, Alpine, container de 512 MB, care primește webhook-uri JSON de la un partener. Volumele nu sunt mari: 3–4 req/s, dar payload-urile variază între 200 KB și 2,3 MB. La un moment dat, cam 1 din 40 de request-uri intra pe ramura de eroare cu json_decode returnând null, deși partenerul jura că trimite JSON valid. Nu era nici timeout, nici body gol.

Primul pas a fost să nu mai ghicesc, ci să măsor. Am adăugat temporar un log care scrie json_last_error_msg() și lungimea body-ului, plus memory_get_peak_usage(true):

$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
if ($data === null && json_last_error() !== JSON_ERROR_NONE) {
    error_log(sprintf('json=%s len=%d peak=%d', json_last_error_msg(), strlen($raw), memory_get_peak_usage(true)));
}

Rezultatul a fost clar după două zile: Maximum stack depth exceeded pe payload-urile mari, nu Syntax error. Partenerul avea un array imbricat generat dintr-un export de arbore de categorii, cu adâncime peste 512. json_decode are implicit depth = 512 și, când îl depășește, întoarce null fără să arunce excepție dacă nu folosești flag-ul potrivit.

Am schimbat apelul:

$data = json_decode($raw, true, 2048, JSON_THROW_ON_ERROR);

Și am prins JsonException în controller, ca eroarea să ajungă în log cu context, nu într-un 500 mut. Am urcat și memory_limit la 256M, pentru că la 2,3 MB de JSON cu obiecte mari, reprezentarea internă a array-urilor asociative consumă de câteva ori mai mult decât textul brut.

De ce funcționează: adâncimea e o limită de siguranță împotriva recursivității, nu o caracteristică a formatului. Dacă o ridici, accepți că parserul poate consuma stivă și memorie proporțional cu structura, deci trebuie să ai o limită de body la nivel de server (eu am client_max_body_size 4m în nginx) și un timeout. Altfel muți problema din json_decode în OOM killer.

Unde se strică: dacă partenerul trimite array-uri mai adânci de 2048, ești în aceeași situație, doar cu un număr mai mare. Și dacă activezi JSON_THROW_ON_ERROR global într-un cod vechi care se baza pe null, începi să arunci excepții în locuri care nu le prind. Am pățit-o într-un job de import care verifica doar if (!$data).

Voi ce limită de adâncime folosiți în producție și ați văzut vreodată un payload legitim care să o depășească?

0
赞同度 100% · 0 条评论 · 3 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。