Novembro de 2015

Volume 30, Número 12

ASP.NET - Usar o ASP.NET como uma Ferramenta de Download de Arquivos de Alto Desempenho

Por Doug Duerner

Conexões lentas e defeituosas são, há muito tempo, o grande problema de download de arquivos grandes. Você pode estar no saguão do aeroporto obtendo mídia por meio de uma conexão WiFi ruim para trabalhar em uma apresentação durante um voo longo, ou na savana africana tentando baixar um grande arquivo de instalação por meio de um link de satélite para uma bomba de água movida a energia solar. Em ambas as instâncias, o custo de ter uma falha no download de um arquivo grande é o mesmo: tempo perdido, produtividade desperdiçada e o comprometimento do sucesso da tarefa.

Não é necessário que seja assim. Neste artigo, mostramos como criar um utilitário para resolver o problema de retomada e continuação de downloads com falhas causadas por conexões ruins que são propensas a ficar offline durante a transferência de arquivos grandes.

Segundo plano

Queríamos criar um utilitário de download de arquivos simples que poderia ser adicionado facilmente ao Servidor Web do IIS existente, com um programa cliente extremamente simples e fácil de usar (ou a opção de, simplesmente, usar o navegador da Web como um cliente).

O Servidor Web do IIS já foi comprovado como um servidor da Web de nível corporativo e altamente escalonável, atendendo de arquivos a navegadores por anos. Basicamente, queríamos desfrutar da capacidade do Servidor Web do IIS de tratar diversas solicitações da Web HTTP simultaneamente, em paralelo, e aplicar isso ao download de arquivo (cópia).

Precisávamos de um utilitário de download de arquivo que poderia baixar arquivos grandes para usuários no mundo inteiro que, às vezes, estão em locais remotos com links de rede lentos e, normalmente, defeituosos. Com a possibilidade de alguns usuários remotos pelo mundo continuarem usando links de modem ou de satélite defeituosos, que podem ficar offline a qualquer momento ou funcionar de modo intermitente, alternando entre online e offline, o utilitário precisaria ser extremamente resiliente com a capacidade de tentar baixar novamente somente os trechos do arquivo que falharam durante o download. Não queríamos que um usuário passasse a noite inteira baixando um arquivo enorme por meio de um link lento e, se houvesse um pequeno contratempo no link de rede, que ele tivesse que iniciar todo o processo de download novamente. Também queríamos garantir que esses arquivos enormes sendo baixados não fossem armazenados em buffer na memória do servidor e que o uso de memória do servidor fosse mínimo, para que o uso de memória não continuasse subindo até ocorrer uma falha no servidor quando diversos usuários baixassem os arquivos simultaneamente.

Por outro lado, se o usuário tivesse sorte de ter um link de rede confiável e de alta velocidade – com computadores cliente e servidor sendo de alta qualidade, equipados com diversas CPUs e placas de rede – queríamos que ele pudesse baixar um arquivo usando diversos threads e conexões, permitindo o download de várias partes do arquivo ao mesmo tempo, usando, em paralelo, todos os recursos de hardware ao usar o mínimo da memória do servidor.

Para resumir, criamos um utilitário de download de arquivo com baixo uso da memória, simples, multi-threaded e paralelo, que pode dividir o arquivo em partes, baixar essas partes individuais em threads separados e permitir ao usuário tentar baixar novamente apenas as partes com falha no download.

O projeto de exemplo que acompanha este artigo contém o código do utilitário de download de arquivo e fornece uma infraestrutura de base rudimentar que pode ser expandida no futuro, permitindo a você obter opções mais sofisticadas conforme a necessidade.

Visão geral do projeto de exemplo

Na essência, DownloadHandler.dll transforma um Servidor Web do IIS em uma ferramenta de download de arquivo multi-threaded, que permite a você baixar partes de um arquivo, em paralelo, usando uma URL simples por meio do cliente executável autônomo (FileDownloader.exe), como mostrado na Figura 1. Observe que o parâmetro (chunksize=5242880) é opcional e se não for incluído, tornará o download padrão do arquivo inteiro em uma parte. A Figura 2 e a Figura 3 demonstram como ele permite a você tentar, repetidamente, baixar somente as partes do arquivo com falha, até que obtenha êxito, sem ter que começar o download do começo, como a maioria dos outros softwares de download de arquivo.

