Skip to content

Impariamo Xojo: Utilizzare API REST

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

In questo post faccio riferimento al nuovo framework di Xojo perché in questo modo le informazioni possono essere utilizzate in tutti progetti compresi quelli per iOS.

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.