Particionando o Resultado no ASP.NET Web API


Via de regra, quando fazemos a requisição para algum recurso, ele retorna todo o conteúdo para cliente que solicitou. Enquanto estamos considerando simples porções de dados, não há muito problema em sempre retornar toda a informação sempre que solicitada. O problema pode ocorrer quando estamos retornando uma quantidade considerável de informação em resposta a requisição solicitada. Se durante a transmissão desta grande quantidade de informação a conexão, por algum motivo, for abortada, toda a informação se perde e será necessário recomeçar.

O protocolo HTTP contempla uma funcionalidade chamada de byte serving, que é um processo que faz o particionamento do resultado, enviando uma pequena porção de informação para o cliente, que por sua vez, pode controlar exatamente o que está sendo recebido, e com isso, poderá solicitar ao serviço a parte que ele ainda não tenha, evitando recomeçar do zero. Para formalizar esse tipo de comunicação, o protocolo HTTP faz uso de três headers: Range (requisições), Accept-Ranges e Content-Range (respostas).

Para exemplificar, vamos considerar que queremos fazer uma requisição para uma API que retorna o conteúdo de um arquivo texto, que possui a seguinte informação: 11223344556677889900. Quando o cliente realizar a requisição, ele deverá informar através do header Range a quantidade (em bytes) que ele quer que seja retornado. Ao atender a requisição, o serviço deverá ser capaz de capturar o arquivo em questão e particionar o conteúdo, para servir ao cliente somente o trecho que ele está solicitando. O serviço deverá responder com o status 206 (Partial Content), incluindo o header Content-Range, onde teremos o intervalo (dos dados) que está sendo disponibilizado e também a quantidade total que o conteúdo (o arquivo) possui. Vale lembrar que a propriedade Content-Length sempre refletirá a quantidade de dados que está sendo devolvido no corpo da mensagem e nunca o tamanho total do recurso (do arquivo) que está sendo utilizado.

O header Range deve definir a unidade e o intervalo que será utilizada para calcular a porção de informação que será devolvida. Note nos logs abaixo que está a requisição e sua respectiva resposta. A requisição sempre define o intervalo, e a resposta ratifica o intervalo extraído, contemplando também o tamanho total, para que o cliente possa criar o mecanismo de extração enquanto não recepcionar o conteúdo na íntegra, podendo inclusive criar mecanismos para pausar o download. O que também chama atenção é na última requisição, que apenas colocamos 13- para identificar que queremos o intervalo entre o byte 13 até o final do conteúdo.

—————————————————–

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=0-7

HTTP/1.1 206 Partial Content
Content-Length: 8
Content-Type: text/txt
Content-Range: bytes 0-7/23

11223

—————————————————–

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=8-12

HTTP/1.1 206 Partial Content
Content-Length: 5
Content-Type: text/txt
Content-Range: bytes 8-12/23

34455

—————————————————–

GET http://localhost:4918/api/Files/Download HTTP/1.1
Host: localhost:4918
Range: bytes=13-

HTTP/1.1 206 Partial Content
Content-Length: 10
Content-Type: text/txt
Content-Range: bytes 13-22/23

6677889900

—————————————————–

A implementação por parte da API não é tão complexa. Basta recorrermos à classe ByteRangeStreamContent, que faz toda a mágica para particionar e devolver somente o que foi solicitado. Obviamente que esta classe recebe como parâmetro um Stream contendo o conteúdo (que pode ser uma imagem, um arquivo texto, etc.) e o intervalo solicitado que pode (e deve) ser extraído da requisição, mas não há necessidade de realizar qualquer cálculo manual, pois de posse do header Range, ela será capaz de realizar todo o procedimento.

public class FilesController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Download()
    {
        return new HttpResponseMessage(HttpStatusCode.PartialContent)
        {
            Content = new ByteRangeStreamContent(
                new FileStream(“Dados.txt”, FileMode.Open, FileAccess.Read),
                    this.Request.Headers.Range,
                    “text/txt”)
        };
    }
}

Publicidade

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s