Streaming de Conteúdo
Esta página foi traduzida automaticamente do inglês. Ler o original
O Sisk suporta a leitura e o envio de fluxos de conteúdo para e do cliente. Essa funcionalidade é útil para remover a sobrecarga de memória para serializar e deserializar conteúdo durante a vida útil de uma solicitação.
Fluxo de conteúdo da solicitação #
Pequenos conteúdos são carregados automaticamente no buffer de memória da conexão HTTP, carregando rapidamente esse conteúdo para HttpRequest.Body e HttpRequest.RawBody. Para conteúdos maiores, o método HttpRequest.GetRequestStream pode ser usado para obter o fluxo de leitura do conteúdo da solicitação.
Vale notar que o método HttpRequest.GetMultipartFormContent lê todo o conteúdo da solicitação na memória, portanto, pode não ser útil para ler conteúdos grandes.
Considere o seguinte exemplo:
[RoutePost ( "/api/upload-document/<filename>" )]
public async Task<HttpResponse> UploadDocument ( HttpRequest request ) {
var fileName = request.RouteParameters [ "filename" ].GetString ();
if (!request.HasContents) {
// solicitação não tem conteúdo
return new HttpResponse ( HttpStatusInformation.BadRequest );
}
var contentStream = request.GetRequestStream ();
var outputFileName = Path.Combine (
AppDomain.CurrentDomain.BaseDirectory,
"uploads",
fileName );
using (var fs = File.Create ( outputFileName )) {
await contentStream.CopyToAsync ( fs );
}
return new HttpResponse () {
Content = JsonContent.Create ( new { message = "Arquivo enviado com sucesso." } )
};
}No exemplo acima, o método UploadDocument lê o conteúdo da solicitação e salva o conteúdo em um arquivo. Nenhuma alocação adicional de memória é feita, exceto pelo buffer de leitura usado por Stream.CopyToAsync. O exemplo acima remove a pressão de alocação de memória para um arquivo muito grande, o que pode otimizar o desempenho da aplicação.
Uma boa prática é sempre usar um CancellationToken em uma operação que possa ser demorada, como enviar arquivos, pois depende da velocidade da rede entre o cliente e o servidor.
O ajuste com um CancellationToken pode ser feito da seguinte forma:
// o token de cancelamento abaixo irá lançar uma exceção se o tempo limite de 30 segundos for atingido.
CancellationTokenSource copyCancellation = new CancellationTokenSource ( delay: TimeSpan.FromSeconds ( 30 ) );
try {
using (var fs = File.Create ( outputFileName )) {
await contentStream.CopyToAsync ( fs, copyCancellation.Token );
}
}
catch (OperationCanceledException) {
return new HttpResponse ( HttpStatusInformation.BadRequest ) {
Content = JsonContent.Create ( new { Error = "O upload excedeu o tempo máximo de upload (30 segundos)." } )
};
}Fluxo de conteúdo da resposta #
Enviar conteúdo de resposta também é possível. Atualmente, existem duas maneiras de fazer isso: através do método HttpRequest.GetResponseStream e usando um conteúdo do tipo StreamContent.
Considere um cenário em que precisamos servir um arquivo de imagem. Para fazer isso, podemos usar o seguinte código:
[RouteGet ( "/api/profile-picture" )]
public async Task<HttpResponse> UploadDocument ( HttpRequest request ) {
// método de exemplo para obter uma imagem de perfil
var profilePictureFilename = "profile-picture.jpg";
byte[] profilePicture = await File.ReadAllBytesAsync ( profilePictureFilename );
return new HttpResponse () {
Content = new ByteArrayContent ( profilePicture ),
Headers = new () {
ContentType = "image/jpeg",
ContentDisposition = $"inline; filename={profilePictureFilename}"
}
};
}O método acima faz uma alocação de memória a cada vez que lê o conteúdo da imagem. Se a imagem for grande, isso pode causar um problema de desempenho e, em situações de pico, até mesmo uma sobrecarga de memória e travar o servidor. Nesses casos, o cache pode ser útil, mas não eliminará o problema, pois a memória ainda será reservada para esse arquivo. O cache aliviará a pressão de ter que alocar memória para cada solicitação, mas para arquivos grandes, não será suficiente.
Enviar a imagem por meio de um fluxo pode ser uma solução para o problema. Em vez de ler todo o conteúdo da imagem, um fluxo de leitura é criado no arquivo e copiado para o cliente usando um buffer pequeno.
Enviando através do método GetResponseStream #
O método HttpRequest.GetResponseStream cria um objeto que permite enviar pedaços da resposta HTTP à medida que o fluxo de conteúdo é preparado. Esse método é mais manual, exigindo que você defina o status, cabeçalhos e tamanho do conteúdo antes de enviar o conteúdo.
[RouteGet ( "/api/profile-picture" )]
public async Task<HttpResponse> UploadDocument ( HttpRequest request ) {
var profilePictureFilename = "profile-picture.jpg";
// nessa forma de envio, o status e o cabeçalho devem ser definidos
// antes de enviar o conteúdo
var requestStreamManager = request.GetResponseStream ();
requestStreamManager.SetStatus ( System.Net.HttpStatusCode.OK );
requestStreamManager.SetHeader ( HttpKnownHeaderNames.ContentType, "image/jpeg" );
requestStreamManager.SetHeader ( HttpKnownHeaderNames.ContentDisposition, $"inline; filename={profilePictureFilename}" );
using (var fs = File.OpenRead ( profilePictureFilename )) {
// nessa forma de envio, também é necessário definir o tamanho do conteúdo
// antes de enviá-lo.
requestStreamManager.SetContentLength ( fs.Length );
// se você não souber o tamanho do conteúdo, pode usar o chunked-encoding
// para enviar o conteúdo
requestStreamManager.SendChunked = true;
// e então, escrever no fluxo de saída
await fs.CopyToAsync ( requestStreamManager.ResponseStream );
}
}Enviando conteúdo através de um StreamContent #
A classe StreamContent permite enviar conteúdo de uma fonte de dados como um fluxo de bytes. Essa forma de envio é mais fácil, removendo os requisitos anteriores e até mesmo permitindo o uso de codificação de compressão para reduzir o tamanho do conteúdo.
[RouteGet ( "/api/profile-picture" )]
public HttpResponse UploadDocument ( HttpRequest request ) {
var profilePictureFilename = "profile-picture.jpg";
return new HttpResponse () {
Content = new StreamContent ( File.OpenRead ( profilePictureFilename ) ),
Headers = new () {
ContentType = "image/jpeg",
ContentDisposition = $"inline; filename=\"{profilePictureFilename}\""
}
};
}Importante
Nesse tipo de conteúdo, não encapsule o fluxo em um bloco using. O conteúdo será automaticamente descartado pelo servidor HTTP quando o fluxo de conteúdo for finalizado, com ou sem erros.