Skip to content
mobius1qwe edited this page Nov 1, 2026 · 1 revision

Home > Rotas e parâmetros > Rotas avançadas 🚧

Rotas avançadas: tipos, restrições, grupos, versões

Important

Em desenvolvimento (2.0). Ainda não está numa versão lançada e pode mudar.

Vale para a 2.0 · Delphi XE em diante e Lazarus/FPC 3.2 em diante · Nível: intermediário

Em uma frase: a rota pode conferir sozinha o tipo e o formato dos parâmetros do caminho, ter parte opcional ou curinga, pertencer a um grupo com prefixo e eventos próprios, atender só certas versões da API e ser escrita com um procedimento anônimo.

Você vai precisar: saber criar uma rota.

O parâmetro do caminho confere o tipo

Diga o tipo entre < e > logo depois do nome:

Servidor.CreateRoute('clientes/:id<int>', BuscarCliente);
  • /clientes/7 chega no BuscarCliente.
  • /clientes/abc não casa com essa rota. Se outra rota casar (por exemplo clientes/:nome), ela responde; se nenhuma casar, o servidor responde 404. O seu código nunca recebe um id que não é número.

O mesmo vale pelo Object Inspector: em URIParams, mude o ParamType do parâmetro. Um parâmetro prtString (o padrão) não é conferido: passa qualquer valor, como sempre.

no texto ParamType aceita
<int> prtInteger 42, -7 (cabe num Int64)
<number> prtNumber 1.5, -2e3 (ponto decimal, sempre)
<decimal> prtDecimal 10.25
<bool> prtBoolean true, false, 1, 0
<date> prtDate 2026-11-01 (data que existe)
<time> prtTime 23:59, 23:59:01.250
<datetime> prtDateTime ISO 8601, 2026-11-01T10:20:30Z
<base64> prtBase64 aGVsbG8=

Restrições

Junte com : dentro do < >:

Servidor.CreateRoute('produtos/:id<int:range(1,9999)>', Produto);
Servidor.CreateRoute('cupons/:codigo<length(8)>', Cupom);
Servidor.CreateRoute('relatorios/:formato<values(pdf,csv)>', Relatorio);
Servidor.CreateRoute('arquivos/:nome<regex([a-z0-9-]+\.txt)>', Arquivo);
restrição o que pede propriedade
min(n), max(n), range(a,b) número entre os limites MinValue, MaxValue
length(n), length(a,b), minlength(n), maxlength(n) quantidade de caracteres MinLength, MaxLength
values(a,b,c) um dos valores, exato Values
regex(...) o valor inteiro casa com a expressão Pattern

O texto da rota é lido uma vez, quando você o escreve, e preenche o URIParams; o pedido não reinterpreta nada. Uma restrição desconhecida levanta exceção na hora, assim como uma expressão regular que não compila.

A expressão regular é a do RAL

O Pattern usa o RALRegex, motor próprio do RAL, igual em todas as IDEs (as classes de regex do Delphi e do FPC são diferentes entre si). A sintaxe é a do RE2: classes [a-z], \d \w \s \b, ^ $ \A \z, * + ? {n,m} (e as versões preguiçosas com ?), grupos com e sem captura, grupos com nome (?P<nome>...), (?i) para ignorar maiúsculas. Ele roda em tempo linear: nenhum texto, por maior que seja, trava o servidor (o famoso (a+)+$ contra milhares de a responde na hora).

