Introduzione a WiRLClient

Luca Minuti - Jan 29 - - Dev Community

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . .