Skip to content
mobius1qwe edited this page Oct 15, 2026 · 2 revisions

Internos: Núcleo 🚧

Home > Guia de contribuição > Internos: Núcleo

Para quem mexe no código: servidor, cliente, pedido, resposta, parâmetros, rotas e módulos. 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).

Tipos que viajam (RALTypes)

  • Valor novo de enum entra sempre no fim (amUNKNOWN em TRALMethod, rteCancelled em TRALTransportError, sftBCD em TRALFieldType). Assim nenhum ordinal gravado num .dfm ou enviado por outra versão muda de sentido, e o else de CanSwitchURL já recusa reenviar o que não conhece.
  • Quem decide o reenvio é TRALTransportError, não StatusCode: sem resposta HTTP não existe status. rteConnect garante que o pedido não chegou ao servidor, então qualquer método pode ir para o próximo BaseURL. rteTimeout quer dizer que ele pode ter rodado lá, então só método idempotente (RALIdempotentMethods) vai para outro.
  • TRALHTTPVersion é um pedido: o RAL não implementa HTTP/2, quem o faz é a plataforma sob o motor (WinHTTP no Windows, OkHttp no Android). O que o ALPN negociou volta em ProtocolVersion. Pedir rhv2 a um motor sem SupportsHTTP2 levanta no primeiro pedido em vez de cair para 1.1 calado. rhv10 só existe do lado observado: o servidor recebe 1.0, e nenhum motor consegue pedir.
  • TRALParamType é dito, não deduzido do valor: o Pascal promove literais (2 cabe em Integer, Int64 e Double, e Currency não se distingue de Double). O texto depende do locale das duas pontas (2,5 lido com . vira 0; 03/04 é março ou abril); o valor binário não.

Constantes (RALConsts)

  • default de propriedade e construtor andam juntos: os timeouts do cliente usam a mesma constante na diretiva default e no construtor (DEFAULTCONNECTTIMEOUT, DEFAULTREQUESTTIMEOUT). O default diz ao streaming para não gravar a propriedade no .dfm; se diverge do construtor, digitar exatamente o valor do default no Object Inspector gera um componente que roda com o valor do construtor. Ao criar um default numérico, confira o construtor.
  • Arquivos de idioma com BOM: os ralconsts_*.inc são UTF-8 com BOM. Sem BOM o Delphi lê o include na página ANSI do sistema e todo acento chega codificado duas vezes; com BOM o FPC só aceita o include sob {$CODEPAGE UTF8}. O RALConsts.pas em si fica ASCII, ou a diretiva muda como os literais dele são lidos.
  • Tempos que parecem iguais e não são:
Constante O que mede Por que é separada
RALENGINEIDLETIMEOUT (5 min) motor parado no pool do cliente abaixo do que um servidor costuma manter uma conexão keep-alive
RALTHREADREQUESTTIMEOUT (30 min) Request de uma thread que sumiu uma thread que pôs um header uma vez e chama a cada dez minutos não pode perder o Request
DEFAULTWEBSESSIONTIMEOUT (30 min) sessão do WebModule TRALServer.SessionTimeout é outra coisa: o tempo que o mORMot2 segura uma conexão ociosa (30 s); com 30 min ali, um cliente ocioso prenderia uma thread do smThreads
RALQUICIDLETIMEOUT conexão QUIC ociosa o QUIC negocia o menor dos dois lados, então cliente e servidor partem do mesmo valor
  • RALMAXIDLEENGINES é também o teto de conexões que um cliente mantém abertas sem nada em voo, onde cada motor guarda a sua.
  • DEFAULTSERVERPORT vale também para um BaseURL QUIC sem porta: abaixo do HTTP não há convenção 80/443.