Visão Geral do Design de Alto Nível do Fluxo de Processamento para DownloadHandler.dll
Figura 1 - Visão Geral do Design de Alto Nível do Fluxo de Processamento para DownloadHandler.dll (Usando FileDownloader.exe como Cliente)

Executável Autônomo como Cliente de Download
Figura 2 - Executável Autônomo como Cliente de Download (com Partes com Falha)

Executável Autônomo como Cliente de Download
Figura 3 - Executável Autônomo como Cliente de Download (Após a Nova Tentativa)

A Figura 1 é uma visão geral de alto nível do design de DownloadHandler.dll e FileDownloader.exe, mostrando o fluxo de processamento conforme as partes do arquivo do disco rígido do computador servidor passam pelo DownloadHandler.dll e FileDownloader.exe para o arquivo no disco rígido do computador cliente, ilustrando os cabeçalhos de protocolo HTTP envolvidos neste processo.

Na Figura 1, FileDownloader.exe inicia um download de arquivo ao chamar o servidor usando uma URL simples, que contém o nome do arquivo que você deseja baixar como um parâmetro de cadeia de consulta de URL (file=file.txt) e usa, internamente, o método HTTP (HEAD); portanto, inicializar o servidor devolverá somente seus cabeçalhos de resposta, um dos quais contém o tamanho do arquivo total. Então, o cliente usa um constructo Parallel.ForEach para iterar, dividindo o tamanho do arquivo total em partes (intervalo de bytes) com base no tamanho da parte no parâmetro (chunksize=5242880). Para cada iteração individual, o constructo Parallel.ForEach executa um método de processamento em um thread separado, aprovando no intervalo de bytes associado. Dentro do método de processamento, o cliente emite uma chamada HttpWebRequest para o servidor usando a mesma URL e, internamente, acrescenta um cabeçalho de solicitação HTTP contendo o intervalo de bytes fornecido para este método de processamento (ou seja, Intervalo: bytes=0-5242880, Intervalo: bytes=5242880-10485760 e assim por diante).

No computador servidor, nossa implementação da interface IHttpAsync­Handler (System.Web.IHttpAsyncHandler) trata de cada solicitação em um thread separado, executando o método HttpResponse.Transmit­File para gravar o intervalo de bytes solicitado pelo arquivo do computador servidor diretamente no fluxo de rede – sem buffer explícito – para que o impacto da memória do servidor seja quase inexistente. O servidor devolve sua resposta com um Código de Status HTTP 206 (PartialContent) e acrescenta, internamente, o cabeçalho de resposta HTTP, identificando o intervalo de bytes sendo retornado (ou seja, Conteúdo-Intervalo: bytes 0-5242880/26214400, Conteúdo-Intervalo: bytes 5242880-10485760/26214400 e assim por diante). Conforme cada thread recebe a resposta HTTP no computador cliente, ele grava os bytes retornados na resposta na parte correspondente do arquivo do disco rígido do computador cliente identificado no cabeçalho de resposta HTTP (Conteúdo-Intervalo). Ele usa um arquivo sobreposto assíncrono de E/S (para garantir que o Gerenciador de E/S do Windows não serialize as solicitações de E/S antes de expedir os Pacotes de Solicitação de E/S para o driver de modo kernel para concluir a operação de gravação do arquivo). Se diversos threads de modo de usuário fizerem uma gravação de arquivo e você não tiver o arquivo aberto para a E/S sobreposta assíncrona, as solicitações serão serializadas e o driver de modo kernel receberá somente uma solicitação por vez. Para obter mais informações sobre E/S sobreposta assíncrona, consulte “Getting Your Driver to Handle More Than One I/O Request at a Time” (bit.ly/1NIaqxP) e “Supporting Asynchronous I/O” (bit.ly/1NIaKMW) no site do Centro de Desenvolvimento de Hardware.

Para implementar a assincronicidade em nosso IHttpAsyncHandler, postamos manualmente uma estrutura de E/S sobreposta na porta de conclusão de E/S, e o CLR ThreadPool executa o delegado de conclusão fornecido na estrutura sobreposta em um thread de porta de conclusão. Estes são os mesmos threads de porta de conclusão usados pela maioria dos métodos assíncronos internos. Geralmente, é melhor usar os novos métodos assíncronos internos para a maioria do trabalho associado à E/S, mas, neste caso, queremos usar a função HttpResponse.TransmitFile devido à sua capacidade incrível de transferir arquivos grandes sem fazer buffer explícito na memória do servidor. É surpreendente!

