In questa lezione ci dedichiamo proprio all'implementazione del Web Service che abbiamo progettato nelle scorse puntate. Le considerazioni che abbiamo fatto fin qui sono indipendenti dallo specifico ambiente di programmazione che andremo ad utilizzare. L'unico aspetto che è stato dettato dall'implementazione è stata la scelta del modello di URI che adotteremo per individuare le risorse.
Nel realizzare il nostro progetto abbiamo scelto di utilizzare ASP.NET, sfruttando gli oggetti Request e Response, senza alcun tipo di supporto da parte di framework specifici che possano nascondere la gestione diretta dei metodi HTTP, come WCF o l'utilizzo di un HTTP handler specifico.
Lo scopo è rendere chiaro cosa succede dietro le quinte in presenza di una richiesta HTTP sulle nostre risorse e per questo intendiamo gestire queste richieste cercando di non rimanere troppo legati ad un framework particolare. D'altronde, il modello REST ci fornisce esclusivamente dei principi lasciandoci liberi da vincoli sulla tecnologia da utilizzare.
Lo schema URI che utilizziamo è tipo http://www.mionegozio.com/ordini/?id=123: il Web server ha il compito di individuare la pagina predefinita nella cartella ordini, tipicamente default.aspx, l'identificazione della risorsa specifica la facciamo tramite il parametro id. Non specificare la pagina predefinita ci permette di mantenere lo schema di URI scelto anche se cambiamo tecnologia di implementazione, utilizzando ad esempio PHP o JSP.
Premettiamo inoltre che per poter gestire i metodi PUT e DELETE con ASP.NET è necessario abilitarli intervenendo sul Web server o sul file di configurazione Web.config. Per maggiori informazioni su questi aspetti, Enabling The PUT Verb with Handlers and IIS 7.0.
La pagina che gestisce una risorsa eseguirà del codice analogo al seguente in corrispondenza dell'evento
Load per individuare il tipo di azione richiesta dal client.
switch (Request.HttpMethod.ToUpper()) {
case "GET": //Gestione del GET
break;
case "POST": //Gestione del POST
break;
case "PUT": //Gestione del PUT
break;
case "DELETE": //Gestione del DELETE
break;
default:
Response.StatusCode = 405; //Method not allowed
break;
}
In corrispondenza di ciascun metodo HTTP avremo una procedura che si occupa di gestire opportunamente la risorsa.
Ad esempio, nel caso della gestione di un ordine avremo:
switch (Request.HttpMethod.ToUpper()) {
case "GET":
RecuperaOrdine(Request.QueryString["id"]);
break;
case "POST":
CreaOrdine();
break;
case "PUT":
ModificaOrdine(Request.QueryString["id"]);
break;
case "DELETE":
EliminaOrdine(Request.QueryString["id"]);
break;
default:
Response.StatusCode = 405; //Method not allowed
break;
}
Richiesta di una risorsa
Analizziamo come poter gestire la richiesta di una risorsa da parte di un client, concentrandoci sul caso di richiesta di un ordine.
Si tratta in pratica di implementare la procedure RecuperaOrdine():
private void RecuperaOrdine(String id) {
Ordine myOrdine;
String myOrdineRepresentation;
myOrdine = getOrdineFromDB(id);
if (myOrdine == null) {
Response.StatusCode = 404; //Not Found
} else {
myOrdineRepresentation = parseOrdine(myOrdine);
//Invio della risposta al client
Response.ClearContent();
Response.ContentType = "application/xml";
Response.Write(myOrdineRepresentation);
Response.End();
}
}
La procedura si occupa di recuperare l'ordine dal database tramite una chiamata a getOrdineFromDB(). Se non esiste
nessun ordine con l'identificatore fornito viene restituito un codice di stato HTTP 404 che corrisponde alla segnalazione di
risorsa non trovata.
Se invece l'ordine è stato trovato con successo, viene generata una sua rappresentazione tramite la procedura
parseOrdine() e viene inviata al client. Da notare la segnalazione al client del tipo di rappresentazione inviata tramite
l'intestazione Content-Type. Questa segnalazione è molto importante perchè prepara il client alla corretta
interpretazione della risorsa inviata.
Creazione di una nuova risorsa
La creazione di un nuovo ordine in corrispondenza del metodo POST corrisponde all'esecuzione della procedura CreaOrdine(). La procedura si occuperà di acquisire la rappresentazione dell'ordine da creare inviata dal client, generare un ordine secondo il proprio modello ad oggetti e restituire la rappresentazione dell'ordine creato al client, come mostrato dal seguente codice:
private void CreaOrdine() {
Ordine myOrdine;
String myOrdineRepresentation;
Int32 contentLength = Convert.ToInt32(Request.InputStream.Length);
byte[] content = new byte[contentLength];
//Acquisizione del corpo della richiesta HTTP
Request.InputStream.Read(content, 0, contentLength);
myOrdineRepresentation = Request.ContentEncoding.GetString(content);
//Generazione dell'ordine secondo il modello ad oggetti interno
myOrdine = unParseOrdine(myOrdineRepresentation);
try {
myOrdine = CreateOrdineInDB(myOrdine);
} catch (Exception e) {
Response.StatusCode = 400; //Bad Request
Response.End();
}
myOrdineRepresentation = parseOrdine(myOrdine);
//Invio della risposta al client
Response.ClearContent();
Response.ContentType = "application/xml";
Response.Write(myOrdineRepresentation);
Response.End();
}
La rappresentazione del nuovo ordine restituita al client avrà alcune informazioni aggiuntive rispetto a quella iniziale,
come ad esempio l'identificatore dell'ordine e lo stato.
Nel caso in cui la creazione dell'ordine secondo il modello ad oggetti interno al Web Service generi un'eccezione, nella procedura sopra riportata viene restituito un generico stato HTTP 400. In una situazione reale andrebbe individuato opportunamente il motivo della mancata creazione e fornito un codice ed una descrizione appropriati. Ad esempio, il fallimento del salvataggio potrebbe essere legato ad una condizione dipendente dai dati forniti dal client ed in questo caso andrebbe segnalato un codice di stato della classe 4xx.
Se invece si è verificato un problema interno durante il salvataggio sul database, ad esempio un errore di timeout nella
comunicazione tra il Web service ed il database, andrebbe comunicato un codice di stato 500, Internal Server Error.
Modifica di una risorsa
La modifica di un ordine viene implementata dalla procedura ModificaOrdine(). La procedura ha il compito di caricare l'ordine individuato dal parametro ID dal database, modificarla con i valori forniti dalla versione inviata dal client e salvarla nuovamente del database.
private void ModificaOrdine(String id) {
Ordine myOrdine;
Ordine clientOrdine;
String clientOrdineRepresentation;
Int32 contentLength = Convert.ToInt32(Request.InputStream.Length);
byte[] content = new byte[contentLength];
//Acquisizione del corpo della richiesta HTTP
Request.InputStream.Read(content, 0, contentLength);
clientOrdineRepresentation = Request.ContentEncoding.GetString(content);
myOrdine = getOrdineFromDB(id);
if (myOrdine == null) {
Response.StatusCode = 404; //Not Found
} else {
clientOrdine = unParseOrdine(clientOrdineRepresentation);
//Aggiornamento dell'ordine
try {
updateOrdineInDB(myOrdine, clientOrdine);
} catch (Exception e) {
Response.StatusCode = 400; //Bad Request
Response.End();
}
//Invio della risposta al client
Response.ClearContent();
Response.StatusCode = 200; //OK
Response.End();
}
}
Come per il caso di creazione di un nuovo ordine, anche qui il codice di stato da restituire in caso di eccezione nel salvataggio andrebbe dettagliato opportunamente.
La risposta fornita al client in caso di aggiornamento con esito positivo è rappresentata dal codice di stato 200.
Eliminazione di una risorsa
Nell'eliminazione di un ordine, la procedura EliminaOrdine() individua la risorsa da eliminare ed esegue l'operazione richiesta.
private void EliminaOrdine(String id) {
Ordine myOrdine;
myOrdine = getOrdineFromDB(id);
if (myOrdine == null) {
Response.StatusCode = 404; //Not Found
} else {
//Eliminazione dell'ordine
try {
deleteOrdineFromDB(myOrdine);
} catch (Exception e) {
Response.StatusCode = 400; //Bad Request
Response.End();
}
//Invio della risposta al client
Response.ClearContent();
Response.StatusCode = 200; //OK
Response.End();
}
}
Anche in questo caso in caso di successo il codice di stato HTTP restituito è 200.
In caso di problemi durante l'eliminazione valgono le stesse considerazioni della creazione e modifica di una risorsa per il codice di stato da restituire.