Parâmetros (RALParams)

  • Valor em texto ou em stream: TRALParam guarda texto em FText e stream em FContent (corpo do motor, arquivo aberto, valor tipado); FIsText diz qual vale. Um header como stream custaria um objeto no heap além do param, e é a quantidade de alocações por pedido que limita o servidor: com várias threads, elas fazem fila no gerenciador de memória.
  • Strings compartilhadas (gTextPlain, gRalBody...): o Delphi copia um literal para o heap a cada atribuição a campo ou resultado; entregar uma global só conta referência. O FPC não copia literal.
  • Índice por nome: todo param lido do fio é buscado primeiro (query, form, headers, cookies), e uma busca linear daria N*N/2 comparações antes de qualquer autenticação. O índice nasce com o primeiro param e dobra ao encher; cada cadeia fica em ordem de criação, então o primeiro param de um nome continua sendo o achado. O hash é FNV-1a com $20 em cada byte, para nomes que RALSameName considera iguais caírem juntos.
  • Bloco de headers guardado como texto (FPending): o motor entrega todo header que o cliente mandou, e a maioria nunca é lida. A busca por nome responde das linhas e cria só aquele param, no lugar que teria; qualquer outra coisa analisa o bloco inteiro antes (FParams). Um bloco não é adiado com Set-Cookie, mais de 32 linhas ou um nome já na lista (a análise teria mesclado nele).
  • Tipados escrevem o conteúdo e depois o tipo: escrever conteúdo derruba o marcador tipado (senão um texto de 8 bytes sobre um rctRALDOUBLE seria lido como double), então quem restaura um param tipado atribui o stream e só depois o ContentType. Os escritores tipados são métodos próprios, não AsInteger/AsDouble, para o fio não mudar para quem já usa; os leitores olham o tipo primeiro e caem no texto. Com um param só, o tipo viaja no Content-Type HTTP, ao qual o SetContentType acrescenta ; charset=utf-8: por isso MediaType corta os parâmetros e os brancos. No FPC big-endian os bytes são trocados nas duas pontas.
  • Separador dos headers decidido pelo dado: TStrings.NameValueSeparator é um Char que vale '=' e nunca é vazio, e o TIdHeaderList do Indy declara ': ' numa propriedade própria, invisível pela referência TStrings. Indy e mORMot2 mandam Nome: Valor, o fpHTTP manda nome=valor (TRequest.CustomHeaders). FindHeaderNameSeparator usa o que vier primeiro na linha entre ': ' e '='. Decidir pelo motor erra dos dois jeitos: o '=' corta um header do Indy no = do valor, e o ':' não acha nada no fpHTTP.
  • Linhas cortadas por índice: montar uma linha caractere a caractere realoca a string a cada um, e cada alocação pega o lock do gerenciador de memória - sob carga, isso sozinho domina o pedido. Os headers da resposta são medidos e escritos no tamanho exato, e um bloco ASCII não passa por UTF-16.
  • AppendParamsText varre uma vez, por ponteiro: um segmento vazio (?&a=1) tem de avançar, ou o laço gira para sempre, e apagar cada segmento do início do texto seria quadrático.
  • Header não é URL-encoded: decodificar trocaria todo + por espaço (o base64 de um Authorization: Basic, application/ld+json).
  • Pointer(FText)^, não FText[1]: indexar uma string compartilhada para um argumento sem tipo faz o Delphi copiar a string inteira antes.
  • OpenFile abre com fmShareDenyNone: fmShareDenyWrite seguraria uma nova versão do arquivo até a resposta sair. O arquivo vai do disco como TRALFileStream.
  • Corpos que o servidor analisa sozinho: o Indy e o libmicrohttpd (Sagui) analisam x-www-form-urlencoded e multipart antes de qualquer camada do RAL. Por isso, no pedido, form e multipart vão sem compressão (gzip lido como form perde todos os campos); form cifrado vai como multipart, e multipart cifrado é declarado octet-stream (o DecodeBody acha o boundary na primeira linha do texto claro). Parte de corpo sem nome de arquivo leva o próprio nome (o libmicrohttpd descarta partes sem nome), mas parte de campo fica sem, ou PHP, Spring e Go a tratam como upload. Só um rpkBODY sozinho é o corpo inteiro: um campo sozinho vai como nome=valor, para um servidor de terceiros achar o campo. Pouco se perde sem compressão no pedido: o volume está na resposta.
  • Um corpo decodificado, lido onde está: o buffer do motor é lido no lugar, a cifra trabalha nele quando pode escrever, e os params são janelas sobre um só corpo. Corpo que começa com delimitador de multipart nunca foi comprimido, diga o header o que disser (dois TRALParams no mesmo processo nascem com gzip).

