Home > Criando um módulo
[!IMPORTANT]
Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.Nível: avançado. Até a 1.2, um módulo só consegue criar rotas com o
TRALRequest/TRALResponsede sempre.
Um módulo é um componente que se liga a um TRALServer (propriedade Server)
e traz as rotas dele. O TRALDBModule, o TRALWebModule e o
TRALSwaggerModule são módulos. Todo módulo descende de TRALModuleRoutes
(unit RALServer), e um módulo de terceiros usa exatamente os mesmos pontos de
extensão que eles.
Do mais simples ao mais completo, um módulo pode ter:
| o quê | como |
|---|---|
| rotas | CreateRoute no construtor, com o handler de sempre |
| algo antes e depois de toda rota dele | sobrescreve BeforeExecute/AfterExecute |
| dados próprios em cada rota, ou um handler de outro tipo | a própria classe de rota: RouteClass |
| pedido e resposta próprios, com métodos que só ele tem | RequestClass/ResponseClass + ExecuteContext |
| uma resposta de erro própria | HandleException |
outro Content-Type padrão para as respostas dele |
DefaultContentType |
| reagir ao servidor ligando e desligando | ServerActivating/ServerDeactivating |
O handler de uma rota comum recebe o TRALRequest e o TRALResponse do núcleo.
Um módulo pode entregar aos handlers dele outras classes, com métodos que o
resto do projeto não tem. O TRALDBModule, por exemplo, tem um
AResponse.Answer(SQLCache) que só as rotas dele enxergam.
Essas classes não são cópias do pedido nem da resposta. TRALModuleRequest e
TRALModuleResponse guardam o objeto do núcleo (Core) e escrevem nele, e é
esse objeto que o engine envia. O módulo cria as duas no início da rota e as
libera no fim.
uses
Classes, SysUtils,
RALServer, RALRoutes, RALRequest, RALResponse, RALTypes, RALConsts,
RALMIMETypes;
type
// a resposta do módulo: um Answer que só as rotas dele têm
TSaudacaoResponse = class(TRALModuleResponse)
public
procedure Answer(const ANome: StringRAL; AVezes: Integer); overload;
end;
TSaudacaoOnReply = procedure(ARequest: TRALModuleRequest;
AResponse: TSaudacaoResponse) of object;
// a rota do módulo: um dado por rota e o handler tipado
TSaudacaoRoute = class(TRALRoute)
private
FVezes: Integer;
FOnSaudacao: TSaudacaoOnReply;
public
property Vezes: Integer read FVezes write FVezes;
property OnSaudacao: TSaudacaoOnReply read FOnSaudacao write FOnSaudacao;
end;
TSaudacaoModule = class(TRALModuleRoutes)
private
procedure Ola(ARequest: TRALModuleRequest; AResponse: TSaudacaoResponse);
protected
class function RouteClass: TRALRouteClass; override;
class function ResponseClass: TRALModuleResponseClass; override;
procedure ExecuteContext(ARoute: TRALRoute; ARequest: TRALModuleRequest;
AResponse: TRALModuleResponse); override;
public
constructor Create(AOwner: TComponent); override;
end;
implementation
procedure TSaudacaoResponse.Answer(const ANome: StringRAL; AVezes: Integer);
var
vTexto: StringRAL;
vInt: Integer;
begin
vTexto := '';
for vInt := 1 to AVezes do
vTexto := vTexto + 'ola ' + ANome + ' ';
Answer(HTTP_OK, Trim(vTexto), rctTEXTPLAIN); // a sobrecarga herdada
end;
class function TSaudacaoModule.RouteClass: TRALRouteClass;
begin
Result := TSaudacaoRoute;
end;
class function TSaudacaoModule.ResponseClass: TRALModuleResponseClass;
begin
Result := TSaudacaoResponse;
end;
constructor TSaudacaoModule.Create(AOwner: TComponent);
var
vRota: TSaudacaoRoute;
begin
inherited Create(AOwner);
// NewRoute cria a rota da classe do módulo, com caminho e descrição
vRota := TSaudacaoRoute(NewRoute('ola/:nome', 'Cumprimenta'));
vRota.Vezes := 2;
vRota.OnSaudacao := {$IFDEF FPC}@{$ENDIF}Ola;
vRota.AllowedMethods := [amGET];
end;
procedure TSaudacaoModule.ExecuteContext(ARoute: TRALRoute;
ARequest: TRALModuleRequest; AResponse: TRALModuleResponse);
begin
if (ARoute is TSaudacaoRoute) and Assigned(TSaudacaoRoute(ARoute).OnSaudacao) then
TSaudacaoRoute(ARoute).OnSaudacao(ARequest, TSaudacaoResponse(AResponse))
else
inherited; // sem handler: 404
end;
procedure TSaudacaoModule.Ola(ARequest: TRALModuleRequest;
AResponse: TSaudacaoResponse);
begin
AResponse.Answer(ARequest.ParamByName('nome').AsString,
TSaudacaoRoute(ARequest.Route).Vezes);
end;
Com Domain := '/saudacao', um GET /saudacao/ola/ana responde ola ana ola ana.
overload no Answer novo. Com a diretiva, o Answer da classe doOnReplyCreateRoute de sempre), ela recebe o TRALRequest/TRALResponse doCore. ARequest.Core é oTRALRequest, e AResponse.Core é o TRALResponse.TRALDBRequest pega atry/finally para isso.AllowedMethods responde 405 antes de ofunction TMeuModule.HandleException(ARequest: TRALRequest;
AResponse: TRALResponse; AException: Exception): boolean;
begin
Result := True; // respondi: o servidor não responde 500 nem chama OnServerError
AResponse.Answer(422, '{"erro":"' + AException.Message + '"}', rctAPPLICATIONJSON);
end;
HandleException recebe as exceções de BeforeExecute, da rota e de
AfterExecute daquele módulo. Devolvendo False (o padrão), a exceção segue
para o servidor, que responde 500 e dispara OnServerError, como sempre.
ServerActivating roda quando o servidor recebe Active := True, ou quando oTRALDBModule usa esse momento para abrir o poolPoolOptions.PrepareOnActivate).ServerDeactivating roda quando o servidor recebe Active := False, quandoOnServerError e nunca impede o servidor deLoadedDefaultContentType diz em que tipo saem as respostas do módulo quando a rota
não diz outro: as do Answer sem tipo e as que o próprio módulo dá (405,
OPTIONS). Vazio, o padrão do núcleo, é JSON. O servidor SOAP
responde application/xml:
function TMeuModulo.DefaultContentType: StringRAL;
begin
Result := rctAPPLICATIONXML;
end;
procedure Register;
begin
RegisterComponents('RAL - Modules', [TSaudacaoModule]);
end;
Não publique Routes num módulo que cria as rotas no construtor. O .dfm
passaria a gravar essas rotas, e ao recarregar o form a coleção seria refeita
sem os handlers. O TRALWebModule publica Routes porque as rotas dele são
editadas pelo usuário.