Router
define quais middlewares e Handlers
processarão uma requisição HTTP. Esta é a funcionalidade mais básica e central do Salvo.
Internamente, o Router
consiste em uma série de filtros (Filters). Quando uma requisição chega, o roteador testa sequencialmente, de cima para baixo, se ele mesmo e seus descendentes podem corresponder à requisição. Se houver correspondência, os middlewares ao longo de toda a cadeia formada pelo roteador e seus descendentes são executados em ordem. Se durante o processamento o status da Response
for definido como erro (4XX, 5XX) ou redirecionamento (3XX), os middlewares e Handlers
subsequentes serão ignorados. Você também pode chamar manualmente ctrl.skip_rest()
para pular os middlewares e Handlers
seguintes.
Durante o processo de correspondência, existe uma informação de caminho URL que pode ser considerada como um objeto que precisa ser completamente consumido pelos Filters. Se todos os Filters em um Router corresponderem com sucesso e essa informação de caminho URL tiver sido completamente consumida, considera-se que houve "correspondência bem-sucedida".
Por exemplo:
É equivalente a:
Se acessar GET /articles/
, considera-se correspondência bem-sucedida e executa list_articles
. Mas se acessar GET /articles/123
, a correspondência falhará e retornará erro 404, porque Router::with_path("articles")
só consumiu /articles
do caminho URL, restando /123
não consumido, portanto a correspondência falha. Para que corresponda, o roteador pode ser alterado para:
Aqui, {**}
corresponderá a qualquer caminho adicional, permitindo que GET /articles/123
execute list_articles
.
Podemos definir rotas em estilo plano:
Também podemos definir rotas em forma de árvore, o que é recomendado:
Esta forma de definição torna a organização do Router clara e simples para projetos complexos.
Muitos métodos no Router
retornam a si mesmos (Self), permitindo encadeamento. Às vezes, você precisa decidir como rotear com base em certas condições. O sistema de roteamento também fornece a função then
, fácil de usar:
Este exemplo mostra que apenas quando o servidor está em admin_mode
, as rotas para criar, editar e excluir artigos são adicionadas.
No código acima, {id}
define um parâmetro. Podemos obter seu valor através da instância Request
:
{id}
corresponde a um segmento do caminho. Normalmente, o id
de um artigo é apenas um número, então podemos usar expressões regulares para restringir a correspondência, como r"{id|\d+}"
.
Para tipos numéricos, há uma maneira mais simples usando <id:num>
:
{id:num}
: corresponde a qualquer número de dígitos;{id:num[10]}
: corresponde exatamente a 10 dígitos;{id:num(..10)}
: corresponde de 1 a 9 dígitos;{id:num(3..10)}
: corresponde de 3 a 9 dígitos;{id:num(..=10)}
: corresponde de 1 a 10 dígitos;{id:num(3..=10)}
: corresponde de 3 a 10 dígitos;{id:num(10..)}
: corresponde a pelo menos 10 dígitos.Também é possível usar {**}
, {*+}
ou {*?}
para corresponder a todos os segmentos restantes do caminho. Para melhor legibilidade, pode-se adicionar nomes descritivos, como {**file_path}
.
{**}
: corresponde a strings vazias, como /files
, /files/abc.txt
, /files/dir/abc.txt
;{*+}
: exige que o segmento não seja vazio, como /files/abc.txt
, mas não /files
;{*?}
: permite strings vazias, mas apenas um segmento, como /files
, /files/abc.txt
, mas não /files/dir/abc.txt
.É possível combinar múltiplas expressões para corresponder ao mesmo segmento, como /articles/article_{id:num}/
, /images/{name}.{ext}
.
Middlewares podem ser adicionados através da função hoop
:
Neste exemplo, o roteador raiz usa check_authed
para verificar se o usuário está logado. Todos os roteadores descendentes são afetados por este middleware.
Se os usuários podem apenas visualizar informações de writer
e artigos sem login, podemos definir as rotas assim:
Mesmo com duas rotas definindo o mesmo caminho path("articles")
, elas ainda podem ser adicionadas ao mesmo roteador pai.
O Router
usa filtros internamente para determinar a correspondência. Filtros suportam operações lógicas básicas como or
e and
. Um roteador pode conter múltiplos filtros, e só corresponde se todos os filtros corresponderem.
A estrutura de caminhos de um site é hierárquica, mas não necessariamente igual à estrutura do roteador. Um caminho pode corresponder a múltiplos nós do roteador. Por exemplo, alguns conteúdos em articles/
podem requerer login, enquanto outros não. Podemos organizar os subcaminhos que requerem login em um roteador com middleware de autenticação, e os que não requerem em outro:
O roteador usa filtros para direcionar requisições aos middlewares e Handlers
apropriados.
path
e method
são dois filtros comuns. path
corresponde ao caminho; method
corresponde ao método HTTP, como GET, POST, PATCH, etc.
Podemos conectar filtros com and
e or
:
Filtros baseados em caminho são os mais usados. Podem incluir parâmetros:
No Handler
, os parâmetros podem ser obtidos através do objeto Request
:
Filtram requisições pelo método HTTP:
Aqui, get
, patch
, delete
são filtros de método, equivalentes a:
Para expressões de correspondência frequentes, podemos usar PathFilter::register_wisp_regex
ou PathFilter::register_wisp_builder
para definir um nome curto. Por exemplo, o formato GUID aparece frequentemente em caminhos. Normalmente, seria necessário escrever:
Escrever essa expressão regular complexa repetidamente é propenso a erros. Em vez disso, podemos fazer:
Após registrar uma vez, podemos usar {id:guid}
para corresponder a GUIDs, simplificando o código.
As principais diferenças entre frameworks baseados em roteamento (como Salvo) e os baseados em MVC ou Controller são:
Flexibilidade: O roteamento permite definir o fluxo de processamento de requisições com mais precisão. Por exemplo, no Salvo você pode definir diretamente:
Enquanto em um Controller, você precisaria definir uma classe e métodos:
Integração de Middleware: Frameworks de roteamento oferecem integração mais simples de middlewares, aplicáveis a rotas específicas:
Organização do Código: O roteamento organiza o código por funcionalidade ou endpoints de API, em vez de seguir a divisão MVC:
Leveza: O roteamento geralmente é mais leve, com menos conceitos e restrições impostos pelo framework. Você pode incluir apenas o necessário, sem seguir estruturas rígidas.
O roteamento torna o desenvolvimento de APIs mais intuitivo, especialmente para microsserviços e APIs RESTful modernas. Em frameworks como Salvo, o roteamento é o conceito central, refletindo diretamente a estrutura e comportamento da API, tornando o código mais fácil de entender e manter. Em contraste, designs baseados em Controller geralmente requerem mais configuração e convenções para alcançar a mesma funcionalidade.
Categoria | Método | Descrição |
---|---|---|
Criação/Acesso | new() | Cria novo roteador |
routers()/routers_mut() | Obtém referência/referência mutável aos roteadores filhos | |
hoops()/hoops_mut() | Obtém referência/referência mutável aos middlewares | |
filters()/filters_mut() | Obtém referência/referência mutável aos filtros | |
Organização de Rotas | unshift() | Insere roteador filho no início |
insert() | Insere roteador filho em posição específica | |
push() | Adiciona roteador filho | |
append() | Adiciona múltiplos roteadores filhos | |
then() | Configuração personalizada da cadeia de roteadores | |
Middleware | with_hoop()/hoop() | Cria/adiciona middleware |
with_hoop_when()/hoop_when() | Cria/adiciona middleware condicional | |
Filtros de Caminho | with_path()/path() | Cria/adiciona filtro de caminho |
with_filter()/filter() | Cria/adiciona filtro | |
with_filter_fn()/filter_fn() | Cria/adiciona filtro de função | |
Filtros de Rede | scheme() | Adiciona filtro de protocolo |
host()/with_host() | Adiciona/cria filtro de host | |
port()/with_port() | Adiciona/cria filtro de porta | |
Métodos HTTP | get()/post()/put() | Cria rota para método |