Molti servizi sul web espongono delle API di tipo REST che permettono di interagire con il servizio. Vediamo come possiamo utilizzarle in Xojo.
API REST
Per API REST si intendono delle interfacce verso delle applicazioni (API sta per Application Programming Interface) che ci permettono di interagire con l’applicazione remota in modalità REST (REpresentational State Transfer, ovvero trasferimento di uno stato che rappresenta una informazione).
In genere sono indirizzi web con una certa struttura nel percorso (a seconda dei comandi da eseguire) e che possono rispondere, o meno, ai diversi modi di comunicazione HTTP (GET, POST, DELETE, PUT e altri) con dei dati o anche semplicemente con lo stato HTTP della risposta (ad indicare la tipologia di risposta); dipende da come è stata creata l’API: quindi per interagire in modo corretto occorre avere la relativa documentazione.
Ad esempio la combinazione degli indirizzi e delle azioni possono permettere di interagire con una base di dati (se le API lo prevedono); una ipotetica API REST potrebbe prevedere i seguenti comandi:
//Ottenere una lista dei post GET https://api.esempio.finto/v1/posts //Se l'HTTPStatus è 200 la risposta conterrà: [ { "id": 1, "titolo": "Questo è il primo post", "body": "Il primo post\ncontiene questo testo" }, … { "id": 225, "title": "Questo è l'ultimo post", "body": "Tutti i post alla fine terminano." } ] //Ottenere un post particolare GET https://api.esempio.finto/v1/posts/1 //Se l'HTTPStatus è 200 la risposta conterrà: { "id": 1, "titolo": "Questo è il primo post", "body": "Il primo post\ncontiene questo testo" } //Ma HTTPStatus può assumere il valore 404 ad indicare //il tentativo di accedere ad un record non esistente //Modificare un post PUT https://api.esempio.finto/v1/posts/1 //con contenuto {"titolo":"Nuovo titolo", "body":"Nuovo contenuto"} //Set HTTPStatus è 200 la risposta conterrà: { "id": 1, "titolo": "Nuovo titolo", "body": "Nuovo contenuto" } //Ma HTTPStatus può assumere il valore 404 ad indicare //il tentativo di modifica di un record non esistente |
… e così via per inserire, cancellare o altre operazioni.
In genere nel percorso viene indicata la versione della API (v1 nell’esempio riportato) in modo da gestire anche la coesistenza di diverse versioni.
Chiaramente, in genere deve esistere una qualche forma di autenticazione: quindi tra i parametri dell’interrogazione possono essere necessari anche dati relativi all’utente che sta operando sotto forma di chiavi, token o username etc.
Come detto in precedenza le opzioni disponibili e i comandi dipendono da chi ha sviluppato le API, ad esempio possono non essere previsti i diversi modi ma solo il POST, i parametri si passano solo via JSON o addirittura gli stessi comandi sono passati nel JSON o il JSON deve essere in qualche modo crittografato.
Interagire con le API REST con Xojo
Nel nuovo framework per utilizzare i servizi HTTP utilizziamo la classe Xojo.Net.HTTPSocket
Una implementazione di base può prevedere l’uso di una istanza della classe in una finestra e un pulsante che avvii il comando.
Riprendendo gli esempi visti precedentemente possiamo scrivere (detta http l’istanza della classe Xojo.Net.HTTPSocket) nell’evento action di un pulsante per ottenere un post particolare:
//Come esempio indichiamo l'ID del post come fisso, //ma lo potremmo prendere da un campo o da altre informazioni dim id as integer=1 http.send("GET", "https://api.esempio.finto/v1/posts/"+id.toText) |
In questo oggetto tutte le azioni sono asincrone, per cui una volta inviata la richiesta il programma non viene interrotto, ma ad un certo punto l’evento PageReceived della classe viene richiamato e dalle informazioni ricevute posso creare le mie azioni.
Creare tutto nell’evento può risultare complesso. La soluzione più conveniente è creare una sottoclasse di Xojo.Net.HTTPSocket e avere direttamente i comandi previsti dividere le risposte a seconda delle azioni. Poi o utilizzare delle callback (tramite delegate) o degli eventi, a seconda delle necessità.
Come detto nel post relativo al JSON è sempre meglio controllare il contenuto della risposta e in ogni caso più breve è l’azione presente nella PageReceived più veloce è la possibilità di riutilizzare il socket o di poter aprire una nuova connessione.
Un esempio potrebbe essere la seguente classe:
Class RestApiEsempio Inherits Xojo.Net.HTTPSocket //Definiamo l'indirizzo base come costante della classe Const baseURL as Text="https://api.esempio.finto/v1/" //Definiamo una enumerazione per distinguere i diversi comandi //qui solo alcuni Enum Azioni as Integer elenco record aggiorna End Enum //Definiamo una proprietà che rappresenta l'azione corrente Property azione as Azioni //Definiamo gli eventi, possiamo definirne uno per azione //o solo due comuni riportando l'azione a cui si riferiscono EventDefinition successo(azione as RestApiEsempio.Azioni, risultato as Auto) EventDefinition errore(azione as RestApiEsempio.Azioni, motivo as Auto) //Implementiamo l'evento PageReceived che deve interpretare la risposta Sub PageReceived(URL as Text, HTTPStatus as Integer, Content as xojo.Core.MemoryBlock) #Pragma Unused URL //Dichiariamo un booleano per segnalare se la risposta è stata gestita Dim ok As Boolean //Valutiamo i vari casi delle azioni. Usiamo un CallLater 0 per uscire immediatamente // dall'evento e non bloccarlo nelle azioni successive. Select case azione Case Azioni.elenco If HTTPStatus=200 Then ok=True Xojo.Core.Timer.CallLater 0, WeakAddressOf interpretaJSON, content End If Case Azioni.record If HTTPStatus=200 Then ok=True Xojo.Core.Timer.CallLater 0, WeakAddressOf interpretaJSON, content Elseif HTTPStatus=404 Then ok=True RaiseEvent errore(azione, "Record non esistente") End If Case Azioni.aggiorna If HTTPStatus=200 Then ok=True Xojo.Core.Timer.CallLater 0, WeakAddressOf interpretaJSON, content Elseif HTTPStatus=404 Then ok=True RaiseEvent errore(azione, "Record non esistente") End If //Gestire gli altri comandi... End Select if not ok then RaiseEvent errore(azione, "Status non previsto:"+HTTPStatus.toText) End Sub //Definiamo il metodo interpretaJSON che legge il risultato // e verifica che sia leggibile Private Sub interpretaJSON(content as Auto) Dim m as Xojo.Core.MemoryBlock Dim risultato as Auto //Proviamo la conversione, se fallisce agiremo di conseguenza // questo è solo un esempio, è consigliabile prevedere una analisi // dell'errore più corretta al fine di migliorare la gestione Try m=content Dim t As Text=Xojo.Core.TextEncoding.UTF8.ConvertDataToText(m) a=Xojo.Data.ParseJSON(t) Catch m=Nil End Try //Valutiamo la conversione If m=Nil Then RaiseEvent errore(azione, "Errore nell'interpretazione della risposta") Else RaiseEvent successo(azione, a) End If End Sub //Inseriamo i metodi per usare la classe come da API //Elenco di tutti i record Sub elenco() azione=azioni.elenco Send "GET", baseUrl+"posts" End Sub //Ottenere un record specifico Sub record(recordId as integer) azione=azioni.record Send "GET", baseUrl+"posts/"+ recordId.ToText End Sub //Aggiornare un record specifico Sub update(recordId as integer, recordData as Xojo.Core.Dictionary) Dim t As Text=Xojo.Data.GenerateJSON(recordData) Dim m As Xojo.Core.MemoryBlock=Xojo.Core.TextEncoding.UTF8.ConvertTextToData(t) azione=Azioni.aggiorna SetRequestContent m, "application/json" send "PUT", baseURL+"posts/"+recordId.ToText End Sub //Aggiungere le altre azioni... End Class |
A questo punto per usare la classe, possiamo trascinarla in una finestra, richiamarne i metodi e gestirne gli eventi. Possiamo crearne delle sottoclassi se magari vogliamo utilizzare un pool di Socket che si autoeliminano e/o delle funzionalità di callback (tramite delegate) invece che gli eventi. Dipende dal progetto, dalle scelte implementative e da quelle che le API mi permettono.
La classe vista è per una API REST classica, esistono altre API che richiedono alcune strutture dati particolari (espresse come JSON) e sono accessibili da un unico indirizzo (analizzano il contenuto del JSON per interpretare la risposta da dare) per cui la classe andrà costruita per rispettare questo vincolo. Probabilmente l’API richiederà l’invio del JSON in modalità POST per farlo basterà semplicemente qualcosa come:
Sub invia(datiComeTestoJSON as Text) dim m as Xojo.Core.MemoryBlock=Xojo.Core.TextEncoding.UTF8.ConertTextToData(datiComeTestoJSON) setRequestContent m, "application/json" send "PUT", urlPrevistoDallAPI End Sub |
In genere il MimeType è applcation/json ma occorre sempre verificare il MimeType richiesto dall’API.