Nei paragrafi precedenti abbiamo visto come consumare o esporre Web API REST scrivendo manualmente il codice di gestione delle chiamate. Ora vedremo come automatizzare questo processo tramite lo standard OData.
OData è uno standard che definisce un insieme di best practice per la progettazione ed il consumo di Web API REST. Delineando header, status, URL e metodi HTTP in base all’operazione che si vuole completare, lo standard permette di focalizzarsi sulle funzionalità degli applicativi piuttosto che sulla loro interazione con la Web API. Definendo un linguaggio comune, le Web API OData possono essere consumate da qualsiasi client che adotti lo standard.
Instant Developer Cloud implementa lo standard OData in maniera completa, infatti:
- Contiene un framework di generazione automatica di Web API OData a partire dai documenti contenuti nell’applicazione.
- Permette di importare una Web API OData esistente generando automaticamente le classi di documento corrispondenti per consumarla.
Esposizione di documenti via Web API OData #
Esporre un documento del progetto via OData è molto semplice: è sufficiente impostare la proprietà Abilità Web API a design time nella classe documento. I possibili valori sono:
- Metodi: sarà possibile solo chiamare i metodi del documento specificatamente abilitati per l’utilizzo tramite Web API.
- Metodi+Lettura: sarà possibile effettuare delle query di lettura dati estraendo documenti e collection, oltre che chiamare i metodi specificatamente abilitati per l’utilizzo tramite Web API.
- Metodi+Lettura+Scrittura: sarà possibile effettuare delle query di lettura dati, inserire, modificare e cancellare i documenti, oltre che chiamare i metodi specificatamente abilitati per l’utilizzo tramite Web API.
Autenticazione e autorizzazioni #
Abilitando le Web API per una classe di documento, esse saranno subito attive al momento dell’installazione dell’applicazione su un server. È quindi necessario aggiungere un livello di autenticazione per controllare che il chiamante abbia il permesso di accedere ai documenti esposti. A tal fine è possibile implementare l’evento onCommand, che viene notificato prima di procedere con l’elaborazione della richiesta, come mostrato nell’esempio seguente:
App.Session.prototype.onCommand = function (request)
{
if (app.isWebApiRequest()) {
let token = "Basic " + App.Utils.toBase64(app, "alladin:opensesame");
if (request.headers.authorization !== token) {
app.sendResponse(401, { error: {
message: "Authorization token is not valid”}
}, {contentType: ”application/json”});
return;
}
}
};
Per disabilitare la gestione automatica della richiesta Web API è sufficiente chiamare il metodo app.sendResponse. Nei casi reali si consiglia di verificare il token di autenticazione in base ad una lista di token validi, che possono essere memorizzati anche su una tabella del database.
Ai token di autorizzazione possono essere associati dei parametri da utilizzare per condizionare le chiamate di lettura o scrittura dei dati. Se, ad esempio, volessimo limitare la possibilità di caricare gli ordini dal database solo a quelli dell’utente associato al token, potremmo operare come segue:
- Nell’evento onCommand, viene impostata una proprietà di sessione (app.employeeID) che identifica l’ID dell’utente per cui limitare l’estrazione degli ordini.
- Nell’evento beforeLoad del documento Order, nel caso di chiamata Web API viene utilizzata tale proprietà per impostare un filtro, come mostrato nell’esempio seguente:
App.BE.Order.prototype.beforeLoad = function (collection, options)
{
if (app.isWebApiRequest())
this.employeeID = app.employeeID;
};
Nell’evento beforeLoad, infatti, le proprietà del documento this rappresentano i filtri QBE che verranno applicati nella fase di caricamento.
Si noti che, in base al tipo di applicazione, può essere più appropriato definire una classe documento apposita che contiene tutti i metodi di accesso e di modifica dei dati esposti via Web API, invece che esporre i documenti applicativi veri e propri.
In questo modo è più facile avere un quadro completo dei possibili accessi dall’esterno, anche se si dovrà implementare un metodo per ogni operazione consentita.
Il compromesso fra velocità di implementazione e controllo degli accessi può essere liberamente gestito miscelando opportunamente le tecniche illustrate in questo paragrafo: esposizione diretta dei documenti applicativi o esposizione indiretta tramite un documento di interfaccia.
Si ricorda che, in ogni caso, è necessario gestire un livello di autenticazione generale tramite l’evento onCommand.
Testare la Web API OData esposta #
Nel caso di Web API OData non è possibile eseguire il test tramite l’anteprima nell’IDE, ma è invece necessario installare l’applicazione in un server di test o di produzione.
In questo modo sarà possibile importare la Web API in un client OData specificando l’endpoint, cioè la URL dell’installazione o la URL dei metadati, che è uguale all’endpoint seguito da $metadata. Ad esempio:
https://examples.instantdevelopercloud.com/datamapdesignpatterns/$metadata
Le eccezioni di funzionamento e i warning potranno essere seguiti nel log strutturato dell’installazione, una volta attivato e configurato specificando app.sessioneName nell’evento app.onCommand.
Personalizzare il framework di gestione delle Web API OData #
Abbiamo visto che la generazione delle Web API OData richiede solo l’attivazione del servizio per i documenti che la richiedono. Tutta la gestione delle richieste e delle relative risposte è quindi a carico del framework di Instant Developer Cloud.
Abbiamo visto anche che è possibile interagire con la richiesta in arrivo tramite l’evento app.onCommand, prima che il framework la gestisca e questo ci dà la possibilità, ad esempio, di gestire l’autenticazione.
Può essere necessario personalizzare più profondamente il funzionamento del framework, ad esempio loggando ogni richiesta di un certo tipo, oppure intercettando la risposta prima che venga inviata al chiamante. A tal fine è possibile inserire nel proprio progetto una classe di codice che estende App.WebApiService.
Per questa classe sono disponibili tre eventi:
- onNewRequest: notificato prima di processare la richiesta. È analogo ad app.onCommand e permette di gestire la richiesta prima di passarla al framework OData.
- onHandleRequest: notificato prima di gestire una richiesta OData già validata. All’evento vengono passati il tipo di azione e i parametri. È quindi possibile modificare i parametri dell’azione, gestire l’azione in autonomia o anche più semplicemente loggare la richiesta.
- onRequestResult: notificato prima di inviare la risposta al chiamante. Può essere usato per analizzare la risposta, inserire header o gestire l’invio in autonomia.
Vediamo un esempio dei primi due eventi precedenti. Nel primo esempio utilizziamo onNewRequest per controllare i parametri di autenticazione della richiesta.
App.NwindLibreria.MyWebApiService.prototype.onNewRequest =
function (request)
{
if (!request.headers.Authorization) {
this.sendError(401, "non autorizzato");
return;
}
};
Il metodo WebApiService.sendError viene usato per restituire un errore di gestione della richiesta, mentre WebApiService.sendResponse serve per inviare la risposta al chiamante. In entrambi i casi si disabilita la gestione automatica da parte del framework, in quanto, una volta fornita una risposta o segnalato un errore, la sessione REST viene terminata.
Nel secondo esempio utilizziamo onHandleRequest per attivare la paginazione dei risultati anche se non è stata richiesta dal chiamante.
App.NwindLibreria.MyWebApiService.prototype.onHandleRequest =
function (request, action)
{
if (action.type === App.WebApiService.actionTypes.loadCollection) {
action.options.pagingMode = action.options.pagingMode ||
App.DataMap.dataPagingModes.offset;
action.options.pageSize = action.options.pageSize || 30;
action.options.preCount = true;
}
};
Vediamo infine come fare in modo che il framework OData utilizzi la nostra classe per notificare gli eventi. Ciò avviene nell’evento app.onCommand, impostando la proprietà webApiService della classe App.Document, tramite la riga di codice seguente:
App.Session.prototype.onCommand = function (request)
{
App.Document.webApiService = App.NwindLibreria.MyWebApiService;
...
}
È possibile impostare direttamente la classe oppure crearne un’istanza e usare questa. Il secondo caso è utile se l’istanza deve essere inizializzata in modo personalizzato.
Comandi OData non implementati #
Il protocollo OData definisce una serie estesa di funzionalità, adatte a gestire un insieme di casistiche complesse. Il framework OData di Instant Developer Cloud implementa una parte delle specifiche, restituendo l’errore 501 – request type not implemented in caso diverso.
In generale, la parte implementata riflette le funzionalità di manipolazione dei documenti descritte nel manuale Document Orientation e le funzionalità non implementate possono essere ottenute tramite una combinazione di quelle disponibili.
Ad esempio, OData permette di caricare un documento e una specifica collection figlia con una sola chiamata. Se volessimo caricare l’entità Order con ID pari a 10540 e contemporaneamente avere il contenuto della collection figlia OrderDetails, OData prevede una chiamata con questo formato:
https://<endpoint>/Order(10540)?$expand=OrderDetails
Questo comando, richiedendo l’uso di una funzionalità non implementata da Instant Developer Cloud, restituisce l’errore 501 – request type not implemented.
Esso può essere sostituito in due modi. Il primo consiste nel caricare la testata specificando il parametro childLevel per caricare anche tutte le collection di primo livello:
https://<endpoint>/Order(10540)?childLevel=1
In alternativa è possibile effettuare due chiamate, una per caricare la testata:
https://<endpoint>/Order(10540)
e l’altra per caricare i dettagli dell’ordine indicando come filtro il campo OrderID:
https://<endpoint>/OrderDetail?$count=true&$filter=OrderID%20eq%2010540
Questo tipo di problematiche può accadere se si accede alla Web API esposta direttamente tramite comandi https; se invece essa viene importata in un altro progetto questo non accadrà in quanto l’interfaccia dei documenti utilizzerà solo i metodi implementati.
Importazione e configurazione di Web API OData #
In questo paragrafo vediamo come utilizzare una Web API esistente in formato OData in un progetto.
Anche in questo caso l’operazione è molto semplice. Basta inserire la URL dell’endpoint (che deve usare il protocollo https) nella funzione di importazione API che si trova nella pagina principale dell’IDE.
La URL dell’endpoint serve alla funzione di importazione per recuperare i metadati della Web API OData; dopo averla inserita verrà mostrato un elenco di entità che è possibile importare.
La procedura di importazione termina con la creazione nel progetto delle classi di documento relative alle entità importate. A questo punto è possibile utilizzarle come se fossero normali documenti: è quindi possibile aggiungere proprietà unbound e metodi, oltre a gestire eventi.
Ma poiché i documenti che gestiscono le entità OData non sono basati direttamente su tabelle del database, le funzionalità specifiche dell’accesso al database non sono disponibili. Ad esempio, non è possibile creare una query di caricamento del documento, oppure gestire proprietà derivate. I cicli di caricamento e salvataggio, tuttavia, notificano gli stessi eventi, a parte l’evento onSave che non serve in quanto il salvataggio vero e proprio avviene all’interno del server che espone la Web API.
Autenticazione e personalizzazione degli endpoint #
In alcuni casi i documenti importati non saranno subito utilizzabili perché la Web API può richiedere un header di autenticazione, oppure perché l’endpoint di produzione può essere diverso da quello da cui è stata importata l’entità.
Come nel caso precedente, anche il framework che gestisce le chiamate a Web API OData può essere personalizzato, inserendo una apposita classe nel proprio progetto che estende WebApiOData.
A questo punto è necessario comunicare ai documenti che gestiscono le entità importate la classe da utilizzare per la gestione delle chiamate. A tal fine si consiglia di inserire in questa classe un metodo statico init, in cui inserire l’impostazione delle proprietà webApiEndpoint dei vari documenti, come mostrato nell’esempio di codice seguente:
App.NwindLibreria.MyODataAPI.init = function (app)
{
App.NwindLibreria.Products.webApiEndpoint = {
type : "NwindLibreria.MyODataAPI",
url : "https://prod3-pro-gamma.instantdevelopercloud.com/academy"
};
...
};
La proprietà webApiEndpoint di una classe documento è un oggetto JavaScript che deve contenere le proprietà:
- type: il nome della classe che gestisce la Web API importata.
- url: endpoint da utilizzare per questa entità.
È necessario specificare questa proprietà per ogni classe documento che gestisce un’entità importata.
A questo punto, nell’evento app.onStart è necessario richiamare il metodo statico init per attivare le impostazioni.
App.Session.prototype.onStart = function (request)
{
App.NwindLibreria.MyODataAPI.init(app);
...
}
Per gestire l’autenticazione è ora possibile inserire l’evento beforeHttpRequest nella classe di gestione della Web API per inserire gli opportuni header, come mostrato nel seguente esempio:
App.NwindLibreria.MyODataAPI.prototype.beforeHttpRequest =
function (request, action)
{
request.options.headers.Authorization = "Basic " +
App.Utils.toBase64(app, "alladin:opensesame");
};