Repository navigation
internos plugins
Home > Guia de contribuição > Internos: Plugins
Para quem mexe no código: o host de plugins, autenticação, segurança, CORS, conteúdo, concorrência e certificado automático. Aqui fica o porquê de cada parte ser como é, e o que não pode mudar sem quebrar algo. O código traz só a descrição curta de cada coisa.
Conteúdo da versão 2.0, ainda em desenvolvimento (branch 2.0).
O que a página de uso já diz (fases, Priority, como escrever um) está em
Criar um plugin.
-
Plugins primeiro, módulos depois.
ProcessCommandsroda os plugins porPrioritye depois os módulos, que acham a rota e a executam. Servidor sem plugin ainda responde. Tudo o que não é roteamento é plugin (limite, compressão, cifra, listas, força bruta, flood, CORS, cada autenticação, corpo JSON como params), como um middleware, mas a ordem vem da prioridade e não da ordem em que a aplicação liga; empate mantém a ordem em que foram adicionados. -
Uma lista por fase. O plugin declara as suas (
Phases) e o host guarda uma lista para cada uma: fase que ninguém usa não custa nada. -
ppValidateroda antes de o corpo ser decodificado: pedido recusado nunca é descomprimido nem decifrado.ppProcessé o laço dos plugins (quem responde marcaAHandled).ppResolveRoute,ppAuthenticateeppAuthResultnão são laços do servidor: são chamadas entre plugins (rota própria, autenticadores decidindo, e quem quer saber o veredito; a força bruta conta as falhas ali, antes de qualquer módulo). -
A rota é procurada uma vez (
FindRoute) e guardada no pedido. A autenticação precisa dela (o método pula autenticação?), o CORS precisa dos métodos dela, e os módulos respondem sem procurar de novo. -
RALPriorityConcurrency(50) fica depois de todo plugin do RAL que recusa emppValidate: um pedido que a lista negra, o flood ou o limite de tamanho recusam nunca espera uma vaga na fila.
-
ServerActivating/ServerDeactivatingtêm o contrato dos módulos. "Ativando" quer dizer pedido, não porta aberta: os motores chamam antes de abrir a porta, então o plugin ainda muda como ela abre (oTRALSelfSignedPluginentrega o certificado ali). Uma exceção mantém o servidor parado. Plugin adicionado a servidor rodando ouveServerActivatingna hora. -
PluginRemovedé o único aviso garantido. O plugin se tira do host antes de as notificações deFreesaírem. Descendente que guarda referência a um plugin (oAuthenticationdo servidor) limpa ali; só comNotificationa referência ficaria solta.
Qualquer mudança (adicionar, remover, Priority, Enabled) monta um snapshot novo
(TRALPluginSnapshot) sob o lock. O velho fica vivo até o host ser destruído, porque
um pedido pode estar percorrendo-o. Cada pedido lê o snapshot uma vez e anda nele.
O que vale como tentativa é o autenticador que diz (AttemptOf, que devolve
TRALAuthAttempt): segredo errado conta contra o endereço, certo zera a contagem, e
nenhum dos dois quando não veio credencial (não é tentativa) ou o token venceu
(ninguém adivinha token).
Não há fase depois do módulo. Log, métricas, rastreio e cache registram, na passagem
de antes do módulo, um aviso no pedido (TRALRequest.AddFinishHandler) ou na resposta
(TRALResponse.AddWireHandler, que roda uma vez no início de TakeWireStream/
TakeWireString, antes de o corpo sair). O pedido guarda o que o fim precisa:
StartTick (posto em ValidateRequest), ResponseStatus e BytesSent (postos ao
entregar a resposta ao motor; -1 numa resposta em fluxo) e TraceId.
Três restrições moldaram o código:
-
Os parâmetros do pedido somem antes do fim.
ProcessCommandslimpaARequest.Paramsao terminar. Cabeçalhos, agente, usuário e corpo que o log quer, e a chave, o TTL e oIf-None-Matchque o cache quer, são copiados na passagem de antes (o log num retrato por pedido; o cache num objeto por pedido cujo aviso de resposta faz o trabalho e cujo aviso de fim o libera). -
O aviso não pode ser método do plugin. Um formulário libera os componentes em
qualquer ordem, e um pedido pode terminar depois de o plugin ter ido embora. O aviso
é método de um objeto com contagem de referência (
TRALLogSink,TRALMetricsRegistry,TRALTraceCollector): o plugin e cada pedido em curso seguram uma referência, e o destrutor do plugin só desliga o objeto (não escreve mais) e solta a sua. -
Uma resposta abaixo de 400 em
ppValidatenão encerra o pedido. Só >= 400 encerra ali; por isso o redirecionamento para HTTPS responde emppProcesscomAHandled := True, e o cache também éppProcess.
O corpo de texto de uma resposta não é um parâmetro ral_body: quem lê a resposta
pronta usa AResponse.Params.SingleBody.
- O texto de
AllowHeadersé montado quando a lista muda e publicado como snapshot (TRALSnapshots): um pedido que lê enquanto a lista muda nunca fica com um texto sendo liberado. Os itens são juntados por vírgula como estão: oDelimitedTextporia entre aspas um item com espaço ou aspas, e o cabeçalho sairia inutilizável. - Os nomes e valores fixos dos cabeçalhos são globais preenchidas no
initialization: no Delphi, um literal atribuído a um param vira uma cópia nova a cada pedido.
- Sob carga, os pedidos disputam a CPU e o gerenciador de memória: passado um ponto, cada pedido a mais deixa todos mais lentos. Ter um limite importa mais que o valor dele (4, 8 e 16 deram quase o mesmo).
- O pedido pega a vaga antes de decodificar o corpo (
ppValidate, depois de todo plugin que recusa ali, para um pedido recusado nunca esperar) e devolve quando a resposta está pronta (codificada, comprimida, cifrada) e antes de o motor enviar: um download grande para um cliente lento não segura vaga. Esse momento éTRALRequest.Finish, que todo motor chama depois doTakeWireStream; o destrutor do pedido chama se uma exceção pulou. - O portão (
TRALConcurrencyGate) fica separado do plugin e é contado por referência: um pedido segura a vaga por mais tempo do que o plugin tem certeza de viver (um formulário libera os componentes em qualquer ordem). O plugin tem uma referência, cada vaga e cada pedido na fila outra; a última libera. - Uma rota que chama o próprio servidor espera vaga segurando uma: com todas as vagas presas assim, nada libera. Por isso
MaxQueueWaitnão é infinito por padrão.
- A poda das listas (
PruneIdle) tira as entradas ociosas numa só tomada da trava da lista, com os objetos: andar a lista pegando a trava por elemento correria contra outra thread (índice já removido, ou a entrada viva que escorregou para ele). - Força bruta: só um segredo aceito zera a contagem. Zerar a caminho de qualquer rota que respondesse deixaria um adivinhador alternar senha errada com uma rota aberta e nunca chegar a
MaxTry. O veredito do autenticador fica como veio: um 403 continua 403. - A verificação e a inserção na lista ficam sob a mesma trava: duas threads que não acham nada e inserem perdem uma contagem, e a tentativa que passaria de
MaxTrynão passa. A expiração conta da última tentativa errada. - Flood: um endereço visto pela primeira vez não tem intervalo para medir. Uma recusa de flood é taxa, não palpite: 403 e
OnClientBlock, mas não conta para a força bruta. Entrada ociosa por um minuto (ou um múltiplo folgado do intervalo) sai da lista: guardá-la faria de uma varredura de origens aleatórias um vazamento.
- Os eventos de token são
of object:OnGetTokendecide quem recebe token eOnValidatese um ainda vale, e os dois precisam chegar a um banco, isto é, ao objeto que tem a conexão. Para procedimento simples há as variantes...Gen. - A rota de token, nesta ordem: credenciais (ou nenhum token) vão para
OnGetToken, mesmo com um Bearer (senão o próximo usuário de uma máquina compartilhada renovaria o token do anterior); um Bearer sozinho se renova comOnRenewToken, e recusado é 401 e nada mais (não tenta uma senha ao lado de um token morto, e não conta como login errado: um navegador com token vencido manda um a cada renovação; comUseCookieele é mandado apagar o cookie); sem nenhum dos dois, 401: semOnGetTokenninguém confere quem pede. - O cookie: o cabeçalho
Cookietraz todos os cookies do site, em qualquer ordem; só oraltokené o token. O motor separa os cookies em paramsrpkCOOKIE; o cabeçalho cru é o recurso de quem o manteve inteiro. - Cliente: num 401, o token só é descartado se for o mesmo com que o pedido foi recusado (comparado sob a trava): um autenticador serve muitos clientes, e os 401 dos pedidos em voo chegam depois de um deles já ter buscado o próximo token.
-
SetTokennão é atribuição: divide o token, decodifica e reescreveFPayload(um objeto). Tudo sobLock, e nada é publicado antes de se saber que é bom: limparFTokenantes faria cada outra thread verIsAuthenticated = Falsee buscar um token próprio.
- O servidor não guarda estado por nonce: o nonce é o próprio carimbo de tempo mais um HMAC dele com um segredo do plugin. Todo nonce emitido é conferido recalculando, e um velho ainda é reconhecido como do servidor: é isso que deixa responder
stale=true(credenciais certas, nonce velho) e o cliente renovar sem contar falha. O único estado é o cache de repetição opcional: o últimoncvisto por nonce, podado quando os nonces vencem. - O cliente lê o desafio do 401 do próprio pedido (
HandleChallenge), sem pedido extra para saber o nonce, e conta oncsob a trava do autenticador (um autenticador serve vários clientes). -
qop=auth-int(hash do corpo) não é oferecido: o servidor precisaria do corpo como veio do fio, antes de decodificar, e cada motor o entrega num ponto diferente.
-
TRALServerOAuth2são duas coisas, cada uma desligável: o servidor de autorização (Issuing: endpoint de token comclient_credentials,refresh_tokeneauthorization_codecom PKCE S256 sempre exigido, introspecção RFC 7662, revogação RFC 7009, JWK set e metadados RFC 8414; os tokens são JWT, HS* comSignSecretKey, RS*/ES* comPrivateKeypela OpenSSL) e o servidor de recursos (Validate: os próprios tokens, os de outro emissor pelo JWK set ou opacos por introspecção, e o escopo de cada rota). - Clientes, segredos, usuários e consentimento são da aplicação, por eventos: o plugin nunca decide quem alguém é. Códigos, refresh tokens e ids revogados ficam em
Store(memória por padrão). - Sem o grant
password(RFC 9700 e OAuth 2.1 o tiram; o/gettokendo JWT cobre o caso), sem fluxo implícito, DPoP ou mTLS.
- Ligado a um servidor, olha o certificado que o motor recebeu ao ligar: de uma CA, deixa e não vigia; autoassinado válido, usa e vigia; autoassinado vencido ou a vencer em
RenewBeforeDays, renova (chave e certificado novos com os mesmos nomes, gravados onde estava o antigo); nenhum, faz o próprio emFoldere aponta o motor para ele. - O certificado novo chega ao motor rodando por
TRALServer.SetTLSCertificate, trocado sob o listener: as conexões abertas ficam com o antigo, os próximos handshakes pegam o novo. O Sagui é a exceção: a libsagui só aceita certificado ao começar a escutar, e reabre o listener. - A chave fica sem senha em
Folder, comoopenssl req -nodes: quem protege é a pasta. O padrão fica no perfil do usuário, não ao lado do executável, onde um módulo web que serve arquivos a exporia. Tudo é Pascal (RALRSA,RALX509): nenhuma biblioteca para fazer o certificado, só a que cada motor já carrega para servi-lo.
Começar
Servidor
- Rotas e parâmetros
- Rotas avançadas: tipos, grupos, versões 🚧
- Ler o pedido e responder
- Objetos no corpo 🚧
- Resposta em fluxo 🚧
- Plugins 🚧
- Autenticação
- Banco de dados (DBWare)
- Servir site e arquivos
- Swagger e Postman
- Servidor SOAP 🚧
- WS-Security 🚧
- HTTPS
- Criptografia ponto a ponto
- Segurança para produção
- Colocar no ar
- Desempenho
- Testes em memória 🚧
MQTT 🚧
- O que é e quando usar 🚧
- Cliente MQTT 🚧
- Broker MQTT 🚧
- MQTT-SN 🚧
- Ponte REST 🚧
- Segurança 🚧
- Rede ruim 🚧
Cliente
Motores
Avançado
Ajuda
- Problemas comuns
- Atualizar o Indy
- Várias versões do Delphi
- Vídeos e guias
- Módulos de terceiros
- Licenças
- Contribuir
- Roadmap e changelog
🚧 = em desenvolvimento, pode mudar