Spesso WiRL viene usato solo come una libreria per scrivere API ReST. Ma già dalle prime versioni è presente un modulo per la creazione di client ReST che si possono adattare a server scritti con qualunque tecnologia.
Negli ultimi tempi abbiamo cercato di semplificare al massimo l'uso di questa parte della libreria lasciandone inalterata la flessibilità e anzi cercando, dove possibile, di ampliarne i possibili casi d'uso. Parte di questo processo consiste nel utilizzare gli stessi concetti dello sviluppo del server anche per la parte client. In effetti molte classi presenti nel server si ripresentano in modo analogo sul client:
Server | Client |
---|---|
TWiRLServer | TWiRLClient |
TWiRLApplication | TWiRLClientApplication |
Risorse (any class*) | TWiRLClientResource |
TWiRLFilterRegistry | TWiRLClientFilterRegistry |
TMessageBodyReader** | TMessageBodyReader |
TMessageBodyWriter** | TMessageBodyWriter |
* Qualunque classe registrata con RegisterResource
** Usano gli stessi reader e writer!!
TWiRLClient
La classe base che implementa il protocollo HTTP è TWiRLClient
; l'utilizzo è molto semplice e ricorda quello della classe THTTPClient (presente da DelphiXE8). Questa classe è il cuore della parte client di WiRL e le varie classi come TWiRLClientApplication
e TWiRLClientResource
la usano per comunicare col server.
procedure TForm1.Button1Click(Sender: TObject);
var
LResponse: IWiRLResponse;
LHeader: TWiRLHeader;
LRequestHeader: IWiRLHeaders;
begin
LRequestHeader := TWiRLHeaders.Create;
LRequestHeader.Accept := 'application/json';
LResponse := WiRLClient1.Get('http://localhost:9999/', nil, LRequestHeader);
Memo1.Lines.Add(LResponse.ContentText);
end;
Nell'esempio al metodo Get
viene passato l'URL da richiamare, uno stream da usare per il corpo della risposta (se è nil, come nell'esempio, viene creato un TMemoryStream da WiRL che sarà anche distrutto in automatico insieme all'oggetto IWiRLResponse) e la lista degli header.
L'oggetto TWiRLClient, per effettuare la chiamate a sua volta si appoggia a THTTP (Indy) oppure THttpClient (nativo Delphi con supporto HTTPS). Si può scegliere quale usare tramite la proprietà ClientVendor
. Attenzione: a seconda del vendor scelto deve essere inclusa la unit WiRL.http.Client.Indy
o WiRL.http.Client.NetHttp
.
È anche possibile creare delle implementazioni personalizzate basate per esempio su ICS o Synapse semplicemente implementando le interfacce IWiRLClient
e IWiRLResponse
.
Inoltre il componente possiede la proprietà WiRLEngineURL
che viene usata da TWiRLClientApplication
e TWiRLClientResource
come entry point base le chiamate HTTP(S).
TWiRLClientApplication
Come sul server la classe TWiRLClientApplication
serve per configurare message body reader e writer, i filtri e tutti i plugin registrati. Il modo più semplice per inizializzarla è tramite la fluent interface:
constructor TMyClass.InitWiRLClient;
begin
// Crea l'istanza di TWiRLClientApplication
FApp := TWiRLClientApplication.Create(nil);
FApp
// Associa tutti i reader registrati
.SetReaders('*.*')
// Associa tutti i writer registrati
.SetWriters('*.*')
// Associa tutti i filtri registrati
.SetFilters('*.*')
// Configurara Neon
.Plugin.Configure<IWiRLConfigurationNeon>
.SetUseUTCDate(True)
.SetVisibility([mvPublic, mvPublished])
.SetMemberCase(TNeonCase.CamelCase)
.BackToApp;
FApp.AppName := 'app';
FApp.Client := FClient;
// ...
end;
I message body reader e writer sono gli stessi del server e permettono di trasformare gli stream di dati inviati al server o in arrivo dal server in oggetti. In questo modo uno stream con un JSON come questo:
{
"name": "Luca",
"role": "ADMIN"
}
può essere usato per inizializzare un ipotetico oggetto TUser.
I filtri possono essere usati un po' come degli eventi per intercettare la richiesta inviata al server o la risposta e compiere qualche azione (eseguire un log, gestire l'autenticazione, salvare i cookie, ecc.).
TWiRLClientResource
Una volta inizializzati TWiRLClient e TWiRLClientApplication è possibile effettuare le chiamate ReST. Anche in questo caso è possibile usare la sintassi fluent e visto che l'implementazione è fatta tramite interfacce non è necessario distruggere gli oggetti di WiRL.
function TMainDataModule.GetPerson(Id: Integer): TPerson;
begin
Result := WiRLClientApplication1
// Crea l'oggetto invokation per la risorsa
.Resource('person')
// Imposta l'header accept (necessario al server
// per decidere il formato della risposta)
.Accept('application/json')
// Imposta il parametro *Id*
.QueryParam('Id', Id.ToString)
// Effettua la chiamata deserializando la risposta
// in un oggetto di tipo TPerson
.Get<TPerson>;
end;
Con questo esempio viene chiesto a WiRLClientApplication1 di generare una nuova TWiRLInvocation
sulla risorsa indicata. Attraverso i metodi Accept
e QueryParam
si impostano gli header e i parametri richiesti dal server. Il metodo Get
farà partire la richiesta vera e propria e il corpo del messaggio restituito dal server sarà usato per creare un oggetto di tipo TPerson
come indicato.
Oggetti creati dall'utente
Un'altra modalità d'uso è quella di passare al metodo Get
(ma la stessa cosa vale anche per Post
, Put
, Delete
, ecc.) un oggetto creato da noi che verrà "riempito" con i dati forniti dal server.
function TMainDataModule.GetPersonName(Id: Integer): string;
var
LPerson: TPerson;
begin
LPerson := TPerson.Create;
try
WiRLClientApplication1
.Resource('person')
.Accept('application/json')
.QueryParam('Id', Id.ToString)
.Get(LPerson);
Result := LPerson.Name;
finally
LPerson.Free;
end;
end;
Questo tipo di approccio è particolarmente utile del caso il server restituisca un array di oggetti. Se le proprietà degli oggetti sono equivalenti ai campi di un dataset WiRL è in grado di popolare i campi del DataSet (per esempio una memory table) automaticamente.
procedure TMainDataModule.GetPeople(ADataSet: TDataSet);
begin
WiRLClientApplication1
.Resource('person')
.Accept('application/json')
.Get(ADataSet);
end;
Leggere la risposta a basso livello
In alcuni casi può essere utile leggere la risposta del server in maniera più dettagliata. Per esempio il server potrebbe restituire un JSON fatto in un modo o in un altro a seconda di determinati header o dello status code. In questo caso è necessario accedere a IWiRLResponse
che contiene tutte le informazioni sulla risposta.
function TMainDataModule.GetPerson(Id: Integer): TPerson;
var
LResponse: IWiRLResponse;
begin
LResponse := WiRLClientApplication1
.Resource('person')
.Accept('application/json')
.QueryParam('Id', Id.ToString)
.Get<IWiRLResponse>;
// Fa qualcosa con la response
if LResponse.Headers.ContentType <> 'application/json' then
Abort;
Result := LResponse.Content.AsType<TPerson>();
end;
Gestione delle eccezioni
In caso di errori di protocollo (status code 400 o 500) la chiamate al server sollevano un eccezione di tipo EWiRLClientProtocolException
questo oggetto, oltre al messaggio di errore, contiene l'intera risposta del server (nella property Response
); è quindi possibile andare a recuperare qualsiasi informazione restituita dal server.
procedure TMainDataModule.GetPeople(ADataSet: TDataSet);
begin
try
WiRLClientApplication1
.Resource('person')
.Accept('application/json')
.Get(ADataSet);
except
on E: EWiRLClientProtocolException do
begin
Log.Error('ERROR: ' + E.Response.ContentText);
raise;
end;
end;
end;
In alternativa è possibile disabilitare le eccezioni e gestire lo status code manualmente.
procedure TMainDataModule.GetPeople(ADataSet: TDataSet);
var
LResponse: IWiRLResponse;
begin
LResponse := WiRLClientApplication1
.Resource('person')
// Disabilita le eccezioni di protocollo
.DisableProtocolException
.Accept('application/json')
.Get<IWiRLResponse>(IWiRLResponse);
if LResponse.Status = TWiRLResponseStatus.Success then
LResponse.Content.AsType(ADataSet);
end;
Nell'esempio è stata usata la proprietà Status
della risposta che contiene un enumerativo che indica la categoria dello status code HTTP (Informational, Success, Redirect, ClientError, ServerError);
Altro
Ci sono molte altre funzionalità utili di WiRL Client come:
- La gestione dei filtri
- Autenticazione e autorizzazione
- Personalizzazione di message body reader e writer
- Creazioni di nuovi "vendor" per TWiRLClient
- Chiamate asincrone
Nei prossimi articoli vedremo di approfondire tutti questi temi.