Parallel.ForEach é, principalmente, para trabalho associado à CPU e nunca deve ser usado na implementação do servidor devido à sua natureza de bloqueio. Descarregamos o trabalho em um thread de porta de conclusão por meio do CLR ThreadPool, em vez de um thread de trabalho regular por meio do CLR ThreadPool, a fim de evitar o enfraquecimento dos mesmos threads usados pelo IIS para atender às solicitações recebidas. Além disso, a maneira mais eficiente na qual a porta de conclusão funciona limita, de certa forma, o consumo de thread no servidor. Há um diagrama com uma explicação mais detalhada listado no código do projeto de exemplo na seção de comentários, na parte superior da classe IOThread, destacando as diferenças entre threads de porta de conclusão e threads de trabalho no CLR ThreadPool. Como a colocação em escala para milhões de usuários não é a meta principal deste utilitário, não podemos arcar com o custo de threads de servidor adicionais exigidos para executar a função HttpResponse.TransmitFile para alcançar as economias de memória associadas no servidor durante a transferência de arquivos em massa. Basicamente, estamos trocando a perda de escalabilidade causada pelo uso de threads adicionais no servidor (em vez dos métodos assíncronos internos sem threads) para usar a função HttpResponse.TransmitFile que consome, extraordinariamente, o mínimo de memória do servidor. Apesar de estar fora do escopo deste artigo, você pode optar por usar os métodos assíncronos internos juntamente com a E/S do arquivo com o buffer eliminado para alcançar uma economia de memória similar sem threads adicionais, mas conforme entendemos, tudo deve estar alinhado no setor e é fazer a implementação adequada. Além disso, parece que a Microsoft removeu, propositalmente, o item NoBuffering da enum FileOptions para evitar, de fato, que uma E/S do arquivo com buffer seja eliminada, exigindo um hack manual para possibilitar tal ação. Estávamos nervosos em relação aos riscos associados à implementação incorreta e decidimos escolher a opção menos arriscada de HttpResponse.TransmitFile, que foi testada por completo.

FileDownloader.exe pode inicializar diversos threads, cada um emitindo uma chamada HttpWebRequest separada correspondente a um trecho separado (intervalo de bytes) do arquivo sendo baixado com base no tamanho total do arquivo divido em “Bytes de Partes” especificado, como mostrado na Figura 2.

Qualquer thread que falhar no download da parte do arquivo (intervalo de bytes) especificado em sua chamada HttpWebRequest poderá tentar novamente ao fazer a mesma chamada HttpWebRequest (somente para aquele intervalo de bytes com falha) repetidamente até que, eventualmente, obtenha êxito, como mostrado na Figura 3. Você não perderá partes do arquivo já baixadas, o que, em caso de uma conexão lenta, pode significar horas de tempo de download economizadas. Você pode praticamente eliminar o impacto negativo de uma conexão defeituosa que fica offline continuamente. E, com os diversos threads de design baixando partes diferentes do arquivo simultaneamente em paralelo – diretamente para o fluxo de rede sem buffer explícito – e para o disco rígido com E/S de arquivo sobreposto assíncrono, é possível maximizar a quantidade de download realizada durante a janela de tempo quando uma conexão não confiável está, na verdade, online. A ferramenta continuará concluindo as partes restantes cada vez que o link de rede voltar a ficar online, sem perder nenhum trabalho. Gostamos de pensar que se trata mais de uma ferramenta de download de arquivo “de novas tentativas” e não uma “retomável”.

A diferença pode ser ilustrada em um exemplo hipotético. Você baixará um arquivo grande que levará a noite inteira. Você inicia uma ferramenta de download de arquivo retomável quando sai do trabalho e a deixa em execução. Ao chegar para trabalhar no dia seguinte, vê que o download de arquivo falhou em 10% e está pronto para ser retomado. Mas, ao ser retomado, ele precisará ser executado novamente durante a noite para concluir os 90% restantes.