Para isso ele não aceita retrovisor (\1) nem lookahead/lookbehind ((?=, (?!, (?<=): o padrão levanta ERALRegex ao compilar, com a posição do erro.

Você pode usar o motor no seu código também:

uses RALRegex;

vRe := TRALRegex.Create('(?P<ano>\d{4})-(?P<mes>\d\d)');
try
  vMatch := vRe.Match('emitida em 2026-11-01');
  if vMatch.Success then
    ShowMessage(vMatch.GroupByName('ano'));        // 2026
  vTexto := vRe.Replace(vTexto, '${mes}/${ano}');
finally
  vRe.Free;
end;

IsMatch, Match, Matches, Replace ($1, ${nome}, $$) e Split. Um padrão compilado pode ser usado por várias threads ao mesmo tempo.

Parte opcional e curinga

Servidor.CreateRoute('pedidos/:status?', ListarPedidos);   // /pedidos e /pedidos/abertos
Servidor.CreateRoute('estaticos/*caminho', Arquivos);       // /estaticos/css/site.css
  • :nome? só no fim da rota. Quando o pedido não traz, o parâmetro não existe (ParamByName('status') devolve nil).
  • *nome só como último pedaço: pega o resto do caminho, com as barras (caminho = css/site.css). Também casa sem nada depois (/estaticos), com valor vazio.
  • Uma rota exata ganha da opcional e do curinga: com estaticos/logo.png e estaticos/*caminho, o pedido /estaticos/logo.png vai para a primeira.

Grupos de rotas

Um grupo junta rotas com o mesmo prefixo, o mesmo login e os mesmos eventos:

vGrupo := Servidor.RouteGroups.AddGroup('api', '/api/v1');
vGrupo.SkipAuthMethods := [amGET];               // GET público em todo o grupo
vGrupo.Description.Text := 'API pública';        // vira a tag no Swagger
vGrupo.OnBeforeExecute := ConferirChave;

Servidor.CreateRoute('clientes', Clientes).Group := 'api';   // atende /api/v1/clientes
  • O caminho da rota fica: Domain do módulo + Prefix do grupo + a rota.
  • Mudou o Prefix, mudam todas as rotas do grupo. Apagou o grupo, elas voltam ao caminho sem prefixo.
  • Os módulos também têm RouteGroups.

Eventos antes e depois

A ordem é sempre esta:

  1. BeforeExecute do módulo
  2. OnBeforeExecute do grupo
  3. OnBeforeExecute da rota
  4. o procedimento da rota
  5. OnAfterExecute da rota
  6. OnAfterExecute do grupo
  7. AfterExecute do módulo

Se o OnBeforeExecute do grupo ou da rota responder um status 400 ou maior (AResponse.Answer(403, ...)), o pedido termina ali: a rota não roda. É o lugar de uma checagem que vale só para parte da API.

Note

Não existe plugin por rota: plugins valem para o servidor inteiro. Para um plugin agir só em parte dos caminhos, use IncludePaths ou ExcludePaths dele (abaixo).

Plugin só em alguns caminhos

Todo plugin tem IncludePaths e ExcludePaths, um caminho por linha:

PluginLimites.IncludePaths.Text := '/api';          // só /api e o que está embaixo
PluginLimites.ExcludePaths.Text := '/health' + sLineBreak + '/api/*/publico';
  • /api vale para /api e tudo abaixo dele; * vale um pedaço qualquer.
  • Valem nas fases de validação e de processamento do plugin. A autenticação tem o SkipAuthMethods de rota e de grupo.

O método QUERY

QUERY é um GET com corpo: seguro e repetível, para uma busca grande demais para a URL.

Servidor.CreateRoute('busca', Buscar).Allow([amQUERY]);

Cliente.Request.AddBody('{"cidade":"Recife","limite":50}', rctAPPLICATIONJSON);
Cliente.Query('/busca', vResposta);
  • Motores de cliente que enviam QUERY: Indy, fpHTTP, mORMot2, netHTTP, OkHttp, MsQuic e Kwik. Em outro motor, Query levanta exceção antes de conectar.
  • Uma rota amALL também aceita QUERY. No Swagger (OpenAPI 3.1) o QUERY não aparece: a especificação só o terá na 3.2.

Versões da API

Servidor.ApiVersioning.Enabled := True;
Servidor.ApiVersioning.Default := '1';
Servidor.ApiVersioning.Deprecated.Text := '1=Sat, 01 Jan 2028 00:00:00 GMT';

Servidor.CreateRoute('clientes', ClientesV1).Versions := '1';
Servidor.CreateRoute('clientes', ClientesV2).Versions := '2,3';
Source o cliente pede a versão 2 assim
avPath (padrão) GET /v2/clientes (o /v2 sai do caminho: a rota é clientes)
avHeader cabeçalho Api-Version: 2 (HeaderName)
avQuery ?api-version=2 (QueryName)
avMediaType Accept: application/vnd.app.v2+json ou Accept: application/json; version=2
  • Sem versão no pedido, vale Default.
  • A versão escolhida fica em ARequest.ApiVersion.
  • Rota sem Versions usa as do grupo; sem nenhuma, atende qualquer versão.
  • Uma versão de Deprecated recebe o cabeçalho Deprecation: true e, se tiver data depois do =, Sunset com ela.
  • Pedido de uma versão que nenhuma rota daquele caminho atende: 404.

Atrás de um proxy num subcaminho: PathBase

O NGINX manda https://empresa.com/minhaapp/clientes para o seu servidor sem tirar o /minhaapp:

Servidor.PathBase := '/minhaapp';
  • O /minhaapp sai do caminho antes de tudo: rotas, plugins e módulos veem /clientes.
  • Pedido fora do /minhaapp: 404.
  • Um Location relativo que a sua rota responde (/novo) sai como /minhaapp/novo.

Rota com procedimento anônimo

No Delphi (e no FPC 3.3.1), a rota pode ser escrita ali mesmo:

uses RALRoutesAnon;

Servidor.Get('/ping',
  procedure(ARequest: TRALRequest; AResponse: TRALResponse)
  begin
    AResponse.Answer(HTTP_OK, 'pong', rctTEXTPLAIN);
  end);

Get, Post, Put, Patch, Delete e Map(rota, [métodos], ...), no servidor e nos módulos; todos devolvem a rota, para encadear. No Lazarus com FPC 3.2 a unit compila vazia: use o CreateRoute de sempre.

Muitas rotas

Com 16 rotas ou mais num servidor ou módulo, o RAL procura a rota por uma árvore dos pedaços do caminho em vez de testar uma por uma. Você não precisa fazer nada, e a escolha é a mesma: entre rotas que casam, ganha a de menor peso e, empatando, a que foi criada primeiro.

Exemplo pronto

Nos Samples da 2.0: Delphi/Console/RoutingExample (Indy) e Lazarus/Console/RoutingExample (fpHTTP), com tudo desta página num servidor só.

Erros comuns

sintoma causa solução
404 num /clientes/abc a rota é :id<int> é o esperado; crie outra rota para texto, se quiser
Unknown route constraint ao criar a rota nome errado dentro do < > veja a tabela de restrições
ERALRegex ao criar a rota \1, (?=, (?<= ou expressão mal formada reescreva sem retrovisor nem lookaround
The QUERY method is not supported by the ... engine o motor do cliente não envia QUERY troque o motor (veja a lista acima)
tudo 404 depois de ligar o PathBase o proxy já tira o prefixo deixe PathBase vazio
rota de grupo não atende Group com o nome errado o nome é o de RouteGroups, sem diferença de maiúsculas

Veja também

Clone this wiki locally