L'API HTTP è una ottima soluzione per interoperabilità e separazione. Può essere usata qualora non esista un driver specifico per il linguaggio o framework utilizzato, oppure quando si vuole integrare Neo4j in un'applicazione che necessita di poche operazioni (anche complesse) senza aggiungere una dipendenza al progetto. Essendo un'interfaccia HTTP, può essere facilmente mockata per effettuare test automatici d'integrazione. Oppure possono essere creati script shell utilizzando il comando curl che viene distribuito nella maggior parte delle distro Linux.
Un altro vantaggio è che le chiamate possono essere facilmente testate senza programmare, utilizzando un client REST HTTP: ce ne sono moltissimi, uno molto semplice e comodo da usare è Advanced REST Client, un plugin di Chrome. In questa lezione vedremo come invocare la HTTP API con questo client, ma i concetti sono validi in generale. Quindi, come prima cosa apriamo il client e inseriamo la URL http://localhost:7474/db/data/transaction/commit.
Questo endpoint permette di eseguire una o più query Cypher su Neo4j in un'unica transazione: una chiamata, una transazione. Come vedremo più avanti, il protocollo permette di effettuare più chiamate HTTP in una transazione. Dopo aver inserito l'URL, impostiamo il metodo (il cosiddetto verb http) a POST e impostiamo come content/type la stringa application/json.
Per accedere ai servizi, è necessaria l'autenticazione, a meno che non sia stata esplicitamente disattivata nel file di configurazione di Neo4j. Le credenziali possono essere specificate con un header HTTP. Advanced REST client supporta l'autenticazione Basic, quindi chiederà automaticamente username e password.
Se usiamo un altro client, supponendo di utilizzare username html_it e password password, allora bisogna convertire in base 64 la sequenza html_it:password (possiamo farlo anche con un tool online di codifica/decodifica in base64).
Quindi, nella sezione Raw headers, inseriamo il testo seguente, che contiene la sequenza codificata.
authorization: Basic aHRtbF9pdDpwYXNzd29yZA==
Come si vede, con l'autenticazione Basic c'è un problema di sicurezza dovuto al fatto che la password viaggia in chiaro. Al problema si può ovviare abilitando HTTPS nelle configurazioni di Neo4j.
Ora possiamo inserire il payload, ossia la richiesta vera e propria che facciamo al database. Ad esempio, nel riquadro Raw payload inseriamo:
{
"statements" : [
{
"statement" : "CREATE (n:User {name: 'test@html.it'}) RETURN id(n)"
},
{
"statement" : "WITH TIMESTAMP() AS timestamp CREATE(u:User {new_user}) SET u.created_at = timestamp RETURN id(u)",
"parameters" : {
"new_user" : {
"name" : "from_API@html.it",
"created_by": "HTTP API"
}
}
}]
}
Queste due query creano altrettanti nodi in transazione.
La seconda, a differenza della prima, fa uso di parametri e, come si vede nel campo parameters
, i parametri possono essere intere mappe chiave-valore che rappresentano le proprietà del nodo. La funzione TIMESTAMP
, che genera il numero di millisecondi dal 1 gennaio 1970 UTC, viene impostata con l'ausilio di WITH
, perché non può essere usata in un parametro.
La risposta del server è:
{
"results": [
{
"columns": [
"id(n)"
],
"data": [
{
"row": [
1024
],
"meta": [
null
]
}
]
},
{
"columns": [
"id(u)"
],
"data": [
{
"row": [
1025
],
"meta": [
null
]
}
]
}
],
"errors": []
}
Questa risposta ci dice che non ci sono stati errori (campo errors
) e restituisce i risultati delle due query: sono gli id (1024 e 1025) dei nodi creati. I valori sono infatti nei campi row mentre i nomi delle colonne sono nel campo columns.
Gestione delle transazioni
Come anticipato, è possibile effettuare più chiamate HTTP in una singola transazione. A questo scopo viene utilizzato un semplice protocollo basato su REST, considerando la transazione come una risorsa:
- Il client invia una richiesta all'endpoint db/data/transaction, indicando eventualmente le prime istruzioni da effettuare in transazione
- Il server risponde con i risultati, indicando inoltre l'endpoint che bisogna invocare per effettuare il commit e l'endpoint da invocare per continuare la transazione con altre istruzioni. Il server comunica anche la scadenza della transazione
- Il client può inviare al server altre istruzioni per alimentare la transazione
- Il server restituisce il risultato e la scadenza della transazione aggiornata
- Il client, quando ha finito, esegue un POST sull'endpoint per il commit. Oppure, in caso di errore, il client può invocare il rollback tramite una DELETE sullo stesso endpoint
Vediamo, ad esempio, le due istruzioni precedenti effettuate in una transazione suddivisa in due chiamate. Effettuiamo la prima invocandola all'endpoint http://localhost:7474/db/data/transaction:
{
"statements" : [
{
"statement" : "CREATE (n:User {name: 'test@html.it'}) RETURN id(n)"
}
]
}
La risposta del server ha codice HTTP 201 (Created) e riporta nello header Location la URL della transazione che può essere invcata nelle successive richieste, ad esempio: http://localhost:7474/db/data/transaction/8. Come risultato, invece, riporta qualcosa di questo tipo:
{
"commit": "http://localhost:7474/db/data/transaction/8/commit",
"results": [
{
"columns": [
"id(n)"
],
"data": [
{
"row": [
1029
],
"meta": [
null
]
}
]
}
],
"transaction": {
"expires": "Fri, 18 Aug 2017 22:49:50 +0000"
},
"errors": []
}
Ora possiamo inviare la seconda query ed effettuare il commit, inviando il seguente payload in POST sulla URL indicata nel campo commit (con Advanced Rest Client è sufficiente cliccare sulla URL per farlo) restituito dal server. Attenzione: questa operazione va effettuata senza aspettare troppo, perché altrimenti scadrebbe la transazione e otterremmo un errore 404 (Not Found) e quindi un rollback automatico.
{
"statements" : [
{
"statement" : "WITH TIMESTAMP() AS timestamp CREATE(u:User {new_user}) SET u.created_at = timestamp RETURN id(u)",
"parameters" : {
"new_user" : {
"name" : "from_API@html.it",
"created_by": "HTTP API"
}
}
}]
}
Il risultato stavolta sarà semplicemente il campo results
oppure un errore (campo errors
) in caso di fallimento.
Per causare il rollback manualmente (senza attendere la scadenza della transazione), possiamo invece inviare un DELETE alla URL http://localhost:7474/db/data/transaction/8.