Ao invés disso, você inicia nossa ferramenta de download de arquivo que pode ter novas tentativas ao sair do trabalho e a deixa executando a noite inteira. Ao chegar ao trabalho pela manhã, você vê que o download de arquivou falhou em uma parte em 10%, mas continuou o download das outras partes do arquivo. Tudo que você precisará fazer é repetir apenas uma vez aquela parte que não foi baixada. Depois de encontrar determinada parte com falha, por meio de um contratempo momentâneo no link de rede, ele prosseguiu e terminou os 90% restantes durante a noite, quando o link de rede voltou a ficar online.

O cliente de download padrão desenvolvido no navegador da Web também pode ser usado como um cliente de download, usando uma URL, como https://localhost/DownloadPortal/Download?file=test.txt&chunksize=5242880.

Observe que o parâmetro (chunksize=5242880) também é opcional ao usar o navegador da Web como um cliente de download. Se não estiver incluído, o servidor baixará todo o arquivo em uma parte, usando o mesmo HttpResponse.TransmitFile. Se estiver incluído, executará uma chamada HttpResponse.TransmitFile separada para cada parte.

A Figura 4 é uma visão geral de alto nível do design de DownloadHandler.dll ao usar um navegador da Web que não dá suporte a conteúdo parcial como um cliente de download. Ela ilustra o fluxo de processamento conforme as partes do arquivo no disco rígido do computador servidor passam pelo DownloadHandler.dll e pelo navegador da Web para o arquivo no disco rígido do computador do navegador da Web.

Visão Geral do Design de Alto Nível do Fluxo de Processamento para DownloadHandler.dll
Figure 4 - Visão Geral do Design de Alto Nível do Fluxo de Processamento para DownloadHandler.dll (Usando Navegador da Web que Não Dá Suporte ao Conteúdo Parcial Como Cliente)

Um recurso legal da nossa implementação da interface IHttpAsyncHandler no Servidor Web do IIS é o suporte ao “atendimento de byte”, ao enviar o cabeçalho HTTP Aceitar-Intervalos na resposta HTTP (Aceitar-Intervalos: bytes), informando os clientes que ele atenderá partes de um arquivo (intervalo de conteúdo parcial). Se o cliente de download padrão dentro do navegador da Web der suporte ao conteúdo parcial, ele poderá enviar ao servidor o cabeçalho HTTP Intervalo em sua solicitação HTTP (Intervalo: bytes=5242880-10485760), e quando o servidor enviar o conteúdo parcial de volta ao cliente, reenviará o cabeçalho HTTP Conteúdo-Intervalo dentro de sua resposta HTTP (Conteúdo-Intervalo: bytes 5242880-10485760/26214400). Portanto, dependendo do navegador da Web que você usar e o cliente de download padrão desenvolvido naquele navegador, você poderá obter alguns dos mesmos benefícios de nosso cliente executável autônomo. Independentemente disso, a maioria dos navegadores da Web permitirá a você compilar seu próprio cliente de download personalizado, que poderá ser conectado ao navegador, substituindo o padrão interno.

Configuração do Projeto de Exemplo

Para o projeto de exemplo, basta copiar DownloadHandler.dll e IOThreads.dll no diretório \bin no diretório virtual e colocar uma entrada na seção de manipuladores e de módulos de web.config, da seguinte maneira:

<handlers>
  <add name="Download" verb="*" path="Download"
    type="DownloaderHandlers.DownloadHandler" />
</handlers>
<modules>
  <add name="CustomBasicAuthenticationModule" preCondition="managedHandler"
    type="DownloaderHandlers.CustomBasicAuthenticationModule" />
</modules>

Se não houver nenhum diretório virtual no Servidor do IIS, crie um com diretório \bin, faça dele um Aplicativo e verifique se está usando um Pool de Aplicativos do Microsoft .NET Framework 4.

O módulo de autenticação básica personalizado usa o mesmo AspNetSqlMembershipProvider fácil de usar que em diversos sites ASP.NET atualmente, armazenando o nome de usuário e a senha necessários para baixar um arquivo dentro de um banco de dados aspnetdb no SQL Server. Um dos benefícios úteis do AspNetSqlMembershipProvider é que o usuário não precisa ter uma conta no domínio Windows. Instruções detalhadas sobre como instalar o AspNetSqlMembershipProvider e as configurações necessárias no Servidor do IIS para configurar as contas de usuário e o certificado SSL estão listadas no código do projeto de exemplo na seção de comentários na parte superior da classe CustomBasicAuthentication­Module. As outras opções de configuração avançadas usadas para ajustar o Servidor do IIS geralmente já foram definidas pelo departamento de TI que gerencia o servidor e estão além do escopo deste artigo, mas se esse não for o caso, elas estão disponíveis para consulta imediata na Biblioteca do TechNet em bit.ly/1JRJjNS.

