RFC 9457 problem+json standardizza le risposte di errore come oggetto JSON con type, title, status, detail e instance, servito come application/problem+json — così i client fanno il parsing degli errori in modo uniforme. La paginazione suddivide le collection grandi in pagine usando un cursor (stabile) o un offset (semplice), con i link next/prev trasportati nell'header Link di RFC 8288.
problem+json: un oggetto di errore standard
Le forme di errore ad hoc costringono ogni client a gestire come caso speciale ogni API. RFC 9457 (che rende obsoleto RFC 7807) fissa un unico formato di errore JSON estensibile, servito con il media type application/problem+json così che un client sappia di doverlo interpretare come un problem. L'oggetto ha cinque membri standard: type, una URI che identifica la classe del problema e funge anche da link alla documentazione umana; title, un riassunto breve, stabile e leggibile dall'uomo di quella classe; status, lo status code HTTP ripetuto nel body; detail, una spiegazione leggibile dall'uomo specifica di questa occorrenza; e instance, una URI che identifica questa particolare occorrenza. Qualsiasi membro aggiuntivo (un array di errori di validazione, un request id, un suggerimento di retry) estende l'oggetto senza rompere quelli standard, così un client generico legge i campi comuni e un client specifico legge anche le estensioni.
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://example/problems/invalid-destination",
"title": "Invalid destination number",
"status": 422,
"detail": "The field 'to' is not a valid E.164 number.",
"instance": "/v1/messages/req-01HZY9",
"errors": [ { "field": "to", "issue": "not_e164" } ]
}Type, title e instance
La disciplina che rende utile problem+json è mantenere type e title stabili e orientati alla macchina, lasciando che detail e instance varino per occorrenza. Un client dovrebbe diramarsi su type (una URI stabile), mai sulla prosa in detail o title, perché detail è libero di cambiare e può essere localizzato. La URI di type idealmente risolve alla documentazione di quella classe di errore, così che uno sviluppatore che la segue atterri su una spiegazione e un rimedio. instance identifica la specifica richiesta fallita — utile da citare in un ticket di supporto — e spesso rispecchia un request id che l'API restituisce anche in un header. Poiché type e title identificano la classe e status/detail/instance descrivono l'occorrenza, un solo problem type può essere riutilizzato su molti fallimenti senza ambiguità.
Tassonomia degli errori: retryable vs terminale
Oltre alla forma, il client deve sapere cosa fare, e la classe dello status code risponde a questo. Un 4xx è un errore del client: la richiesta così com'è inviata non riuscirà mai, quindi ritentarla senza modifiche è inutile — la correzione è correggere la richiesta (400/422 per input errato, 401/403 per l'auth, 404 per una risorsa mancante, 409 per un conflitto). Un 5xx è un errore del server o a monte: la richiesta potrebbe benissimo riuscire più tardi, quindi è ritentabile con backoff — idealmente sotto lo stesso Idempotency-Key così che il retry non possa causare un doppio invio. 429 è il caso transitorio speciale: ritentabile, ma solo dopo il ritardo Retry-After. Codificare questo nello status code (e rinforzarlo con un problem type) consente a un client di decidere automaticamente tra correggi-e-reinvia, arretra-e-ritenta e rinuncia.
| Classe | Significato | Riprovabile? | Azione del client |
|---|---|---|---|
| 400 / 422 | Input non valido/non corretto | No | Correggi la richiesta, poi inviala di nuovo |
| 401 / 403 | Non autenticato / vietato | No | Correggere le credenziali o l'ambito |
| 404 | Risorsa non trovata | No | Correggere l'identificativo |
| 409 | Conflitto di stato (ad es. riutilizzo della chiave) | No | Risolvere il conflitto |
| 429 | Limite di frequenza superato | Sì (differito) | Attendi il Retry-After, quindi riprova |
| 500 / 502 / 503 | Errore del server/a monte | Sì | Backoff + riprova con la stessa chiave di idempotenza |
Paginazione: cursor vs offset
Un endpoint di collection che potrebbe restituire milioni di messaggi deve paginare. I due modelli principali barattano semplicità contro correttezza. La paginazione a offset (?limit=50&offset=100) è banale e consente a un client di saltare a una pagina arbitraria, ma è instabile su una collection che cambia: se righe vengono inserite o cancellate tra le pagine, gli elementi si spostano e il client salta o ripete record — e gli offset profondi diventano lenti perché lo store deve scansionare e scartare tutto ciò che li precede. La paginazione a cursor (keyset) consegna al client un cursor opaco che codifica la posizione dell'ultimo elemento visto (per esempio un id e un timestamp); la richiesta successiva chiede le righe dopo quel cursor. È stabile sotto scritture concorrenti e resta veloce a qualsiasi profondità perché la query si posiziona direttamente sul cursor, al costo di non supportare salti di pagina arbitrari. Per risorse ad alto volume e append-heavy come i log di messaggi, la paginazione a cursor è il default corretto.
| Proprietà | Offset | Cursore (keyset) |
|---|---|---|
| Richiesta | ?limit=50&offset=100 | ?limit=50&after=<cursor> |
| Salto di pagina arbitrario | Sì | No (solo sequenziale) |
| Stabile in caso di inserimenti/cancellazioni | No — gli elementi si spostano | Sì |
| Prestazioni sulle pagine profonde | Degrada (scansiona + scarta) | Costante (seek al cursore) |
| Ideale per | Collezioni piccole e stabili | Log di grandi dimensioni con molte aggiunte in coda |
L'header Link (RFC 8288)
Invece di seppellire gli URL next/previous nel body, un'API può trasportarli nell'header Link standard definito da RFC 8288 (Web Linking), usando relation type registrati — next, prev, first, last. Il client segue il link rel="next" per avanzare senza costruire URL da solo né conoscere lo schema del cursor, il che mantiene il meccanismo di paginazione interamente controllato dal server e gli consente di evolvere (offset oggi, cursor domani) senza rompere i client. Un approccio comune con doppia sicurezza rispecchia anche i link nel body JSON per i client che non leggono gli header, ma l'header Link è la loro casa standardizzata a livello di transport ed è lo stesso meccanismo che un browser o un client API ben educato già comprende.
HTTP/1.1 200 OK
Content-Type: application/json
Link: </v1/messages?after=01HZY8Q3M6&limit=50>; rel="next",
</v1/messages?limit=50>; rel="first"
{ "data": [ /* up to 50 messages */ ],
"page": { "count": 50, "has_more": true } }Limiti e default di page size
La page size deve essere delimitata sul server, non lasciata al client, altrimenti una singola richiesta di una pagina enorme diventa un vettore di denial-of-service e una fonte di latenza imprevedibile. Il pattern è un default sensato (usato quando il client omette limit) e un massimo rigido a cui il server fa clamp silenziosamente — un client che chiede di più riceve semplicemente il massimo. Pubblicizzare sia la page size effettiva sia se restano altre pagine (un flag has_more o la presenza di un link rel="next") consente a un client di iterare in sicurezza: continuare a seguire next finché non c'è più un next. Combinato con la paginazione a cursor e gli errori problem+json, questo dà un'API di collection prevedibile da consumare a qualsiasi scala. BroadNet documenta i suoi codici di errore e la paginazione concreti per gli sviluppatori.
Domande frequenti
Che cos'è problem+json e perché usarlo?
problem+json (RFC 9457, che rende obsoleto RFC 7807) è un formato di errore JSON standard servito come application/problem+json, con i membri type, title, status, detail e instance. Consente a ogni client di fare il parsing degli errori allo stesso modo invece di gestire come caso speciale una forma ad hoc per ogni API, ed è estensibile così che si possano aggiungere membri extra senza rompere quelli standard.
Su quale campo problem+json dovrebbe diramarsi un client?
type — una URI stabile che identifica la classe di errore. title e detail sono leggibili dall'uomo e possono cambiare o essere localizzati, quindi il codice non dovrebbe mai fare switch sulla loro prosa. La URI di type idealmente risolve alla documentazione di quell'errore, mentre status, detail e instance descrivono la specifica occorrenza.
Come fa un client a sapere se ritentare un errore?
Dalla classe dello status. Un 4xx è un errore del client che non riuscirà senza modifiche, quindi correggi la richiesta anziché ritentare. Un 5xx è un guasto del server o a monte che potrebbe riuscire più tardi, quindi ritenta con backoff sotto lo stesso Idempotency-Key. 429 è ritentabile ma solo dopo il ritardo Retry-After.
Qual è la differenza tra paginazione a cursor e a offset?
L'offset (?limit&offset) è semplice e supporta il salto a qualsiasi pagina ma è instabile quando righe vengono inserite o cancellate tra le richieste e rallenta agli offset profondi. La paginazione a cursor (keyset) passa un marcatore opaco dell'ultimo elemento visto e chiede le righe dopo di esso — stabile sotto scritture concorrenti e veloce a qualsiasi profondità, ma solo sequenziale. Il cursor è il default corretto per i grandi log di messaggi.
Come aiuta la paginazione l'header Link di RFC 8288?
Trasporta gli URL delle pagine next, prev, first e last in un header standard usando relation type registrati, così che il client segua rel="next" per avanzare senza costruire URL o conoscere lo schema del cursor. Questo mantiene il meccanismo di paginazione controllato dal server e libero di evolvere, ed è un meccanismo che i client HTTP generici già comprendono.
Perché limitare la page size sul server?
Perché una page size illimitata consente a una richiesta di estrarre un result set enorme, il che è un rischio di denial-of-service e dà latenza imprevedibile. Il server applica un default quando limit è omesso e fa clamp silenziosamente a un massimo rigido, e pubblicizza se restano altre pagine così che i client possano iterare seguendo next finché non è assente.
Specifiche della fonte
Gli standard pubblicati su cui si basa questa pagina. Si apre in una nuova scheda.