Un modulo AJAX si aspetta JSON ma riceve HTML: esamina la risposta passo dopo passo
Una risposta 200 non dimostra che sia JSON. Percorri il pannello Rete passo dopo passo e separa i guasti di trasporto, rappresentazione e contratto.
Il modulo viene inviato, compare l’indicatore e la console segnala un token < inatteso in JSON alla posizione 0. La pagina in sé si carica bene, ed è per questo che il primo istinto è dare la colpa al parser o al tunnel. Di solito non c’entra né l’uno né l’altro: l’invio è una richiesta diversa dal caricamento della pagina, ed è tornato con qualcosa che non è JSON. Lavora nell’ordine in cui il browser lo rivela davvero, perché le quattro risposte possibili richiedono quattro correzioni diverse.
Passo 1: cattura l’invio che è avvenuto realmente
Apri il pannello Rete prima di inviare, imposta il registro per conservare le voci tra una navigazione e l’altra, poi fai un invio di prova innocuo su un account che ti è consentito usare. Seleziona la richiesta avviata dal modulo stesso, non la richiesta del documento della pagina.
Attenzione ai filtri. Un filtro XHR o Fetch è comodo, ma se il framework invia ancora tramite un iframe nascosto o un modulo creato dinamicamente, il filtro nasconderà l’unica richiesta che conta. Invia prima una volta senza filtro e guarda cosa è comparso.
Per la richiesta scelta, annota sei cose prima di qualsiasi interpretazione: il metodo, l’URL completo con la stringa di query, lo stato, la catena di reindirizzamenti, il Content-Type della risposta e le prime righe del corpo. Il riferimento al pannello Rete di Chrome DevTools copre le viste Headers, Response e Initiator dove si trovano quei dati, e i pannelli equivalenti degli altri browser mostrano gli stessi campi.
Passo 2: conferma che sia la richiesta che pensi
Un numero sorprendente di errori JSON sono in realtà errori di instradamento. Se il gestore dell’invio lancia un’eccezione prima di chiamare preventDefault, il browser esegue un POST nativo del modulo all’indirizzo indicato dall’attributo action, e il server risponde con una pagina HTML completa. Il gestore non viene mai eseguito, quindi non è mai stato chiesto JSON.
Confronta l’URL nel pannello con quello che chiama il tuo JavaScript. Cerca un reindirizzamento che finisce altrove, una barra finale mancante o in più, una discrepanza di metodo in cui il gestore invia GET e l’endpoint accetta POST, o un modulo che invia a un percorso relativo da una pagina che si è spostata. Segui la catena di reindirizzamenti fino in fondo e annota l’URL finale, non il primo.
Poi controlla l’iniziatore. Se la richiesta è arrivata da una navigazione di documento e non dal tuo script, hai già trovato il problema, e nessuna modifica al parser della risposta aiuterà.
Passo 3: classifica l’esito in quattro categorie
Ogni esito appartiene esattamente a una di queste, e la categoria decide dove guardare dopo.
| What you see | Category | Where to look |
|---|---|---|
| Request never finishes, TLS or connection error | No usable response | Endpoint reachability, certificate, front-end timeout |
| Request completes, script gets an opaque failure | Access blocked | Cross-origin policy, credentials mode, missing response header |
| 200 with an HTML body or a login form | Wrong representation | Redirect, expired session, route fallback, error handler |
| Body begins with a notice then valid JSON | Wrong representation | Server-side warning output before serialisation |
| Valid JSON, framework rejects it | Contract mismatch | Envelope shape, field names, value types |
Le righe centrali sono quelle che vengono diagnosticate male, perché lo stato è 200. Una pagina di accesso raggiunta dopo un reindirizzamento è una risposta HTTP del tutto valida e un pessimo documento JSON. Il tipo di contenuto application/json non lo ripara: è un’etichetta, e un corpo etichettato male continua a non potersi analizzare. Accesso bloccato è la riga che sembra una richiesta completata: il pannello mostra uno stato, ma la politica di origine incrociata trattiene il corpo, quindi per lo script chiamante è inutilizzabile quanto un timeout.
La quarta riga è altrettanto comune su stack PHP e framework più vecchi. Un avviso di deprecazione stampato prima del carico codificato produce un corpo che nessun parser JSON accetterà, e spesso quell’avviso è tutta la spiegazione.
Passo 4: leggi l’errore del parser alla lettera
Il testo dell’errore nomina il primo carattere che ha rotto l’analisi, e questo di solito identifica la categoria senza alcuna congettura.
| Error text (Chrome/V8) | What it means |
|---|---|
| Unexpected token < in JSON at position 0 | The body starts with markup: an HTML page or an error document |
| Unexpected end of JSON input | Empty body, or the response was truncated |
| Unexpected token o in JSON at position 1 | You parsed something already parsed, typically an object rather than text |
| Unexpected token u | The body starts with the bare word undefined, usually an empty response rendered as a string |
Un corpo vuoto con 200 è un caso a sé: l’endpoint è stato eseguito, non ha prodotto nulla, e qualcosa più a valle lo ha preso per un successo. È un guasto di contratto dell’applicazione e non di trasporto, e il suo posto è nel passo successivo, non nel proxy. Firefox e Safari formulano gli stessi quattro guasti in modo diverso, quindi confronta sul carattere o sul corpo vuoto e non sulla stringa esatta.
Passo 5: correggi lo strato che le prove indicano
Per un problema di instradamento, correggi la richiesta: sistema l’action, impedisci al gestore di cadere nel vuoto, allinea endpoint e metodo, e riprova. Per un problema di rappresentazione, correggi ciò che ha prodotto il corpo e scopri perché viene restituito un documento di errore dove ci si aspettava dati. Per un problema di contratto, leggi la struttura realmente attesa dal framework per la versione che esegui e dai al corpo quella forma invece di allentare il client.
Una risposta con accesso bloccato richiede un passaggio proprio, perché la richiesta può essere corretta e il corpo può non raggiungere mai il tuo codice. Confronta l’Access-Control-Allow-Origin della risposta con l’Origin esatto inviato dal browser, e ricorda che un jolly non soddisfa una richiesta con credenziali. Abbina credentials: 'include' sul client a Access-Control-Allow-Credentials: true sul server, e verifica se la chiamata ha usato la modalità no-cors, dove un corpo opaco è il risultato documentato e non un difetto.
Sostituire il parser con qualcosa di permissivo è l’unica correzione da evitare. Valutare la risposta come codice, o sbucciare il testo finché non si analizza, nasconde la discrepanza di contratto e aggiunge una superficie di iniezione in cambio del silenzio. Allo stesso modo, controllare lo stato separatamente dal corpo è necessario: un 200 che porta un oggetto di errore a livello applicativo è normale in molte API e va gestito leggendo il carico.
Quando ti serve aiuto da qualcun altro, condividi un resoconto minimo — metodo, URL, stato, URL finale dopo i reindirizzamenti, tipo di contenuto della risposta, forma del corpo e contratto atteso — invece di un’esportazione grezza. Gli archivi di rete contengono cookie, token, stringhe di query e i dati personali che erano nel modulo, e un ripulitore automatico non è una revisione.
- Cattura la richiesta senza filtro prima di cambiare qualsiasi codice.
- Conferma che l’iniziatore fosse il tuo script e che l’URL finale sia il tuo endpoint.
- Classifica la risposta prima di toccare il parser.
- Correggi instradamento, accesso, rappresentazione o contratto, e mai allentando il parser.