Você terminou. Simples assim.

Fatores atraentes

O fator atraente mais importante do design é que além de ser mais rápido, é mais resiliente e tolerante a falhas de queda de rede causadas por links de rede instáveis e inseguros, continuamente ficando online e offline. Normalmente, baixar um arquivo em uma parte e em uma conexão, renderá a produtividade máxima.

Há algumas exceções exclusivas a essa regra, como um ambiente de servidor espelhado em que um arquivo é baixado em pedaços diferentes, obtendo cada pedaço do arquivo de um servidor espelho diferente, como mostrado na Figura 5. Mas, em termos gerais, baixar um arquivo em diversos threads é mais lento do que baixar um arquivo em um thread, pois a rede costuma ser o gargalo. Entretanto, conseguir tentar novamente somente as partes com falha do download de arquivo repetidamente até obter êxito, sem ter que iniciar todo o processo de download, molda aquilo que chamamos de uma espécie de tolerância a quase falhas.

Aprimoramentos Futuros Hipotéticos para Simular uma Infraestrutura Espelho Extremamente Rudimentar
Figura 5 - Aprimoramentos Futuros Hipotéticos para Simular uma Infraestrutura Espelho Extremamente Rudimentar

Além disso, se alguém precisar modificar o design como um aprimoramento futuro para simular uma infraestrutura de servidor espelho extremamente rudimentar, como mostrado na Figura 5, ele poderá formar o que seria chamado de uma espécie de quase redundância.

Basicamente, o design permite a você baixar um arquivo de modo confiável por meio de uma rede não confiável. Um breve contratempo no seu link de rede não significa que você precisa começar desde o início, é possível simplesmente repetir a operação somente para as partes do arquivo com falha. Uma boa adição ao design (que o tornaria ainda mais resiliente) seria armazenar o estado do progresso atual do download em um arquivo no disco rígido durante o andamento do download, assim, você poderia repetir um download com falha mesmo em diversos aplicativos cliente e reinicializações do computador cliente. Mas esse será um exercício deixado para o leitor.

Outro fator atraente, rival do item citado em destaque, se encaixa no uso de HttpResponse.TransmitFile no servidor para gravar os bytes do arquivo diretamente no fluxo de rede – sem buffer explícito – a fim de minimizar o impacto na memória do servidor. É surpreendente o quão insignificante é o impacto na memória do servidor, mesmo ao baixar arquivos extremamente grandes.

Há três fatores adicionais que são muito menos significativos, mas que não deixam de ser atraentes.

Primeiro, como o design inclui o cliente de front-end e o servidor de back-end, você tem controle total sobre as configurações no servidor. Isso dá a liberdade e a força necessárias para ajustar as definições de configuração que costumam impedir bastante o processo de download do arquivo em servidores de propriedade de outra pessoa e que estão fora do seu controle. Por exemplo, você pode ajustar a restrição de limite de conexão imposta pelo endereço IP do cliente para um valor maior que o limite normal de duas conexões. Também é possível ajustar a limitação por conexão do cliente para um valor maior.

Segundo, o código do projeto de exemplo dentro do cliente de front-end (FileDownloader.exe) e do nosso servidor de back-end (DownloadHandler.dll) pode funcionar como blocos simples e claros do código de exemplo, demonstrando o uso da solicitação HTTP e os cabeçalhos de resposta necessários para facilitar intervalos de bytes de conteúdo parcial no protocolo HTTP. É fácil perceber quais cabeçalhos de solicitação HTTP o cliente deve enviar para solicitar os intervalos de bytes e quais cabeçalhos de resposta HTTP o servidor deve enviar para retornar os intervalos de bytes como conteúdo parcial. Deve ser relativamente fácil modificar o código para implementar uma funcionalidade de nível superior sobre esta funcionalidade base simples, ou implementar algumas funcionalidades mais avançadas disponíveis em pacotes de software mais sofisticados. Além disso, você pode usá-lo como um simples modelo de início que torna relativamente fácil a adição de suporte para alguns dos outros cabeçalhos HTTP mais avançados, como Content-Type: multipart/byteranges,Content-MD5: md5-digest, If-Match: entity-tag e assim por diante.