Pedido e resposta (RALRequest, RALResponse)

  • ClientInfo.ConnectionID é a conexão, não o cliente: vários pedidos podem dividir uma (keep-alive no HTTP/1.1, multiplexação no HTTP/2 e no QUIC); contar valores distintos contra o número de pedidos é o que mostra se houve multiplexação. Só é único e comparável dentro de um servidor rodando: cada motor entrega o que tem (http.sys o endereço e a porta do par, porque o ConnectionId dele é por stream no HTTP/2 e o RawConnectionId não vem certo em todo Windows; os modos socket do mORMot2 um contador próprio; os outros o objeto ou o handle da conexão com a porta do par acima, porque o sistema entrega o handle de uma conexão fechada à seguinte). Nunca grave, mande ao cliente ou compare entre servidores. Zero é "o motor não sabe" (CGI, UniGUI) e quem conta deve pular.
  • A credencial é decodificada só quando pedida: um Bearer é analisado uma vez, e o autenticador usa o mesmo resultado.
  • AddFinishHandler/Finish: o que um plugin pega para um pedido e precisa devolver (a vaga do TRALConcurrencyPlugin) volta no Finish, que todo motor chama logo depois de pegar o corpo da resposta (TakeWireStream): mandar os bytes fica fora do limite. O destrutor do pedido é a rede de segurança para uma exceção antes do Finish. Os dois primeiros handlers ficam no próprio pedido, sem alocação.
  • HttpVersion é o esquema (HTTP/HTTPS), não a versão; a versão é Protocol/ProtocolVersion.
  • Cookies da resposta: todo motor monta os seus Set-Cookie de GetParamsCookies, então é ali que CR e LF saem. Cookie simples sai com Path=/ (sem Path o navegador o guarda só para a pasta da URL que o criou) e com Expires em data HTTP, que não depende do locale.
  • ResponseText/ResponseStream no servidor são o corpo decodificado; motor que precisa do corpo do fio usa TakeWireStream.

Rotas (RALRoutes)

  • 404 ou 405: CanAnswerRoute acha a rota pelo caminho; entre rotas do mesmo caminho vence a que aceita o método, e quando nenhuma aceita, a rota do caminho responde e o módulo dela devolve 405. Filtrar por método ali faria todo verbo errado virar 404. AllowedMethodsOf junta o que as rotas do caminho aceitam, para o Allow do 405 (RFC 9110 15.5.6), já que várias rotas podem dividir um caminho com um verbo cada.
  • Custo por pedido: cada rota guarda o caminho completo e os segmentos (UpdateSegments, chamado quando a rota, a coleção ou o Domain do módulo mudam), e o texto do Allow, que o CORS pede a cada pedido. O caminho do pedido é dividido uma vez só.
  • Find[] procura pelo Name da rota, não pelo caminho.

Servidor (RALServer)

  • Os dois laços: ProcessCommands acha a rota uma vez (FindRoute, guardada no pedido) antes do OnRequest, roda os plugins ppProcess por prioridade até um responder e depois os módulos até um ser dono da rota; ninguém: 404. O primeiro módulo é o interno (TRALBaseModule): rotas do próprio servidor e a página de status em /. Ele nunca aparece em SubModule[] e só entra no laço quando o servidor tem rota ou mostra o status.
  • Corpo que o motor não desmontou (multipart sem parte) é 400 antes de qualquer rota: a rota receberia os params vazios, o upload perdido sem aviso.
  • Exceção no handler: o 500 com a mensagem vem primeiro, para nunca sobrar o 200 com corpo vazio com que a resposta nasce; depois OnServerError, ou a exceção sobe com RaiseError.
  • HideErrorDetails: a mensagem de um driver cita tabelas, colunas, SQL e às vezes a string de conexão. ErrorText é a regra única que todo motor, o TRALDBModule e o DAO usam para o texto de um 500.
  • Active lido do form: é a primeira propriedade publicada, então aplicá-lo na hora subiria o motor antes de Port, IPConfig e SSL serem lidos (um servidor salvo com SSL ouviria em HTTP puro). WriteActive só guarda, Loaded aplica. Só um True é guardado, porque os setters que reiniciam o servidor (SetPort, PoolCount, Mode) gravam Active False e True enquanto as próprias propriedades são lidas.
  • Ciclo de vida: SetActive avisa os plugins primeiro (eles preparam o que o motor abre, o certificado por exemplo) e depois os módulos; parando, módulos e depois plugins. Os motores chamam inherited SetActive antes de abrir a porta, então "ativando" quer dizer pedido, não escuta. Um servidor liberado ativo nunca passa por SetActive(False): BeforeDestruction para os plugins antes do inherited, que marca o componente como destruindo (e um host destruindo não avisa ninguém).
  • IPv6Enabled num motor sem IPv6 é recusado antes de o servidor ser parado, para a recusa não derrubar um servidor no ar.
  • Método desconhecido é 501 (RFC 9110 9.1), em ValidateRequest, depois dos plugins ppValidate: o flood conta o pedido como qualquer outro.
  • SessionTimeout é quanto o mORMot2 mantém uma conexão keep-alive ociosa (0 desliga ali); o Indy o passa à própria lista de sessões, que o RAL deixa desligada, e o fpHTTP ao período em que o laço de accept acorda ocioso. Não é a sessão do WebModule.
  • DecodeAuthValue usa RALTrim e Pos em StringRAL: no Delphi, Trim e Pos(' ') passam por UTF-16, com alocações a cada pedido que traz credencial.
  • Certificado em DER (TRALTLSCertificate): existe também para que o motor que carrega o certificado por conexão nunca leia um arquivo pela metade numa renovação. A troca a quente e o que cada motor faz estão em Certificado automático.

