Skip to content
mobius1qwe edited this page Oct 7, 2026 · 1 revision

Corpo da requisição e da resposta sem cópias

Home > Corpo da requisição e da resposta sem cópias

Important

Em desenvolvimento (1.3). Ainda não está numa versão lançada e pode mudar.

Nível: avançado

Em resumo, para quem só usa:

  • uploads e respostas grandes gastam muito menos memória, sem mudar o seu código;
  • não converta Body.Content para TMemoryStream: leia como TStream;
  • para corpos de centenas de MB, ligue SpoolAbove (vai para o disco).

O resto da página é para quem precisa do detalhe. Até a 1.2 o corpo era copiado de 2 a 5 vezes entre o motor e a rota: um upload de 100 MB passava por meio gigabyte.

Desde a 1.3 existe um corpo por requisição (no servidor) e um por resposta (no cliente). Os parâmetros leem esse corpo onde ele está, sem copiar, e a compressão e a criptografia só acontecem na hora de enviar, escrevendo direto no que vai para a rede.

Na prática, com um corpo de 8 MB:

situação 1.2 1.3
servidor recebe e a rota lê 3x o corpo além dele mesmo nada além do corpo
servidor responde um texto que a rota já tem 3x nada
servidor responde com gzip 3x 5% (o gzip de saída)
cliente recebe e lê ResponseText 4x 1x (a própria string)
cliente envia 2x nada

Ler o corpo: Content, AsString e AsStream

forma o que é custo
Body.Content o próprio corpo, onde ele está nenhum
Body.AsString o corpo como texto uma cópia (nenhuma quando o motor entregou uma string: mORMot2, fpHTTP, CGI)
Body.AsStream uma cópia que você libera uma cópia

Use Content quando só vai ler:

procedure TForm1.Importa(ARequest: TRALRequest; AResponse: TRALResponse);
var
  vCorpo: TStream;
begin
  vCorpo := ARequest.Body.Content;   // não libere: é do pedido
  MeuLeitorCSV.LoadFromStream(vCorpo);
  AResponse.Answer(HTTP_OK, 'importado', rctTEXTPLAIN);
end;

O corpo recebido é somente leitura: escrever em Body.Content levanta exceção. Quem precisa alterar o corpo pega uma cópia com AsStream.

A classe de Content depende do tamanho, e por isso não converta para TMemoryStream:

  • até 8 MB é memória contínua (uma vista ou um TMemoryStream);
  • acima disso é um TRALChunkedStream, em blocos de 1023 KB;
  • acima de SpoolAbove (desligado por padrão) é um arquivo temporário.

Todas são TStream e se leem igual. Um código que faz TMemoryStream(Body.Content).Memory deixa de funcionar acima de 8 MB.

Num multipart, cada parte (ParamByName('arquivo').Content) é uma janela sobre o corpo que chegou, com as mesmas regras.

Responder sem copiar

forma o que acontece
Answer(status, texto) a resposta guarda a sua string, sem copiar
Answer(status, stream, tipo) copia o stream; você continua dono dele e o libera
Answer(status, stream, tipo, True) a resposta fica com o stream e o libera; não mexa mais nele
AResponse.BodyStream um stream para escrever o corpo direto, que a resposta envia
// um arquivo gerado pela rota: a resposta fica com ele
vPlanilha := TMemoryStream.Create;
GeraPlanilha(vPlanilha);
AResponse.Answer(HTTP_OK, vPlanilha, 'application/vnd.ms-excel', True);

// escrever direto no corpo da resposta
AResponse.ContentType := rctAPPLICATIONJSON;
MinhaQuery.SaveToStream(AResponse.BodyStream);

BodyStream devolve sempre o mesmo stream enquanto ele for o corpo, então várias escritas se acumulam. O tipo do corpo é o ContentType da resposta, mesmo que você o defina depois de escrever.

O que muda para quem lê a resposta ou o pedido inteiro

  • RequestText/RequestStream no servidor e ResponseText/ResponseStream no cliente são o corpo que chegou, já descomprimido e decifrado. Um multipart volta com os bytes que chegaram, não remontado com outra fronteira.
  • ResponseText/ResponseStream no servidor são o que a rota respondeu, descomprimido. Até a 1.2 eram o corpo já codificado. Um OnResponse que lia ResponseText recebe o texto, não gzip.
  • Quem escreveu um motor próprio e enviava ResponseStream deve passar a usar TakeWireStream (veja "Para quem escreve um motor").

Tipos já comprimidos não são comprimidos de novo

Imagens (menos SVG), áudio, vídeo, zip, gzip, 7z, rar, bzip2, xz, zstd, brotli, pdf e fontes woff/woff2 saem sem Content-Encoding, mesmo com gzip pedido. Comprimir de novo gasta processador e um buffer do tamanho do corpo e não reduz nada. Qualquer cliente HTTP lê as duas formas, então nada muda para quem recebe.

  • No servidor: TRALCompressPlugin.SkipCompressedTypes (padrão True) e SkipContentTypes, uma lista de tipos a mais (application/x-meu, ou um prefixo terminado em /, como model/).
  • No cliente: TRALClient.SkipCompressedTypes (padrão True) e SkipCompressTypes.

SkipCompressedTypes := False volta ao comportamento da 1.2.

Corpos grandes em disco: SpoolAbove

TRALLimitsPlugin.SpoolAbove (servidor) e TRALClient.SpoolAbove (cliente), em bytes, desligados por padrão (0). Acima desse tamanho, o corpo que o RAL monta vai para um arquivo temporário, apagado quando o pedido ou a resposta é liberado. A memória deixa de crescer com o corpo: medido, 1 MB de memória para um corpo de 256 MB.

  • A pasta é a temporária do sistema, ou a da variável global RALSpoolFolder (unit RALStream). Precisa de permissão de escrita.
  • Um processo que morre no meio deixa o arquivo para trás; ao ligar o SpoolAbove, o RAL apaga os que têm mais de um dia.
  • No servidor Indy o corpo vai para o disco desde o primeiro byte. Os outros motores (mORMot2, fpHTTP, Sagui, CGI, QUIC) entregam o corpo já inteiro na memória deles: ali o disco guarda o que o RAL monta a partir dele (o corpo descomprimido ou decifrado) e a resposta.
  • No cliente, Indy, fpHTTP e netHTTP recebem direto no disco.

Win32: um processo de 32 bits tem 2 GB de endereços, já fragmentados. Corpos de algumas centenas de MB, em Win32, só com SpoolAbove.

Para quem escreve um motor

  • Entrada: SetWireBody(AStream, APosse), SetWireBody(ATexto) ou SetWireBody(APonteiro, ATamanho), com ContentEncoding e ContentEncription já preenchidos. A posse (TRALBodyOwnership) diz de quem é o buffer: boBorrowed (do motor, que o mantém vivo até o fim do pedido), boBorrowedWritable (idem, e o RAL pode decifrar ali mesmo), boOwned (o RAL fica com ele) ou boCopy.
  • Saída: TakeWireStream (um stream que o motor libera) ou TakeWireString. Leia ContentType e ContentEncoding depois da chamada: eles dizem o que foi feito de verdade.
  • O pedido tem de nascer e morrer dentro do callback que segura o buffer do motor: é isso que torna o empréstimo seguro.

Clone this wiki locally