Terceiro, como o design usa o Servidor Web do IIS, você automaticamente se beneficia de algumas das funcionalidades internas fornecidas pelo servidor. Por exemplo, a comunicação pode ser criptografada automaticamente (usando HTTPS com um certificado SSL) e compactada (usando compactação gzip). Entretanto, pode não ser recomendado executar a compactação gzip em arquivos extremamente grandes se esta ação resultar em muito estresse nas CPUs do servidor. Mas, caso as CPUs do servidor possam receber carga adicional, a eficiência de transferir dados compactados muito menores pode, às vezes, fazer uma grande diferença na produtividade geral de todo o sistema.

Futuros Aprimoramentos

O código do projeto de exemplo somente fornece as funcionalidades básicas mínimas necessárias para que a ferramenta de download de arquivo opere. Nossa meta era manter o design simples e fácil de ser entendido para poder ser usado, relativamente sem esforços, como uma base para adicionar aprimoramentos e funcionalidades extras. Ele é meramente um ponto inicial e um modelo base. Muitos aprimoramentos adicionais seriam essenciais antes de poderem ser usados em um ambiente de produção. A adição de uma camada de abstração de alto nível que fornece esta funcionalidade adicional mais avançada é deixada como um exercício para o leitor. Entretanto, vamos abordar diversos dos aprimoramentos mais importantes.

O código de projeto de exemplo atualmente não inclui uma soma de verificação de hash MD5 no arquivo. No mundo real, é essencial empregar algum tipo de estratégia de soma de verificação do arquivo para garantir que o arquivo baixado para o cliente corresponda ao arquivo no servidor, e que isso não seja manipulado ou alterado de outra forma. Os cabeçalhos HTTP simplificam esse processo com o cabeçalho (Content-MD5: md5-digest). Na verdade, um de nossos primeiros protótipos incluía a execução de uma soma de verificação de hash MD5 no arquivo toda vez que o arquivo era solicitado e a colocação do resumo no cabeçalho (Content-MD5: md5-digest) era feita antes do arquivo deixado no servidor. Então, o cliente executaria a mesma soma de verificação de hash MD5 no arquivo recebido e verificaria o resumo resultante correspondente ao resumo no cabeçalho (Content-MD5: md5-digest) retornado pelo servidor. Se eles não fossem correspondentes, o arquivo estaria manipulado ou corrompido. Apesar de alcançar a meta de garantir que o arquivo não seja alterado, arquivos grandes causaram pressão intensa na CPU no servidor e demoraram muito para serem executados.

Na verdade, ele provavelmente precisará de algum tipo de camada de cache que executa o processamento da soma de verificação de hash MD5 no arquivo (no segundo plano) uma vez para a duração do arquivo, e armazena o resumo resultante no dicionário com o nome do arquivo como chave. Então, uma simples pesquisa no dicionário é o necessário no servidor para obter o resumo do arquivo, que poderá ser adicionado ao cabeçalho enquanto o arquivo sai do servidor, de modo rápido, com impacto mínimo nas CPUs do servidor.

Atualmente, o código do projeto de exemplo também não restringe um cliente de usar um grande número de threads e dividir o arquivo em várias partes. Basicamente, ele permite que um cliente faz o que for preciso para garantir que ele consiga baixar um arquivo. No mundo real, provavelmente seria necessário algum tipo de infraestrutura que impusesse um limite no cliente, para que um cliente não fosse capaz de comprometer o servidor e sentir falta de todos os outros clientes.

A Figura 5 ilustra um aprimoramento futuro hipotético para simular uma infraestrutura espelho extremamente rudimentar ao modificar o design para fornecer uma lista de pares de “nome do nó/intervalo de bytes”, como o parâmetro da cadeia de caracteres de consulta de URL, em vez do atual parâmetro “chunksize” do design. O design atual poderia ser modificado de modo relativamente fácil para obter cada parte do arquivo por meio de um servidor diferente ao, simplesmente, iterar os pares de “nome do nó/intervalo de bytes”, iniciando um HttpWebRequest para cada par, em vez de iterar internamente para dividir o tamanho do arquivo total em partes, com base no parâmetro “chunksize” e iniciando um HttpWebRequest para cada parte.

