Menu ▾ ▴

criar modulo

Mobius One

Criando um módulo

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/TRALResponse de 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

Pedido e resposta do módulo

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.

O que saber antes de escrever o seu

  • overload no Answer novo. Com a diretiva, o Answer da classe do
    módulo se soma às sobrecargas herdadas (status + texto, status + stream,
    status, arquivo). Sem ela, esconde todas. Vale no Delphi e no FPC.
  • Rota com handler do núcleo continua como sempre. Se a rota tem OnReply
    (o CreateRoute de sempre), ela recebe o TRALRequest/TRALResponse do
    núcleo e o módulo não cria o contexto. O contexto só existe para as rotas sem
    esse handler. Por isso um módulo pode misturar os dois tipos de rota.
  • O que a classe não repete está em Core. ARequest.Core é o
    TRALRequest, e AResponse.Core é o TRALResponse.
  • Estado de um pedido fica na classe do pedido. O TRALDBRequest pega a
    conexão do pool no primeiro uso e a devolve no destrutor, e nenhuma rota do
    banco escreve try/finally para isso.
  • OPTIONS, 405 e autenticação são do núcleo. O módulo não precisa tratar
    nenhum dos três: o método fora de AllowedMethods responde 405 antes de o
    handler rodar.

Erro próprio

function 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.

Servidor ligando e desligando

  • ServerActivating roda quando o servidor recebe Active := True, ou quando o
    módulo é ligado a um servidor que já está ativo.
  • Roda antes de o engine abrir a porta.
  • Uma exceção ali deixa o servidor parado, e os módulos que já tinham ouvido
    o start ouvem o stop.
  • O TRALDBModule usa esse momento para abrir o pool
    (PoolOptions.PrepareOnActivate).
  • ServerDeactivating roda quando o servidor recebe Active := False, quando
    o módulo sai de um servidor ativo, ou quando o servidor é liberado ativo.
  • Pedidos podem ainda estar rodando nesse momento: não libere o que uma rota
    pode estar usando.
  • Uma exceção ali vai para OnServerError e nunca impede o servidor de
    parar.
  • Nenhum dos dois roda em design time. Ao carregar um form, rodam no Loaded
    do módulo, depois que as propriedades dele foram lidas.

Content-Type padrão

DefaultContentType 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;

Na paleta

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.