Roteador
O que é Roteamento
Router define qual middleware e Handler processarão uma requisição HTTP. Esta é a funcionalidade mais fundamental e central no Salvo.
Internamente, um Router é essencialmente composto por uma série de filtros. Quando uma requisição chega, o roteador testa a si mesmo e seus descendentes em ordem, de cima para baixo, para ver se conseguem corresponder à requisição. Se uma correspondência for bem-sucedida, o middleware em toda a cadeia formada pelo roteador e seus roteadores descendentes é executado sequencialmente. Se durante o processamento, o status da Response for definido como erro (4XX, 5XX) ou redirecionamento (3XX), o middleware subsequente e o Handler serão ignorados. Você também pode chamar manualmente ctrl.skip_rest() para pular o middleware e Handler restantes.
Durante o processo de correspondência, existe um objeto de informação do caminho da URL, que pode ser considerado como um objeto que deve ser completamente consumido pelos filtros durante a correspondência. Se todos os filtros em um determinado Router corresponderem com sucesso, e esta informação do caminho da URL tiver sido totalmente consumida, é considerada uma "correspondência bem-sucedida".
Por exemplo:
É na verdade equivalente a:
Se acessar GET /articles/, é considerada uma correspondência bem-sucedida e list_articles é executado. No entanto, se acessar GET /articles/123, a correspondência da rota falha e retorna um erro 404 porque Router::with_path("articles") consome apenas a parte /articles da informação do caminho da URL, deixando a parte /123 não consumida, portanto a correspondência é considerada falha. Para conseguir uma correspondência bem-sucedida, a rota pode ser alterada para:
Aqui, {**} corresponde a qualquer caminho restante, então pode corresponder a GET /articles/123 e executar list_articles.
Definição Plana
Podemos definir rotas em um estilo plano:
Definição em Árvore
Também podemos definir rotas em uma estrutura em árvore, que é a abordagem recomendada:
Esta forma de definição torna as definições de Router hierárquicas, claras e simples para projetos complexos.
Muitos métodos em Router retornam Self após serem chamados, facilitando a escrita de código encadeado. Às vezes, você precisa decidir como rotear com base em certas condições. O sistema de roteamento também fornece a função then, que é fácil de usar:
Este exemplo significa que as rotas para criar, editar e excluir artigos só são adicionadas quando o servidor está em admin_mode.
Recuperando Parâmetros das Rotas
No código acima, {id} define um parâmetro. Podemos recuperar seu valor através da instância Request:
{id} corresponde a um segmento no caminho. Normalmente, o id de um artigo é apenas um número. Neste caso, podemos usar uma expressão regular para restringir a regra de correspondência para id, como r"{id|\d+}".
Para tipos numéricos, há um método ainda mais simples usando <id:num>. Notações específicas são:
{id:num}corresponde a qualquer número de caracteres de dígito.{id:num[10]}corresponde exatamente a um número específico de caracteres de dígito; aqui, 10 significa que corresponde exatamente a 10 dígitos.{id:num(..10)}corresponde a 1 a 9 caracteres de dígito.{id:num(3..10)}corresponde a 3 a 9 caracteres de dígito.{id:num(..=10)}corresponde a 1 a 10 caracteres de dígito.{id:num(3..=10)}corresponde a 3 a 10 caracteres de dígito.{id:num(10..)}corresponde a pelo menos 10 caracteres de dígito.
Você também pode corresponder a todos os segmentos de caminho restantes usando {**}, {*+} ou {*?}. Para melhor legibilidade do código, você pode adicionar nomes apropriados para tornar a semântica do caminho mais clara, por exemplo, {**file_path}.
{**}: Representa uma correspondência curinga onde a parte correspondente pode ser uma string vazia. Por exemplo, o caminho/files/{**rest_path}corresponde a/files,/files/abc.txt,/files/dir/abc.txt.{*+}: Representa uma correspondência curinga onde a parte correspondente deve existir e não pode ser uma string vazia. Por exemplo, o caminho/files/{*+rest_path}NÃO corresponde a/filesmas corresponde a/files/abc.txt,/files/dir/abc.txt.{*?}: Representa uma correspondência curinga onde a parte correspondente pode ser uma string vazia, mas só pode conter um segmento de caminho. Por exemplo, o caminho/files/{*?rest_path}NÃO corresponde a/files/dir/abc.txtmas corresponde a/files,/files/abc.txt.
Múltiplas expressões podem ser combinadas para corresponder ao mesmo segmento de caminho, por exemplo, /articles/article_{id:num}/, /images/{name}.{ext}.
Adicionando Middleware
Middleware pode ser adicionado através da função hoop em um roteador:
Neste exemplo, o roteador raiz usa check_authed para verificar se o usuário atual está logado. Todos os roteadores descendentes são afetados por este middleware.
Se os usuários estão apenas navegando pelas informações de writer e artigos, podemos preferir que eles possam navegar sem fazer login. Podemos definir as rotas assim:
Mesmo que dois roteadores tenham a mesma definição de caminho path("articles"), eles ainda podem ser adicionados ao mesmo roteador pai.
Filtros
Router internamente usa filtros para determinar se uma rota corresponde. Filtros suportam operações lógicas básicas usando or ou and. Um roteador pode conter múltiplos filtros; quando todos os filtros correspondem com sucesso, a rota corresponde com sucesso.
A informação do caminho do site é uma estrutura em árvore, mas esta estrutura em árvore não é equivalente à estrutura em árvore que organiza os roteadores. Um caminho de site pode corresponder a múltiplos nós de roteador. Por exemplo, algum conteúdo sob o caminho articles/ pode exigir login para visualização, enquanto outro não. Podemos organizar subcaminhos que requerem verificação de login sob um roteador contendo middleware de verificação de login, e aqueles que não requerem verificação sob outro roteador sem ele:
Roteadores usam filtros para filtrar requisições e enviá-las para o middleware e Handler correspondentes para processamento.
path e method são dois dos filtros mais comumente usados. path é usado para corresponder informações de caminho; method é usado para corresponder ao Método da requisição, por exemplo, GET, POST, PATCH, etc.
Podemos conectar filtros de roteador usando and, or:
Filtro de Caminho
Filtros baseados em caminhos de requisição são os mais frequentemente usados. Filtros de caminho podem definir parâmetros, por exemplo:
Em um Handler, eles podem ser recuperados através da função get_param do objeto Request:
Filtro de Método
Filtra requisições com base no Method da requisição HTTP, por exemplo:
Aqui, get, patch, delete são todos filtros de Método. Isto é na verdade equivalente a:
Wisp Personalizado
Para certas expressões de correspondência que ocorrem frequentemente, podemos atribuir um nome curto via PathFilter::register_wisp_regex ou PathFilter::register_wisp_builder. Por exemplo, o formato GUID frequentemente aparece em caminhos. A maneira normal é escrever assim toda vez que a correspondência é necessária:
Escrever esta regex complexa toda vez é propenso a erros e torna o código menos legível. Você pode fazer isso em vez disso:
Você só precisa registrá-lo uma vez. Depois disso, você pode usar diretamente notação simples como {id:guid} para corresponder a GUIDs, simplificando a escrita do código.
Vindo de um Framework Web Baseado em Controller, Como Entender o Router?
As principais diferenças entre frameworks web projetados com roteamento (como Salvo) e frameworks tradicionais MVC ou projetados com Controller são:
-
Flexibilidade: O design de roteamento permite uma definição mais flexível dos fluxos de processamento de requisições, permitindo controle preciso sobre a lógica para cada caminho. Por exemplo, no Salvo você pode definir diretamente funções handler para caminhos específicos:
No design Controller, você normalmente precisa definir uma classe controller primeiro, depois definir múltiplos métodos dentro da classe para lidar com diferentes requisições:
-
Integração de Middleware: Frameworks de roteamento geralmente fornecem uma maneira mais concisa de integrar middleware, permitindo que o middleware seja aplicado a rotas específicas. O middleware do Salvo pode ser aplicado precisamente a rotas particulares:
-
Organização de Código: O design de roteamento tende a organizar o código com base na funcionalidade ou endpoints da API, em vez da camada Model-View-Controller do MVC. O design de roteamento incentiva organizar o código de acordo com a funcionalidade do endpoint da API:
-
Leveza: O design de roteamento é tipicamente mais leve, reduzindo conceitos e restrições