Você poderia fazer o constructo da URL para HttpWebRequest ao substituir o nome do servidor pelo nome do nó associado por meio da lista de pares de “nome do nó/intervalo de bytes”, adicionando o intervalo de bytes associado ao cabeçalho HTTP Intervalo (ou seja, Intervalo: bytes=0-5242880) e, em seguida, removendo a lista de “nome do nó/intervalo de bytes” por completo da URL. Algum tipo de arquivo de metadados poderia identificar em quais servidores as partes de um arquivo estão localizadas e, então, o computador solicitante poderia montar o arquivo por meio dos seus pedaços espalhados pelos diferentes servidores.

Se um arquivo estiver espelhado em 10 servidores, o design poderia ser modificado para obter o pedaço 1 do arquivo da cópia espelhada do servidor 1, o pedaço 2 do arquivo da cópia espelhada do servidor 2, o pedaço 3 do arquivo da cópia espelhada do servidor 3 e assim por diante. Novamente, seria essencial fazer uma soma de verificação de hash MD5 no arquivo depois de recuperar todos os seus pedaços e remontar o arquivo completo no cliente, a fim de garantir que nenhuma parte tenha sido corrompida em nenhum servidor espelho e que você recebeu, de fato, o arquivo completo. Você poderia, inclusive, subir de nível e ter os servidores distribuídos geograficamente pelo país, compilando uma inteligência elaborada no código que determinaria quais servidores estão na última carga de processamento, usando, então, esses servidores para atender à solicitação devolvendo as partes do arquivo.

Conclusão

A meta do nosso design não era criar uma ferramenta de download de arquivo mais escalonável e rápida, mas criar uma extremamente resiliente a falhas momentâneas de rede.

Fizemos um grande esforço para garantir que o design era extremamente simples e que demonstrava com clareza como usar os cabeçalhos de protocolo HTTP para os intervalos de bytes de “atendimento de byte” e conteúdo parcial.

Em nossa pesquisa, foi difícil encontrar um exemplo bom e claro de como fazer um atendimento de byte HTTP simples e como usar, de modo adequado, cabeçalhos de intervalo de bytes no protocolo HTTP. Quase todos os exemplos eram desnecessariamente complexos ou usavam muitos dos outros cabeçalhos para implementar recursos muito mais avançados no protocolo HTTP, dificultando o entendimento e, principalmente, a tentativa de aprimorar ou expandir futuramente.

Queríamos fornecer uma base sólida e simples, que inclui somente o mínimo necessário, tornando relativamente fácil a experimentação e a adição incremental de funcionalidades mais avançadas com o tempo – ou até mesmo implementar uma camada de abstração de nível superior completa que adiciona alguns dos recursos mais avançados do protocolo HTTP.

Queríamos, simplesmente, fornecer um exemplo direto com o qual seria possível aprender e se desenvolver com o passar do tempo. Aproveite!


Doug Duerner é engenheiro sênior de software com mais de 15 anos projetando e implementando sistemas de grande porte com tecnologias da Microsoft. Ele trabalhou para diversas instituições bancárias de empresas da Fortune 500 e em uma empresa de software comercial que projetou e criou o sistema de gerenciamento de rede distribuída em grande escala usado pelo DISA (Departamento de Defesa da Defense Information Systems Agency) para sua "Grade de informações globais" e o Departamento de Estado. Ele é um geek de coração, concentrando-se em todos os aspectos, mas aproveita as dificuldades técnicas mais complexas e desafiadoras, especialmente aquelas que todos dizem "não podem ser feito." Duerner pode ser contatado pelo coding.innovation@gmail.com.

Yeon-Chang Wang é engenheiro sênior de software com mais de 15 anos projetando e implementando sistemas de grande porte com tecnologias da Microsoft. Ele também trabalhou para diversas instituições bancárias de empresas da Fortune 500 e em uma empresa de software comercial que projetou e criou o sistema de gerenciamento de rede distribuída em grande escala usado pelo Departamento de Defesa da Defense Information Systems Agency (DISA) para sua "Grade de informações globais" e o Departamento de Estado (DoS). Ele também projetou e implementou um Sistema de Certificação de Driver em larga escala para um dos maiores fabricantes de chips do mundo. Wang possui mestrado em Ciência da Computação. Ele consome problemas complexos para o jantar e pode ser contatado pelo yeon_wang@yahoo.com.

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Stephen Cleary e James McCaffrey