Skip to content
mobius1qwe edited this page Oct 26, 2026 · 3 revisions

Internos: Motores 🚧

Home > Guia de contribuição > Internos: Motores

Para quem mexe no código: Indy, fpHTTP, CGI, Synopse (mORMot2), Sagui, UniGUI, netHTTP, OkHttp, MsQuic e Kwik. 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).

fpHTTP: fcl-web 3.2 e 3.3

O fcl-web serve uma requisição por conexão e, no 3.2, cria uma thread para cada uma. O RAL põe um pool de threads (TRALfpHttpWorker) no lugar: o laço de accept enfileira a conexão e um trabalhador livre a pega; um trabalhador novo só nasce quando nenhum está livre, então uma rota que trava não segura a próxima conexão.

O gancho muda de versão para versão do fcl-web, e o código testa o que o fcl-web tem, não o número do FPC (o trunk é 3.3.1 há anos, com APIs diferentes):

  • fcl-web 3.2: CreateConnectionThread é sobrescrito, enfileira e devolve nil.
  • fcl-web 3.3 ({$IF DECLARED(TFPHTTPServerConnectionHandler)}): esse método não existe mais; cada conexão vai para um connection handler criado por CreateConnectionHandler. O RAL devolve o seu (TRALfpHttpConnectionHandler), que entrega a conexão ao mesmo pool. ConnectionCount passa a ser a contagem do pool.

O fcl-web 3.3 trouxe limites novos ligados por padrão: no máximo 128 conexões simultâneas (com um 503 escrito cru no socket, ilegível em TLS), 256 cabeçalhos, linha de cabeçalho de 32 KB, URL só ASCII e um tempo máximo de 30 s por leitura. O RAL desliga todos, para que o 3.3 sirva o que o 3.2 servia: o teto de conexões é o MaxConnections do RAL (vira MaxLiveConnectionCount, porque esse fcl-web não chama mais o OnAllowConnect) e os limites de tamanho são os dos plugins. O corpo da requisição, que no 3.3 fica em ContentBytes, é lido sem cópia.

Keep-alive continua desligado - uma requisição por conexão e Connection: close sempre. O fcl-web 3.3 sabe manter a conexão, mas só de dois jeitos: deixando uma thread parada em cada conexão ociosa (o que o pool existe para evitar) ou procurando pedidos pendentes no "tique" do accept ocioso, que exige AcceptIdleTimeout > 0 - a configuração que corria com a limpeza do próprio fcl-web no Windows e derrubava o processo. Ligar também faria o mesmo motor se comportar diferente no 3.2 e no 3.3. Se um dia vier, que venha opcional e com uma verificação de prontidão do próprio RAL.

Resposta em fluxo: o que cada motor faz por baixo 🚧

O núcleo só conhece o canal (TRALResponseStream: trava, enquadramento chunked, compressor opcional) e um escritor por motor (TRALStreamWriter). Motor sem escritor fica em memória e manda tudo no fim - correto, só não é ao vivo.

  • Indy e fpHTTP empurram: o handler escreve direto no socket da conexão, na thread do pedido. No Indy, uma resposta HTTP/1.0 não tem chunked: ContentLength := -2 e a conexão fecha no fim. No fpHTTP o "cliente ainda está aí?" é um select + recv com MSG_PEEK, que pergunta sem consumir nada.
  • Sagui puxa: o libmicrohttpd pede bytes quando quer (sg_httpres_sendstream). Os pedaços vão para uma fila contada por referência, que o callback de leitura esvazia e o handler enche; a fila é de quem a soltar por último (o handler ou o libmicrohttpd). Uma rota Streaming roda numa thread própria e o callback espera só o commit, porque o payload do pedido só vale enquanto o callback não retorna (é copiado). O fim e o erro são -1 e -2 direto: o sg_eor do binding devolvia lixo no Linux. O libmicrohttpd só nota um cliente que saiu quando uma escrita falha, então um fluxo calado e sem SSE (sem ping) não descobre.
  • mORMot2 não tinha como mandar um corpo de tamanho desconhecido: a resposta inteira sai depois que o Request retorna. O RAL acrescentou ao mORMot2 o BeginChunkedResponse/SendChunk/EndChunkedResponse no THttpServer (modo smThreads), genérico, para virar PR no mORMot2. O motor usa sob {$IF DECLARED(THttpServerChunkedState)}: com um mORMot2 sem a mudança ele compila e fica em memória. smAsync e smHttpSys também ficam em memória até o mORMot2 ganhar o equivalente lá.
  • Cliente: o motor escreve o corpo num "ralo" (TRALClientStreamSink) no lugar do stream de sempre; no primeiro pedaço o ralo lê status e tipos (ReadStreamHead) e decide se entrega aos pedaços. O netHTTP não deixa: o THTTPClient só devolve a resposta (e seus cabeçalhos) quando o Execute termina, e sem tipo nem codificação não dá para separar SSE nem descomprimir - por isso entrega no fim.
  • Compressão em fluxo usa janela de 4 KB e memLevel 5 (uns 30 KB por fluxo, contra 256 KB do padrão do zlib) e descarrega (Z_SYNC_FLUSH) a cada Send. O Delphi antes do XE2 não tem deflateInit2/inflateInit2: lá não há compressão nem descompressão aos pedaços.

Clone this wiki locally