A capacidade de controlar a velocidade de download de arquivos em aplicações web é fundamental para gerenciar a carga do servidor, otimizar a experiência do usuário e garantir o uso justo dos recursos. Sem limitações, um servidor pode ser sobrecarregado por múltiplos downloads simultâneos em alta velocidade, impactando a performance geral do site.
Em PHP, a implementação de um mecanismo de limitação de velocidade para downloads envolve a manipulação de cabeçalhos HTTP, a leitura do arquivo em blocos e a introdução de pausas estratégicas durante a transmissão de dados. O princípio é simples: ler uma porção do arquivo, enviá-la ao navegador e, se necessário, pausar a execução do script por um curto período antes de enviar a próxima porção.
Cabeçalhos HTTP Essenciais para Download
Para garantir que o navegador trate o conteúdo como um arquivo para download, e não como uma página web, é crucial definir os cabeçalhos HTTP apropriados. Os mais comuns incluem:
Content-Description: File Transfer: Uma descrição geral para a transferência.Content-Type: application/octet-stream: Indica que o conteúdo é um fluxo de bytes genérico, e o navegador deve tratá-lo como um arquivo binário.Content-Disposition: attachment; filename="nome_do_arquivo.ext": Força o download do arquivo e sugere um nome para ele.Content-Transfer-Encoding: binary: Indica que o corpo da mensagem é um arquivo binário.Expires: 0,Cache-Control: must-revalidate,Pragma: public: Impedem que o arquivo seja armazenado em cache.Content-Length: [tamanho_do_arquivo_em_bytes]: Informa o tamanho total do arquivo, permitindo que o navegador exiba o progresso do download.
Estratégias de Limitação de Velocidade
1. Cotnrole Preciso com Pausas em Microssegundos (usleep)
Esta abordagem visa um controle mais granular da velocidade, ajustando as pausas com base no tempo esperado para a transmissão de um bloco de dados. É ideal para garantir que a taxa de download não exceda um limite definido por segundo.
<?php
/**
* Serve um arquivo com uma taxa de download limitada.
*
* @param string $caminhoDoArquivo O caminho completo para o arquivo no sistema de arquivos.
* @param int $taxaKiloBytesPorSegundo A taxa máxima de download em KB/s. Padrão para 100 KB/s.
* @return void
*/
function servirArquivoComLimite(string $caminhoDoArquivo, int $taxaKiloBytesPorSegundo = 100): void
{
if (!file_exists($caminhoDoArquivo)) {
http_response_code(404);
echo "O arquivo solicitado não foi encontrado.";
return;
}
$tamanhoTotalBytes = filesize($caminhoDoArquivo);
$nomeParaDownload = basename($caminhoDoArquivo);
$taxaBytesPorSegundo = $taxaKiloBytesPorSegundo * 1024; // Converte KB/s para Bytes/s
// Define os cabeçalhos HTTP para download
header('Content-Description: Transferência de Arquivo');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $nomeParaDownload . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $tamanhoTotalBytes);
// Abre o arquivo em modo de leitura binária
$handle = fopen($caminhoDoArquivo, 'rb');
if (!$handle) {
http_response_code(500);
echo "Não foi possível abrir o arquivo para leitura.";
return;
}
$tamanhoDoBlocoParaLeitura = 1024 * 64; // Tenta ler blocos de 64 KB
// Ajusta o tamanho do bloco se a velocidade for muito baixa
if ($taxaBytesPorSegundo < $tamanhoDoBlocoParaLeitura) {
$tamanhoDoBlocoParaLeitura = max(1024, (int)($taxaBytesPorSegundo / 2)); // Mínimo 1KB
}
$ultimoTempoDeEnvio = microtime(true);
while (!feof($handle)) {
echo fread($handle, $tamanhoDoBlocoParaLeitura);
flush(); // Garante que o conteúdo seja enviado imediatamente ao navegador
$tempoAtual = microtime(true);
$tempoDecorrido = $tempoAtual - $ultimoTempoDeEnvio;
// Calcula o tempo que deveria ter passado para enviar este bloco
$tempoEsperadoParaBloco = $tamanhoDoBlocoParaLeitura / $taxaBytesPorSegundo;
if ($tempoDecorrido < $tempoEsperadoParaBloco) {
// Calcula e aplica a pausa necessária em microssegundos
$tempoDePausaMicrosegundos = (int)(($tempoEsperadoParaBloco - $tempoDecorrido) * 1000000);
if ($tempoDePausaMicrosegundos > 0) {
usleep($tempoDePausaMicrosegundos);
}
}
$ultimoTempoDeEnvio = microtime(true); // Reinicia o contador para a próxima iteração
}
fclose($handle);
exit; // Termina o script para evitar output adicional
}
// Exemplo de uso:
// $caminhoDoMeuArquivo = "caminho/para/seu/arquivo.zip";
// servirArquivoComLimite($caminhoDoMeuArquivo, 250); // Limita o download a 250 KB/s
?>
2. Controle Simplificado com Blocos Maiores e Pausas em Segundos (sleep)
Esta técnica é mais simples e geralmente usada para arquivos grandes, onde a precisão de sub-segundos não é tão crítica. O script lê um bloco maior do arquivo e pausa por um segundo inteiro antes de continuar. Isso resulta em uma velocidade de download mais aproximada.
<?php
/**
* Distribui um arquivo em blocos, com pausas em segundos.
*
* @param string $caminhoRecurso O caminho completo para o arquivo.
* @param int $tamanhoBlocoMegaBytes O tamanho de cada bloco a ser enviado, em MB. Padrão para 2 MB.
* @param int $intervaloEntreBlocosSegundos O tempo de pausa entre o envio de blocos, em segundos. Padrão para 1 segundo.
* @return void
*/
function distribuirArquivoEmBlocos(string $caminhoRecurso, int $tamanhoBlocoMegaBytes = 2, int $intervaloEntreBlocosSegundos = 1): void
{
set_time_limit(0); // Remove o limite de tempo de execução do script
if (!file_exists($caminhoRecurso)) {
http_response_code(404);
echo 'O recurso solicitado não existe ou está indisponível!';
return;
}
// Utiliza SplFileInfo para obter informações do arquivo de forma robusta
$infoDoArquivo = new SplFileInfo($caminhoRecurso);
$nomeParaExibicao = $infoDoArquivo->getBasename();
$tamanhoTotalDoArquivo = $infoDoArquivo->getSize();
// Define os cabeçalhos HTTP
header('Content-Description: Transferência de Conteúdo');
header('Content-Type: application/octet-stream');
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . $tamanhoTotalDoArquivo);
header('Content-Disposition: attachment; filename="' . $nomeParaExibicao . '"');
$manipuladorDeArquivo = fopen($caminhoRecurso, 'rb');
if (!$manipuladorDeArquivo) {
http_response_code(500);
echo 'Erro ao abrir o recurso para leitura.';
return;
}
$bytesPorLeitura = $tamanhoBlocoMegaBytes * 1024 * 1024; // Converte MB para Bytes
ob_start(); // Inicia o buffer de saída do PHP
while (!feof($manipuladorDeArquivo)) {
echo fread($manipuladorDeArquivo, $bytesPorLeitura);
ob_flush(); // Libera o conteúdo do buffer PHP para o servidor web
flush(); // Libera o conteúdo do buffer do servidor web para o navegador
// Aplica a pausa se o intervalo for maior que zero
if ($intervaloEntreBlocosSegundos > 0) {
sleep($intervaloEntreBlocosSegundos);
}
}
ob_end_clean(); // Finaliza e limpa o buffer de saída
fclose($manipuladorDeArquivo);
exit;
}
// Exemplo de uso:
// $caminhoDoMeuVideo = "/var/www/uploads/filme.mp4";
// distribuirArquivoEmBlocos($caminhoDoMeuVideo, 4, 1); // Envia 4MB a cada 1 segundo (resultando em ~4MB/s)
?>
Ambas as abordagens oferecem soluções viáveis para o controle da velocidade de download. A escolha entre elas dependerá da precisão necessária e da natureza dos arquivos que estão sendo servidos.