Módulos (TRALModuleRoutes)

O que um módulo pode mudar, do mais leve ao mais fundo: rotas no construtor; BeforeExecute/AfterExecute; RouteClass (a rota guarda dados ou um handler tipado); RequestClass/ResponseClass com ExecuteContext; HandleException; ServerActivating/ServerDeactivating. O guia de quem escreve um é Criar um módulo.

  • CreateRoute/NewRoute passam pela coleção (Routes.Add), porque TRALRoute.Create criaria sempre uma TRALRoute simples, qualquer que fosse o RouteClass.
  • O pedido e a resposta do módulo são invólucros, não subclasses: o motor cria os objetos do núcleo antes de a rota ser conhecida, e os plugins escrevem neles primeiro. TRALModuleRequest/TRALModuleResponse guardam o objeto do núcleo (Core) e escrevem nele, então o corpo nunca é copiado por causa deles. Rota com handler do núcleo roda direto; o contexto só é montado para as outras.
  • overload num Answer do descendente soma às sobrecargas herdadas, no Delphi e no FPC; sem a diretiva, as esconde.
  • Ciclo de vida: ServerActivating levantando mantém o servidor parado; ServerDeactivating pode correr com pedidos ainda rodando, e a exceção vai para OnServerError - parar sempre termina. Nada dispara em design, carga ou destruição (CanNotify); um módulo lido do form com o servidor ativo ouve no próprio Loaded.
  • Não publique Routes num módulo que cria as rotas no construtor: o form as guardaria, e a carga as recriaria sem handler.

Cliente (RALClient)

Motores, conexões e threads

  • Pool de motores (PoolConnection): um motor tem uma conexão e só atende um pedido por vez. Com o pool, a thread pega um emprestado e devolve, e o pool se acomoda na concorrência real do cliente; o motor emprestado sai do pool, então duas threads nunca dividem uma conexão. Sem o pool (Enabled = False) fica um motor para a primeira thread e um motor novo por pedido para as outras: socket e handshake TLS a cada pedido. Medido com 20 processos, um cliente de três threads cada, contra mORMot2 em TLS: 448 pedidos/s sem o pool, 3816 com ele.
  • A chave do pool (TargetKey, esquema + host + porta) prefere um motor já apontado para o destino; sem ele, reaproveita outro (o motor percebe e reconecta) em vez de crescer o pool. IdleTimeout é conferido ao pegar e ao devolver, sem timer.
  • Pool não é ShareConnection: este divide o transporte entre motores (netHTTP, OkHttp, MsQuic); o pool reaproveita o objeto motor, que é do que Indy, mORMot2 e fpHTTP dependem. Os dois ligados convivem. MaxIdle limita conexões só onde cada motor tem a sua; com transporte compartilhado, limita memória.
  • TRALThreadClient pega o motor do pool só com o pool ligado: o construtor roda na thread que chama, e com o pool desligado AcquireEngine entregaria a ele o motor guardado para ela. Devolve o motor em Execute, não no destrutor: o destrutor roda depois do OnTerminate, que libera o chamador para o próximo pedido, e o pool não teria o motor a tempo.
  • Um Request por thread: o uso "preenche e chama" não se protege com lock, porque o chamador segura o objeto entre instruções. Cada thread tem o seu, e a que criou o cliente fica com o original. A primeira leitura de outra thread copia o do criador, para "preenche na principal, chama no worker" mandar o que foi preenchido; o criador não trava enquanto preenche, então quem lê só deve começar depois. Threads são distinguidas por ThreadToken, nunca pelo id do sistema, que a próxima thread herda.
  • AdvanceIndexUrl só move o índice de failover quando ele ainda está onde o pedido começou: dois avanços simultâneos viram um (no máximo uma tentativa falha a mais), mas o índice nunca volta para um servidor já morto.

