Web service, XML, JSON, SOAP e REST
In questa pagina 9
Finora HTTP ha trasportato documenti destinati a una persona (pagine HTML). Un'applicazione può però voler chiamare un servizio su un'altra macchina e ricevere dati, non una pagina: il prezzo di un libro, l'elenco degli appelli, l'esito di un pagamento. Questo è un web service.
Definizione (web service). Un sistema software progettato per supportare l'interazione fra macchine attraverso una rete, in cui le richieste e le risposte sono messaggi in un formato standard, di solito trasportati da HTTP.
Servono tre cose: un formato per i dati (XML o JSON), un modo per esprimere le operazioni (i messaggi SOAP, oppure i metodi HTTP applicati a risorse con REST) e un trasporto (HTTP: HTTP 1.1 - connessioni persistenti, Content-Length e chunked transfer encodingHTTP/1.1 (oggi RFC 9110 e 9112) rende la connessione persistente di default (si chiude solo con "Connection: close"), rende obbligatorio l'header Host (virtual hosting) e introduce i nuovi metodi PUT, DELETE, OPTIONS, TRACE, Expect: 100-continue, richieste di intervalli (206) e Transfer-Encoding: chunked; con la connessione persistente il client deve sapere dove finisce ogni risposta: lunghezza del corpo nell'ordine HEAD/1xx/204/304 senza corpo, Transfer-Encoding chunked, Content-Length, altrimenti fino alla chiusura; il chunked divide il corpo in blocchi preceduti dalla lunghezza in esadecimale, termina con un chunk 0 e un trailer facoltativo, e si decodifica contando i byte dichiarati (non cercando CRLF).HTTP 1.1 - connessioni persistenti, Content-Length e chunked transfer encoding →). Questa nota presenta i formati, SOAP e REST e li confronta; un servizio REST completo in C è in Esercizio - Servizio REST con JSON (sul modello della prova pratica).
XML
XML (eXtensible Markup Language, W3C) descrive dati strutturati come un albero di elementi delimitati da marcatori (tag). A differenza di HTML i tag non sono prefissati: li definisce chi progetta il formato.
<?xml version="1.0" encoding="UTF-8"?>
<libro id="42">
<titolo>Reti di Calcolatori</titolo>
<autori>
<autore>Rossi</autore>
<autore>Bianchi</autore>
</autori>
<prezzo valuta="EUR">29.9</prezzo>
<disponibile>true</disponibile>
</libro>Le regole perché un documento sia ben formato (well-formed):
- c'è un solo elemento radice;
- ogni tag di apertura ha il suo tag di chiusura e gli elementi sono annidati correttamente (
<a><b></b></a>, mai<a><b></a></b>); un elemento vuoto si può scrivere<br/>; - i nomi distinguono maiuscole e minuscole;
- i valori degli attributi (
id="42") stanno fra virgolette; - i caratteri speciali nel testo si scrivono con entità:
<(<),>(>),&(&),",'; per un blocco di testo grezzo c'è<![CDATA[ ... ]]>; - la dichiarazione iniziale
<?xml version="1.0" encoding="UTF-8"?>è facoltativa ma consigliata; - i commenti sono
<!-- ... -->.
Gli spazi dei nomi (namespace, xmlns:m="http://example.com/libreria") evitano conflitti fra tag di vocabolari diversi: <m:prezzo> e <p:prezzo> sono elementi distinti. La validazione (il documento rispetta uno schema) si fa con una DTD o, meglio, con XML Schema (XSD), che dichiara elementi, tipi e vincoli: un documento ben formato non è automaticamente valido. L'XML si legge in due modi: DOM (si carica tutto in un albero in memoria) o SAX (eventi mentre si legge, senza tenere tutto); in C si usano librerie come libxml2 o expat.
JSON
JSON (JavaScript Object Notation, RFC 8259) è un formato di testo più leggero, nato dalla sintassi degli oggetti di JavaScript e oggi usato da quasi tutti i linguaggi. Ha sei tipi di valore:
| tipo | sintassi | esempio |
|---|---|---|
| oggetto | { "chiave": valore, ... } (chiavi stringhe) |
{"id": 42} |
| array | [ valore, ... ] |
["Rossi", "Bianchi"] |
| stringa | caratteri fra virgolette doppie, con escape | "Reti di Calcolatori" |
| numero | decimale (niente zeri iniziali, niente esadecimale, niente NaN) |
29.9, -3, 1e3 |
| booleano | true o false |
true |
| nullo | null |
null |
Escape nelle stringhe: \", \\, \/, \b, \f, \n, \r, \t, \uXXXX (carattere Unicode a 4 cifre esadecimali). Un carattere di controllo (codice sotto 0x20) deve essere escapato. Non sono ammessi commenti né virgole finali. Il testo scambiato è UTF-8; il tipo MIME è application/json.
Lo stesso libro in JSON:
{"id":42,"titolo":"Reti di Calcolatori","autori":["Rossi","Bianchi"],"prezzo":29.9,"disponibile":true}XML o JSON
Lo stesso libro in forma compatta occupa 102 byte in JSON contro 174 in XML: il tag di chiusura ripete ogni nome.
| XML | JSON | |
|---|---|---|
| dimensione | maggiore (tag di apertura e chiusura) | minore |
| tipi di dato | solo testo (i tipi si dichiarano con XSD) | numeri, booleani, null, array nativi |
| leggibilità per un programma | serve un parser XML, DOM o SAX | corrisponde alle strutture dei linguaggi (oggetti, liste) |
| struttura | elementi, attributi, testo misto, namespace, commenti | solo oggetti e array |
| validazione | DTD, XSD, XSLT per trasformare | JSON Schema |
| uso tipico | SOAP, documenti, configurazioni complesse | API REST, scambio di dati nel Web |
Nessuno dei due è migliore in assoluto: XML è più ricco (namespace, documenti con testo misto a marcatori), JSON è più semplice e di fatto lo standard per le API del Web.
JSON in C
C non ha un tipo "oggetto" né una libreria standard per JSON. Per leggere e scrivere si usano librerie (cJSON, jansson, json-c) oppure, per oggetti piatti come quelli dell'esercizio, un estrattore minimo che cerca la chiave e legge il valore, con tre cautele: gestire le sequenze di escape nelle stringhe, controllare i limiti del buffer, e fare l'escape dei valori quando si genera l'uscita (virgolette, barre inverse e caratteri di controllo: un titolo Dire "ciao" deve diventare "Dire \"ciao\"", altrimenti il JSON generato è rotto). Un estrattore con strstr non è un vero parser: non valida la struttura e può confondere una chiave con una stringa uguale in un valore.
SOAP
SOAP (Simple Object Access Protocol, W3C; versione 1.2 del 2003) è un protocollo per scambiare messaggi XML fra applicazioni, indipendente dal trasporto ma quasi sempre usato su HTTP. Un messaggio è una busta (Envelope) con un Header facoltativo (informazioni di contorno: autenticazione, transazioni) e un Body (il contenuto: l'operazione richiesta o il risultato).
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<m:GetPrezzo xmlns:m="http://example.com/libreria">
<m:id>42</m:id>
</m:GetPrezzo>
</soap:Body>
</soap:Envelope>Con l'associazione a HTTP la busta viaggia nel corpo di una POST a un solo indirizzo (l'endpoint), qualunque sia l'operazione: l'operazione è dentro il Body, non nell'URI né nel metodo.
POST /servizi/libreria HTTP/1.1
Host: example.com
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 259
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope ...>...GetPrezzo id 42...</soap:Envelope>(In SOAP 1.1 il tipo è text/xml e un header SOAPAction indica l'azione.) La risposta è un'altra busta:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body>
<m:GetPrezzoRisposta xmlns:m="http://example.com/libreria">
<m:prezzo>29.90</m:prezzo>
</m:GetPrezzoRisposta>
</soap:Body>
</soap:Envelope>Gli errori sono segnalati da un elemento Fault nel Body (con Code, per esempio Sender se la colpa è del client e Receiver se del server, Reason, eventuale Detail); l'associazione HTTP usa lo stato 500 per i Fault del servizio.
Il contratto di un servizio SOAP è un file WSDL (Web Services Description Language, XML) che descrive messaggi, operazioni, tipi (con XSD), l'associazione al protocollo e l'indirizzo: un programma lo legge e genera automaticamente il codice per chiamare il servizio. Grazie al WSDL e ai numerosi standard WS-* (sicurezza, transazioni, affidabilità) SOAP si adatta ad ambienti aziendali formali, a costo di messaggi verbosi, di un parsing XML pesante e di una complessità elevata.
REST
REST (REpresentational State Transfer) è uno stile architetturale, descritto da Roy Fielding nella tesi di dottorato del 2000, che sfrutta direttamente come è fatto il Web: non un protocollo ma un insieme di vincoli. Un servizio che li rispetta si dice RESTful.
I vincoli
Risorse e rappresentazioni
Il concetto centrale è la risorsa: qualunque cosa abbia un nome (un libro, un utente, un ordine, un elenco), identificata da un URI. Il client non vede la risorsa ma una sua rappresentazione (JSON, XML, HTML) scelta con la negoziazione (Accept, Content-Type). Le operazioni sono i metodi HTTP, con il loro significato standard:
| operazione | metodo e URI | successo | corpo della richiesta | note |
|---|---|---|---|---|
| elencare | GET /libri |
200 |
no | sicuro, idempotente |
| leggere | GET /libri/42 |
200 |
no | 404 se non esiste |
| creare | POST /libri |
201 Created con Location: /libri/43 |
rappresentazione della nuova risorsa | il server sceglie l'identificatore; non idempotente |
| sostituire | PUT /libri/42 |
200 o 204 |
rappresentazione completa | idempotente |
| modificare in parte | PATCH /libri/42 |
200 |
solo i campi da cambiare | |
| eliminare | DELETE /libri/42 |
204 No Content |
no | idempotente (la seconda può dare 404) |
Gli URI indicano nomi, non verbi: /libri/42 e non /leggiLibro?id=42. Le collezioni si filtrano con la query: GET /libri?autore=Rossi&pagina=2. I codici di stato sono parte del contratto: 200 successo, 201 creato, 204 successo senza corpo, 400 richiesta malformata, 401 autenticazione richiesta, 403 vietato, 404 risorsa inesistente, 405 metodo non permesso (con Allow), 409 conflitto (la risorsa esiste già, versione incoerente), 415 tipo di contenuto non supportato, 500 errore del server.
Esempio. Una sessione completa con il servizio dell'esercizio (le risposte sono quelle reali del programma):
POST /tasks HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Content-Length: 29
{"title":"Comprare il latte"}HTTP/1.1 201 Created
Content-Type: application/json
Content-Length: 49
Location: /tasks/1
Connection: close
{"id":1,"title":"Comprare il latte","done":false}GET /tasks/9 HTTP/1.1
Host: 127.0.0.1:8080
HTTP/1.1 404 Not Found
Content-Type: application/json
Content-Length: 31
{"error":"compito inesistente"}PUT /tasks/1 HTTP/1.1
Content-Type: application/json
Content-Length: 29
{"title":"Latte","done":true}
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 36
{"id":1,"title":"Latte","done":true}DELETE /tasks/1 HTTP/1.1
HTTP/1.1 204 No ContentSi osservi: la risposta di creazione ha 201 e l'header Location, la risposta di DELETE non ha corpo né Content-Type, e gli errori hanno un corpo JSON con un messaggio.
Altri aspetti
- Idempotenza.
GET,PUTeDELETEripetute hanno lo stesso effetto di una sola: un client può ritentare in caso di errore di rete.POSTno: un ritentativo può creare due risorse (si usano chiavi di idempotenza se serve). - Senza stato non vuol dire senza autenticazione: ogni richiesta porta le proprie credenziali (token
Bearer,Authorization), così il server può essere replicato dietro un bilanciatore senza condividere sessioni. - HATEOAS. La risposta contiene i collegamenti alle azioni possibili (
"links": {"self": "/libri/42", "autori": "/libri/42/autori"}): il client li segue invece di costruire URI a mano. È la parte meno applicata dello stile. - Livelli di maturità (Richardson): 0 un solo endpoint e la
POSTcome tunnel (SOAP); 1 risorse con URI distinti; 2 uso corretto dei metodi HTTP e dei codici di stato; 3 collegamenti ipertestuali. La maggior parte delle API "REST" è al livello 2. - Versioni. Si indicano nell'URI (
/v1/libri) o con la negoziazione; un'API pubblicata non si cambia in modo incompatibile.
Un servizio REST in C
Un server REST è un server HTTP (Esercizio - Server HTTP iterativo con GET, HEAD e codici di errore (sul modello della prova pratica)) con tre elementi in più: l'instradamento (riconoscere /tasks e /tasks/ID e il metodo), la lettura del corpo con Content-Length nelle POST/PUT con controllo di Content-Type e dimensione (411, 413, 415), e la serializzazione JSON con l'escape. È quanto realizza Esercizio - Servizio REST con JSON (sul modello della prova pratica).
REST e SOAP a confronto
| SOAP | REST | |
|---|---|---|
| natura | protocollo con regole rigide | stile architetturale, flessibile |
| formato | solo XML (busta) | JSON, XML, HTML, testo... |
| trasporto | HTTP (ma anche altri) | HTTP |
| operazioni | nel Body del messaggio, tutte con POST allo stesso URI |
metodi HTTP su URI diversi |
| descrizione | WSDL | OpenAPI (facoltativa) |
| errori | elemento Fault (stato 500) |
codici di stato HTTP (404, 400...) |
| cache | difficile (sempre POST) |
naturale per GET |
| prestazioni | messaggi grandi, parsing pesante | leggero |
| uso tipico | integrazioni aziendali con standard WS-* |
API pubbliche, applicazioni web e mobili |
Sicurezza e buone pratiche
- Cifrare con HTTPS: i dati e le credenziali viaggiano in chiaro su HTTP (Crittografia asimmetrica, RSA e TLSCrittografia asimmetrica (a chiave pubblica): chiave di cifratura pubblica v_B, chiave di decifratura privata s_B, matematicamente legate; C = E_vB(P), P = D_sB(C); dà riservatezza. I certificati digitali, emessi da una Certificate Authority e firmati con la sua chiave privata, legano un'identità a una chiave pubblica. Firma digitale: hash del messaggio cifrato con la chiave privata; garantisce autenticità, integrità e non ripudio. RSA: si scelgono due primi grandi p e q, N = pq, phi(N) = (p-1)(q-1), e coprimo con phi(N), d = e^(-1) mod phi(N); chiave pubblica (N, e), segreta (N, d); cifratura c = m^e mod N, decifratura m = c^d mod N con m < N (teorema di Eulero); sicura finché la fattorizzazione è difficile (N di almeno 2048 bit); il padding casuale (PKCS#1 v1.5: 00 02 [casuale] 00 [m]) difende da malleabilità e determinismo. La firma RSA è s = m^d mod N, verificata con s^e mod N. TLS (su TCP) autentica gli estremi con il certificato, cifra i dati con una chiave di sessione simmetrica e garantisce l'integrità con i MAC; da TLS 1.0 a 1.3 la chiave si ricava con Diffie-Hellman e l'handshake si accorcia. DTLS è la versione per UDP.Crittografia asimmetrica, RSA e TLS →).
- Convalidare ogni dato in ingresso (tipi, lunghezze, intervalli): un JSON ben formato può contenere valori assurdi. Per XML disabilitare le entità esterne (attacco XXE).
- Limitare dimensione del corpo e profondità di annidamento (un documento nidificato oltre misura esaurisce lo stack di un parser ricorsivo).
- Non fidarsi del
Content-Typedichiarato senza controllarlo.
Esercizi collegati
- Esercizio - Servizio REST con JSON (sul modello della prova pratica): GET, POST, PUT, DELETE, OPTIONS, JSON,
201conLocation,204, errori JSON. - Esercizio - Server con autenticazione Basic, ETag e GET condizionale (sul modello della prova pratica): autenticazione e validatori, utili anche per un'API.
Errori tipici
- Dire che JSON è "più veloce" di XML per natura: è più compatto e più semplice da elaborare, non è un protocollo diverso.
- Non fare l'escape di virgolette, barre inverse e caratteri di controllo quando si genera JSON.
- Usare
GETper operazioni che modificano dati, oPOSTper tutto (livello 0). - Rispondere sempre
200, anche in caso di errore, con l'errore nel corpo: il codice di stato è parte del contratto. - Creare con
POSTsenza restituire201eLocation; eliminare e restituire un corpo con204. - Credere che REST sia un protocollo o che "senza stato" significhi "senza autenticazione".
- Mettere verbi negli URI (
/creaLibro); usarePUTper modifiche parziali (sostituisce l'intera risorsa). - Confondere XML ben formato e valido.
Domande d'esame
1. Confrontare SOAP e REST e dire per quali casi si sceglie l'uno o l'altro.
Traccia. SOAP: protocollo con messaggi XML (Envelope, Header, Body, Fault), operazioni nel corpo di una POST a un solo endpoint, descrizione WSDL, estensioni WS-*; verboso, adatto a integrazioni aziendali con requisiti formali. REST: stile con risorse e URI, metodi HTTP con il loro significato, codici di stato, rappresentazioni in JSON o XML, senza stato, cache; leggero, adatto ad API pubbliche e applicazioni web e mobili.
2. Una API REST per i libri: scrivere le richieste e gli stati per creare, leggere, sostituire ed eliminare il libro 43.
Traccia. POST /libri con il JSON del libro: 201 Created e Location: /libri/43. GET /libri/43: 200 con il JSON, oppure 404. PUT /libri/43 con la rappresentazione completa: 200 (o 204), idempotente. DELETE /libri/43: 204 No Content; una seconda DELETE dà 404, ma lo stato del server è lo stesso.
3. Scrivere in JSON e in XML un libro con titolo, due autori e prezzo; dire quali regole devono rispettare per essere validi.
Traccia. JSON: {"titolo":"...","autori":["A","B"],"prezzo":29.9}; chiavi stringhe fra virgolette doppie, array con [ ], numeri senza zeri iniziali, niente virgola finale. XML: elemento radice unico <libro>, tag annidati e chiusi (<autori><autore>A</autore>...</autori>), nomi che distinguono maiuscole e minuscole, attributi fra virgolette, caratteri speciali come &. Per XML "valido" significa inoltre conforme a uno schema (XSD).
Versione ripasso
- Web service. Servizio software usato da altri programmi via rete, con messaggi in formato standard su HTTP. Servono: formato dei dati (XML, JSON), modo di esprimere le operazioni (SOAP o REST), trasporto (HTTP).
- XML. Albero di elementi con tag; ben formato: una sola radice, tag annidati e chiusi, nomi case-sensitive, attributi fra virgolette, entità
< > & " ',<![CDATA[ ]]>. Namespace (xmlns:m="..."); valido = conforme a DTD/XSD. DOM o SAX;libxml2,expat. Tipoapplication/xml. - JSON (RFC 8259). Valori: oggetto
{}, array[], stringa (virgolette doppie, escape\" \\ \/ \b \f \n \r \t \uXXXX), numero,true/false,null. Niente commenti né virgole finali, UTF-8,application/json. Esempio:{"id":42,"titolo":"Reti di Calcolatori","autori":["Rossi","Bianchi"],"prezzo":29.9,"disponibile":true}= 102 byte; in XML 174.
| XML | JSON | |
|---|---|---|
| dimensione | maggiore | minore |
| tipi | solo testo (XSD) | numeri, booleani, null, array |
| struttura | elementi, attributi, namespace | oggetti e array |
| uso | SOAP, documenti | API REST |
- JSON in C. Librerie (
cJSON,jansson) o estrattore minimo per oggetti piatti: gestire gli escape, controllare i buffer, fare l'escape in uscita (Dire "ciao"->"Dire \"ciao\"");strstrnon è un parser. - SOAP. Messaggi XML:
Envelope>Header(facoltativo) +Body; errori nell'elementoFault(CodeSender/Receiver,Reason, stato HTTP500). Su HTTP:POSTa un solo endpoint,Content-Type: application/soap+xml(SOAP 1.1:text/xml+SOAPAction); l'operazione sta nel Body. WSDL descrive il servizio; standardWS-*. Verboso, parsing pesante, adatto a integrazioni aziendali. - REST (Fielding, 2000). Stile architetturale. Vincoli: client-server, senza stato (ogni richiesta autosufficiente), cache, interfaccia uniforme (URI, rappresentazioni, messaggi autodescrittivi, HATEOAS), sistema a livelli, codice su richiesta (facoltativo).
| operazione | richiesta | successo |
|---|---|---|
| elencare / leggere | GET /libri, GET /libri/42 |
200 (o 404) |
| creare | POST /libri |
201 Created + Location |
| sostituire | PUT /libri/42 (rappresentazione completa) |
200/204 |
| modificare in parte | PATCH /libri/42 |
200 |
| eliminare | DELETE /libri/42 |
204 No Content |
- Regole. URI = nomi (
/libri/42), filtri con query (?autore=Rossi). Codici:400,401,403,404,405+Allow,409,415,500. Idempotenti:GET,PUT,DELETE;POSTno. Maturità di Richardson: 0POSTunico, 1 risorse, 2 metodi e codici, 3 HATEOAS. Versioni nell'URI (/v1/). - REST vs SOAP. REST: stile, JSON/XML, metodi HTTP su URI diversi, errori con codici di stato, cache naturale, leggero. SOAP: protocollo, solo XML, tutto in
POST, erroriFault, cache difficile, pesante. - Sicurezza. HTTPS, convalidare gli ingressi, limitare dimensione e annidamento, disabilitare le entità esterne XML (XXE), non fidarsi di
Content-Type. - Esempi da saper scrivere.
{"id":42,"titolo":"Reti di Calcolatori","autori":["Rossi","Bianchi"],"prezzo":29.9,"disponibile":true}<libro id="42"><titolo>Reti di Calcolatori</titolo><autori><autore>Rossi</autore><autore>Bianchi</autore></autori>
<prezzo>29.9</prezzo><disponibile>true</disponibile></libro><soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Body><m:GetPrezzo xmlns:m="http://example.com/libreria"><m:id>42</m:id></m:GetPrezzo></soap:Body>
</soap:Envelope>Sessione REST.
POST /tasks+{"title":"Comprare il latte"}->201 Created,Location: /tasks/1, corpo{"id":1,"title":"Comprare il latte","done":false};GET /tasks/9->404+{"error":"compito inesistente"};PUT /tasks/1+{"title":"Latte","done":true}->200;DELETE /tasks/1->204senza corpo.Errori tipici: JSON senza escape;
GETche modifica; sempre200;POSTsenza201/Location;204con corpo; verbi negli URI;PUTper modifiche parziali; REST creduto un protocollo; XML ben formato confuso con valido.