In questo tutorial vedremo il modo più semplice per sviluppare un server WiRL. In realtà se durante l'installazione avete installato il package designtime WiRLDesign.dproj
in Delphi, alla voce File|New|Other, avrete una nuova opzione "WiRL Server Application Wizard" che crea una scheletro per una applicazione WiRL.
Ma qui vedremo passo passo come configurare il vostro server manualmente in modo da avere un'idea più chiara di quello che succede.
Applicazione console
Cominciamo creando un'applicazione console che può andare bene per lo sviluppo ed eventualmente può essere facilmente trasformata in un servizio Windows per l'ambiente di produzione.
Riduciamo all'osso il codice e andiamo a trasformare quello generato da Delphi in questo:
program Project1;
{$APPTYPE CONSOLE}
uses
System.SysUtils;
begin
Readln;
end.
In questo modo abbiamo un'applicazione che apre una console e termina alla pressione del tasto invio.
Creazione del server
Per prima cosa dovremo andare a creare un oggetto di tipo TWiRLServer
che si occuperà di rimanere in ascolto su una specifica porta e che smisterà il traffico HTTP. Se provate ad attivare il server (tramite la proprietà Active
) riceverete un errore a runtime:
Project Project1.exe raised exception class EWiRLException with message 'CreateServer: no server registered (add "WiRL.http.Server.*" unit to the project)'.
Questo perché la struttura di TWiRLServer gli permette di aprire il canale HTTP tramite diverse librerie; e non abbiamo ancora indicato quale libreria usare. In questo caso utilizzeremo i componenti Indy (già installati in Delphi). Per farlo è sufficiente aggiungere la unit WiRL.http.Server.Indy
.
program Project1;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
WiRL.http.Server.Indy,
WiRL.http.Server;
var
LServer: TWiRLServer;
begin
LServer := TWiRLServer.Create(nil);
try
LServer.SetPort(8080);
LServer.Active := True;
Writeln('Server running at http://localhost:' + LServer.Port.ToString + '/');
Readln;
finally
LServer.Free;
end;
end.
Configurare l'engine
Con il codice che abbiamo scritto abbiamo già un server HTTP funzionante ma qualsiasi tentativo di connessione restituirà l'errore:
Project Project1.exe raised exception class EWiRLNotFoundException with message 'Engine not found for URL [/test]'.
Questo perché non abbiamo ancora configurato l'Engine. L'engine è la parte di WiRL che si occupa di interpretare la chiamata. Al momento esistono due engine: TWiRLEngine
e TWiRLFileSystemEngine
. Il primo è il motore principale di WIRL che si occupa di gestire le chiamate ReST ed è quello che vedremo in questo tutorial, mentre il secondo permette di restituire file (html, js, css, ecc.). Volendo è possibile implementare degli Engine custom per esempio per gestire chiamate SOAP e GraphQL.
Ogni engine è associato ad un path quindi per usare l'engine ReST possiamo usare un codice simile a questo:
uses
...
WiRL.Core.Engine,
...
begin
...
LServer.AddEngine<TWiRLEngine>('rest');
...
Configurare l'applicazione
A questo punto è possibile configurare i diversi moduli di WiRL, per esempio:
WiRL ha un concetto di applicazione virtuale. In questo modo è possibile in un'unica applicazione fisica creare più moduli con delle configurazioni diverse. In questo caso, per semplicità useremo solo un'applicazione virtuale con la configurazione minima.
program Project1;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
WiRL.http.Server.Indy,
WiRL.http.Server,
WiRL.Core.Engine;
var
LServer: TWiRLServer;
begin
LServer := TWiRLServer.Create(nil);
try
LServer.SetPort(8080);
LServer.Active := True;
LServer.AddEngine<TWiRLEngine>('rest')
.AddApplication('app')
.SetResources('*');
Writeln('Server running at http://localhost:' + LServer.Port.ToString + '/');
Readln;
finally
LServer.Free;
end;
end.
In questo modo andiamo a creare l'applicazione app, raggiungibile tramite il path omonimo, contenente tutte le risorse.
Con questo abbiamo terminato la configurazione ma mancano ancora le risorse.
La prima risorsa
WiRL è in grado di manipolare (sia in ingresso che in uscita) diversi tipi di dati, da quelli più semplici, come stringhe o interi, a oggetti complessi compresi anche i DataSet. Questi saranno le nostre risorse ReST. Nelle demo presenti nei sorgenti si trovano numerosi esempi. Qui vedremo come restituire un semplice oggetto di tipo TPerson
serializzato come JSON.
Incominciamo a definire la classe TPerson
:
TPerson = class(TObject)
private
FName: string;
public
property Name: string read FName write FName;
end;
A questo punto dobbiamo creare una classe che sia in grado di manipolare l'oggetto TPerson
. In particolare proviamo a creare una semplice classe che istanzia oggetti:
TPersonResource = class
public
function GetPerson(const AName: string): TPerson;
end;
function TPersonResource.GetPerson(const AName: string): TPerson;
begin
Result := TPerson.Create;
Result.Name := AName;
end;
Quello che abbiamo visto è una normalissima classe con un metodo GetPerson
che crea le nostre persone. Non ci rimane che spiegare a WiRL come usare la classe TPersonResource
per rispondere alla chiamate HTTP. WiRL ci permette di fare tutto ciò decorando la classe e i metodi con degli attributi:
[Path('person')]
TPersonResource = class
public
[GET]
[Produces(TMediaType.APPLICATION_JSON)]
function GetPerson([QueryParam('name')] const AName: string): TPerson;
end;
Gli attributi (dichiarati nella unit WiRL.Core.Attributes
) che abbiamo usato sono:
-
Path
: Applicato ad una classe. Indica il path a cui la risorsa risponde. In pratica quando arriva una chiamata ReST con quel path WiRL istanzierà la classe e la distruggerà al termine della chiamata. Puoi essere anche applicato ad un metodo ed in quel caso indica una "sotto risorsa", il path del metodo e quello della classe verranno usati insieme per determinare l'URL della sotto risorsa. -
GET
: Applicato ad un metodo della classe. Quel metodo di istanza sarà chiamato quando il metodo HTTP corrisponde a GET (esistono anche gli attributiPOST
,PUT
,DELETE
, ecc). -
Produce
: Applicato ad un metodo. Indica che quel metodo deve essere chiamato SOLO se il client richiede il media type indicato (headerAccept
della richiesta). Esiste anche l'attributoConsumes
che serve quando con la richiesta arrivano anche dei dati nel corpo del messaggio HTTP. In quel casoConsumes
indica se il nostro metodo è in grado di accettare quel tipo di contenuto. -
QueryParam
: Viene usato su un parametro della richiesta. In questo caso indica che il parametro deve essere letto dalla query string indicata nell'URL. Esistono anchePathParam
,FormParam
,BodyParam
e molti altri.
A questo punto dobbiamo solo registrare la nostra risorsa:
TWiRLResourceRegistry.Instance.RegisterResource<TPersonResource>;
E avviando il programma possiamo puntare il browser su:
http://localhost:8080/rest/app/person?name=luca
Riceveremo il JSON corrispondente alla classe TPerson
. Per capire l'URL corretto da usare possiamo attenerci a questo schema:
Dove sono indicate le varie sezioni dell'URL e chi ne è responsabile:
-
TWiRLServer
: imposta la porta col metodoSetPort
(8080) -
TWiRLEngine
: per la prima parte dell'URL conAddEngine
(rest) -
TWiRLApplcation
: tramite il metodoAddApplication
(app) - La risorsa: tramite l'attributo
Path
(person)
Conclusioni
Con questo abbiamo concluso il nostro esempio, il codice completo si trova su GitHub è possibile trovare ulteriori informazioni nella guida ufficiale e negli esempi presenti nei sorgenti.