Cada tentativa (BeforeSendUrl)

  • Uma tentativa por BaseURL, sem piso: com um piso, uma URL só receberia o mesmo pedido várias vezes, e um POST com timeout seria gravado mais de uma vez.
  • Recusas antes do socket (TLS exigido, pin sem suporte, rhv2 sem suporte, rhv10), nunca dentro do callback TLS, que roda na pilha do OpenSSL.
  • Tentativa não é chamada: o failover e a repetição do 401 são tentativas novas, com Attempt maior; quem quer a chamada ignora Attempt > 1. OnBeforeExecute roda antes de qualquer rede, inclusive a busca do token (quem recusa por falta de rede não paga o token); AInfo é const porque reescrever a URL ali passaria por cima do pin já decidido. OnAfterExecute sempre faz par, inclusive na recusa, levantada dentro do try. ErrorMessage vem do ExceptObject só quando algo está desenrolando: numa saída normal ele pode ser a exceção de um handler externo.
  • O lock é do autenticador, não do cliente: um autenticador serve muitos clientes (o DAO faz um por dataset), e enquanto um busca o token os outros esperam e depois o acham pronto.
  • SetTransportError põe ErrorCode -1 quando o motor passa 0, porque é o ErrorCode que o resto lê para saber que falhou, e StatusCode 0, o único valor que todo motor pode dar quando não houve resposta.
  • Quem cria a resposta e levanta, a libera: ExecuteSingle libera a resposta quando levanta, e Get/Post/... zeram AResponse antes, porque a atribuição no chamador não roda e um finally dele liberaria o que a variável tinha.

Política de certificado

  • AcceptServerCert é o único lugar que julga: o evento decide, senão o pin, senão o motor. Os motores só traduzem o callback para TRALCertInfo (mesmos campos em todo motor, compilador e plataforma; o que o motor não sabe vem vazio). Host/Port vêm da URL escolhida pelo RAL, não do certificado: é isso que deixa um handler servir vários servidores.
  • SSL.Pins: = separa o lugar do hash porque os dois lados têm : (AB:CD:..., [::1]:8443); RALSplitHostPort é o único parser dos dois. Uma linha que não é SHA-256 levanta ao entrar na lista, não no primeiro pedido, onde pareceria troca de certificado. O formato e as receitas estão em HTTPS no cliente.
  • svEngine é o padrão porque ligar a verificação quebraria HTTPS no Windows para Indy e fpHTTP, cujo OpenSSL não tem repositório de certificados lá. CertCheckWanted só a liga quando um pin ou o evento pedem.
  • CertPolicyKey: uma conexão TLS foi julgada uma vez, no handshake, por quem a abriu; outro cliente que a reusa não faz handshake e herdaria um veredito que não deu. Por isso só divide conexão quem julga igual. Fica na base porque todos os motores que dividem precisam da mesma resposta.
  • LeavesTLS/TLSRequired: os motores seguem redirect sozinhos, e um 301 para http:// tiraria a chamada do TLS por trás do SSL.Required e do pin.

Propriedades que dependem do motor

  • Supports* e MinKeepAliveInterval são métodos de classe porque a IDE pergunta a um motor só escolhido no Object Inspector, sem instância.
  • IsPropertyRelevant esconde ShareConnection onde o motor não divide conexão (um motor de uma conexão por objeto serializaria as chamadas se dividisse) e HTTPVersion onde não há escolha. SetEngineType fixa HTTPVersion em 1.1 ou rhvDefault, porque um rhv2 deixado por outro motor faria todo pedido levantar emHTTP2Unsupported.
  • KeepAliveInterval: no HTTP/2 a conexão é longa e compartilhada, e um par que some (Wi-Fi caindo, troca de ponto de acesso, servidor reiniciando) não deixa rastro; o TCP não avisa e a chamada só falharia no RequestTimeout. O OkHttp manda PING a cada intervalo e derruba quando o pong não volta no mesmo intervalo, sem mínimo. O netHTTP usa WINHTTP_OPTION_HTTP2_KEEPALIVE, que começa depois do intervalo de inatividade, recusa menos de 5000 ms e só existe no Windows 11 (sem ele o pedido vai em HTTP/2 sem ping). O piso é aplicado na atribuição, para o valor lido ser o valor em vigor. QUIC multiplexa sem pedir HTTP/2, por isso não exige rhv2.
  • MaxRedirects mora no cliente porque cada motor tinha um limite que ninguém escolheu (Indy 3, mORMot2 3, fpHTTP 255, netHTTP o do THTTPClient).
  • AcceptEncoding é o que o cliente lê; CompressType é o que ele envia. Um Accept-Encoding posto no Request vence os dois.
  • Jar de cookies (CookieJar): é do TRALClient, não do motor. Os motores que o cliente empresta veem os cookies uns dos outros, e dois clientes nunca, nem dividindo transporte. O motor usa o tipo de jar da sua biblioteca (regras de domínio, caminho e validade), mas não deixa a biblioteca escrever o Cookie.

