Repository navigation
corpo unico
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.ContentparaTMemoryStream: leia comoTStream; - 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 |
| 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.
| 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.
-
RequestText/RequestStreamno servidor eResponseText/ResponseStreamno 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/ResponseStreamno servidor são o que a rota respondeu, descomprimido. Até a 1.2 eram o corpo já codificado. UmOnResponseque liaResponseTextrecebe o texto, não gzip. - Quem escreveu um motor próprio e enviava
ResponseStreamdeve passar a usarTakeWireStream(veja "Para quem escreve um motor").
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ãoTrue) eSkipContentTypes, uma lista de tipos a mais (application/x-meu, ou um prefixo terminado em/, comomodel/). - No cliente:
TRALClient.SkipCompressedTypes(padrãoTrue) eSkipCompressTypes.
SkipCompressedTypes := False volta ao comportamento da 1.2.
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(unitRALStream). 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.
- Entrada:
SetWireBody(AStream, APosse),SetWireBody(ATexto)ouSetWireBody(APonteiro, ATamanho), comContentEncodingeContentEncriptionjá 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) ouboCopy. - Saída:
TakeWireStream(um stream que o motor libera) ouTakeWireString. LeiaContentTypeeContentEncodingdepois 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.
Começar
Servidor
- Rotas e parâmetros
- Ler o pedido e responder
- Plugins 🚧
- Autenticação
- Banco de dados (DBWare)
- Servir site e arquivos
- Swagger e Postman
- HTTPS
- Criptografia ponto a ponto
- Segurança para produção
- Colocar no ar
- Desempenho
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