Nella scrittura di API capita spesso di vedere gestite in maniera scorretta i metodi PUT e PATCH.
Anche sul web c’è un po’ di confusione causata da molti tutorial imprecisi e a volte dall’uso improprio che viene fatto delle specifiche HTTP anche nei framework di sviluppo.
Il lavoro di interoperabilità fortunatamente fa’ un po’ di chiarezza sul tema, partendo dalle basi.
HTTP assegna ad ogni metodo un significato ben preciso: tecnicamente questo significato viene chiamato “semantica”. Vediamo i due esempi più noti:
- il metodo GET “scarica” una risorsa;
- il metodo POST “processa” una richiesta secondo la semantica della risorsa;
Con POST l’implementatore definisce il comportamento della risorsa eg. https://api.example/library/v1/books: questo non è definito a priori e dev’essere esplicitamente indicato nella documentazione OpenAPI.
Possiamo usare POST quindi per creare, cancellare, modificare questa o altre risorse ma dobbiamo evitare di sovraccaricare di significati la risorsa per rendere l’API fruibile; a tal fine possiamo usare i metodi PUT e PATCH.
Il metodo PUT “sostituisce” una risorsa, creandola all’occorrenza; la specifica ci ricorda quindi che PUT rimpiazza la risorsa destinazione usando la rappresentazione contenuta nel payload body della richiesta. Prima regola quindi:
Usate PUT solo se volete SOSTITUIRE COMPLETAMENTE una risorsa.
PATCH richiede invece alcuni accorgimenti, perché questo metodo non è definito nelle nuove specifiche di HTTP/1.1 del 2014 ma nel precedente RFC 5789.
Come indicato in questa considerazione NON SI DOVREBBE associare un significato di patch a dei media-type che non lo prevedono (eg. application/json
o application/xml
) ma utilizzare dei media-type adeguati.
E’ possibile ad esempio usare application/merge-patch+json
definito in RFC 7386 facendo attenzione:
- che l’HTTP method PATCH rifiuti richieste con media-type non adeguato con HTTP status 415 Unsupported Media Type;
- che il media-type di patching sia compatibile con gli schemi utilizzati;
- di verificare le considerazioni di sicurezza presenti in RFC 7396#section-5 e RFC 5789#section-5
Vediamo un esempio di merge-patch+json dove:
- la property
a
viene valorizzata az
; - la property
c.f
viene rimossa
L’esempio chiarisce che questa specifica non permette di impostare una property anull
.
PATCH /target HTTP/1.1
Host: example.org
Content-Type: application/merge-patch+json
{
"a": "z",
"c": { "f": null }
}
Il medatype json-patch+json è più complesso del precedente ma copre più casi d’uso, comportandosi come un changelog:
- se a.b.c == “foo” lo rimuove
- imposta a.b.c = [“foo”, “bar”]
- imposta a.b.c = 42
- imposta a.b.d = 42 e rimuove a.b.c
- imposta a.b.e = 42 e a.b.d = 42
PATCH /my/data HTTP/1.1
Host: example.org
Content-Length: 326
Content-Type: application/json-patch+json
If-Match: "abc123"
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
Un altro errore, è quello di usare PATCH su una risorsa diversa da quella che si vuole modificare, ad esempio invocando PATCH /user/123/enable
senza passare un request body. Questo approccio non è interoperabile: non solo la risorsa di destinazione è diversa, ma l’assenza del request body denota che la richiesta non vuole fare nessuna modifica. Quando si utilizza il nome di un verbo o di un’azione in un path, il metodo corretto da utilizzare è di solito POST
poiché la sua semantica è interamente definita dalla risorsa di destinazione: questo significa che l’implementatore può associare ad essa il comportamento che preferisce.
Sul verificatore openapi online stiamo implementando ulteriori controlli per aiutare gli sviluppatori a non commettere questo tipo di errori.
A tutti i curiosi, potete approfondire leggendo le specifiche o commentando questo post anche via https://slack.developers.italia.it