Registro na IDE (RALRegister)

  • Propriedade que não se aplica some do Object Inspector, e quem decide é o próprio componente (TRALComponent.IsPropertyRelevant): a unit de registro nunca distingue um motor do outro, e motor nenhum depende da IDE. É conforto, não regra: o valor escondido fica e é ignorado; escolha impossível nunca se esconde, levanta exceção dizendo por quê. O Delphi filtra por uma segunda interface no selection editor (ISelectionPropertyFilter), o Lazarus por método virtual mais atributo. Com seleção mista, só some o que nenhum dos selecionados acha relevante.
  • O mesmo um nível abaixo (TRALNestedProperty): dentro de uma propriedade objeto (o SSL), o componente é perguntado pelo nome composto ('SSL.CertificateFile'), então um só IsPropertyRelevant responde pelos dois níveis. É registrado pelo tipo base, e vale para o SSL de todo motor sem uma linha de design-time em cada um.
  • Classe guardada morre com o pacote dela: toda unit que registra uma classe numa lista do RAL a tira no finalization (motor: UnregisterEngine; driver de banco: UnregisterDatabase; storage: UnregisterStorageLink). A IDE descarrega e recarrega um pacote quando o recompila, ou quando recompila um de que ele depende, e o PascalRAL.bpl fica carregado: a lista apontaria para a classe liberada, e o próximo EngineType := ou IsPropertyRelevant seria um access violation. Cada unit tira só a própria entrada, e registrar o mesmo nome de novo troca a classe, então um motor de terceiros sem finalization volta a funcionar quando o pacote dele recarrega. O GetClass não tem esse problema: a RTL tira as classes de um módulo quando ele descarrega.

XML e SOAP (modules/xml, modules/soap)

  • Uma pasta por submódulo, um pacote cada. RALModuleXMLPackage (leitor, escritor, DOM, storage) e RALModuleSOAPPackage (módulo, exportador de WSDL, cliente, ação). O pacote principal não conhece nenhuma unit nova: ganhou só pontos de extensão (TRALModuleRoutes.DefaultContentType, prtXML em TRALRouteParamType, TRALClientClass para o Clone de um descendente, e os virtuais AllowNative/NativeContentType/PrepareForDataSet do TRALStorageLink). O nome do pacote Lazarus não pode ser o de uma unit dele: ralxml.lpk gera a unit ralxml.pas, que esconde a RALXML de quem a usa.
  • O leitor é pull e trabalha em bytes UTF-8. Toda entrada é convertida para UTF-8 uma vez; os nomes vão para uma tabela de nomes por documento, para que comparar nomes seja comparar ponteiros na maioria das vezes. O DOM libera e serializa sem recursão: um documento muito fundo não estoura a pilha.
  • O módulo SOAP escolhe a operação antes da autenticação. O roteamento acontece no CanAnswerRoute, que os plugins chamam para saber de quem é a rota: ali só se lê o começo do envelope (versão, primeiro elemento do Body) com o leitor pull, e o que se leu fica no RouteData do pedido. O envelope inteiro só é montado quando o tratador pede (Document). As duas rotas do próprio endpoint (o WSDL no GET, o "operação não encontrada" no POST) ficam numa coleção interna, fora de Operations, para não irem ao .dfm.
  • Toda exceção do módulo vira Fault. HandleException responde o envelope na versão do pedido, com o status da norma; o texto passa por ErrorText, para o HideErrorDetails valer também aqui.
  • O cliente é um TRALClient. Por isso Clone cria a classe do objeto (TRALClientClass(ClassType)) e CopyProperties é virtual: o DAO e as threads clonam o cliente, e um clone que virasse TRALClient perderia as operações. O cliente SOAP nasce com CompressType := ctNone: quase nenhum servidor SOAP lê um pedido comprimido (o WCF responde 400). Uma chamada em thread monta o envelope na thread de quem chamou - os Params são do formulário - e entrega a resposta pelo OnTerminate, na thread principal; o destrutor espera as chamadas pendentes.

Clone this wiki locally