Estou tentando montar uma função que recebe um caminho de arquivo, identifica o que é, define os cabeçalhos apropriados e serve-o como faria o Apache.
Estou fazendo isso porque preciso usar o PHP para processar algumas informações sobre a solicitação antes de servir o arquivo.
Velocidade é crítica
virtual () não é uma opção
Deve trabalhar em um ambiente de hospedagem compartilhada onde o usuário não tem controle do servidor web (Apache / nginx, etc)
Aqui está o que tenho até agora:
File::output($path);
<?php
class File {
static function output($path) {
// Check if the file exists
if(!File::exists($path)) {
header('HTTP/1.0 404 Not Found');
exit();
}
// Set the content-type header
header('Content-Type: '.File::mimeType($path));
// Handle caching
$fileModificationTime = gmdate('D, d M Y H:i:s', File::modificationTime($path)).' GMT';
$headers = getallheaders();
if(isset($headers['If-Modified-Since']) && $headers['If-Modified-Since'] == $fileModificationTime) {
header('HTTP/1.1 304 Not Modified');
exit();
}
header('Last-Modified: '.$fileModificationTime);
// Read the file
readfile($path);
exit();
}
static function mimeType($path) {
preg_match("|\.([a-z0-9]{2,4})$|i", $path, $fileSuffix);
switch(strtolower($fileSuffix[1])) {
case 'js' :
return 'application/x-javascript';
case 'json' :
return 'application/json';
case 'jpg' :
case 'jpeg' :
case 'jpe' :
return 'image/jpg';
case 'png' :
case 'gif' :
case 'bmp' :
case 'tiff' :
return 'image/'.strtolower($fileSuffix[1]);
case 'css' :
return 'text/css';
case 'xml' :
return 'application/xml';
case 'doc' :
case 'docx' :
return 'application/msword';
case 'xls' :
case 'xlt' :
case 'xlm' :
case 'xld' :
case 'xla' :
case 'xlc' :
case 'xlw' :
case 'xll' :
return 'application/vnd.ms-excel';
case 'ppt' :
case 'pps' :
return 'application/vnd.ms-powerpoint';
case 'rtf' :
return 'application/rtf';
case 'pdf' :
return 'application/pdf';
case 'html' :
case 'htm' :
case 'php' :
return 'text/html';
case 'txt' :
return 'text/plain';
case 'mpeg' :
case 'mpg' :
case 'mpe' :
return 'video/mpeg';
case 'mp3' :
return 'audio/mpeg3';
case 'wav' :
return 'audio/wav';
case 'aiff' :
case 'aif' :
return 'audio/aiff';
case 'avi' :
return 'video/msvideo';
case 'wmv' :
return 'video/x-ms-wmv';
case 'mov' :
return 'video/quicktime';
case 'zip' :
return 'application/zip';
case 'tar' :
return 'application/x-tar';
case 'swf' :
return 'application/x-shockwave-flash';
default :
if(function_exists('mime_content_type')) {
$fileSuffix = mime_content_type($path);
}
return 'unknown/' . trim($fileSuffix[0], '.');
}
}
}
?>
php
performance
file-io
x-sendfile
Kirk Ouimet
fonte
fonte
$extension = end(explode(".", $pathToFile))
ou você pode fazê-lo com substr e strrpos:$extension = substr($pathToFile, strrpos($pathToFile, '.'))
. Além disso, como alternativamime_content_type()
, você pode tentar uma chamada de sistema:$mimetype = exec("file -bi '$pathToFile'", $output);
Respostas:
Minha resposta anterior foi parcial e não bem documentada, aqui está uma atualização com um resumo das soluções dela e de outros na discussão.
As soluções são ordenadas da melhor solução para a pior, mas também da solução que precisa de mais controle sobre o servidor web para a que precisa de menos. Não parece haver uma maneira fácil de ter uma solução que seja rápida e funcione em qualquer lugar.
Usando o cabeçalho X-SendFile
Conforme documentado por outros, é realmente a melhor maneira. A base é que você faz o controle de acesso em php e, em vez de enviar o arquivo você mesmo, diz ao servidor da web para fazê-lo.
O código php básico é:
Onde
$file_name
está o caminho completo no sistema de arquivos.O principal problema desta solução é que ela precisa ser permitida pelo servidor web e não está instalada por padrão (apache), não está ativa por padrão (lighttpd) ou precisa de uma configuração específica (nginx).
Apache
No apache, se você usar mod_php, você precisa instalar um módulo chamado mod_xsendfile e então configurá-lo (na configuração do apache ou .htaccess se você permitir)
Com este módulo, o caminho do arquivo pode ser absoluto ou relativo ao especificado
XSendFilePath
.Lighttpd
O mod_fastcgi suporta isso quando configurado com
A documentação para o recurso está no wiki lighttpd eles documentam o
X-LIGHTTPD-send-file
cabeçalho, mas oX-Sendfile
nome também funcionaNginx
No Nginx você não pode usar o
X-Sendfile
cabeçalho, você deve usar o seu próprio cabeçalho nomeadoX-Accel-Redirect
. Ele é habilitado por padrão e a única diferença real é que seu argumento deve ser um URI, não um sistema de arquivos. A conseqüência é que você deve definir um local marcado como interno em sua configuração para evitar que os clientes encontrem a url do arquivo real e acessem diretamente a ela, o wiki contém uma boa explicação disso.Links simbólicos e cabeçalho de localização
Você pode usar links simbólicos e redirecionar para eles, basta criar links simbólicos para seu arquivo com nomes aleatórios quando um usuário estiver autorizado a acessar um arquivo e redirecionar o usuário para ele usando:
Obviamente, você precisará de uma maneira de podá-los quando o script para criá-los for chamado ou via cron (na máquina se você tiver acesso ou via algum serviço webcron caso contrário)
No apache, você precisa ser capaz de habilitar
FollowSymLinks
em um.htaccess
ou na configuração do apache.Controle de acesso por IP e cabeçalho de localização
Outro hack é gerar arquivos de acesso apache a partir do php, permitindo o IP explícito do usuário. No apache, significa usar comandos
mod_authz_host
(mod_access
)Allow from
.O problema é que bloquear o acesso ao arquivo (já que vários usuários podem querer fazer isso ao mesmo tempo) não é trivial e pode fazer com que alguns usuários esperem muito. E você ainda precisa remover o arquivo de qualquer maneira.
Obviamente, outro problema seria que várias pessoas por trás do mesmo IP poderiam acessar o arquivo.
Quando tudo mais falhar
Se você realmente não tem como obter ajuda do seu servidor web, a única solução restante é readfile, que está disponível em todas as versões de php atualmente em uso e funciona muito bem (mas não é realmente eficiente).
Combinando soluções
Em suma, a melhor maneira de enviar um arquivo realmente rápido se você quiser que seu código php possa ser usado em qualquer lugar é ter uma opção configurável em algum lugar, com instruções sobre como ativá-lo dependendo do servidor web e talvez uma detecção automática em sua instalação roteiro.
É muito semelhante ao que é feito em muitos softwares para
mod_rewrite
no apache)mcrypt
módulo php)mbstring
módulo php)fonte
header("Location: " . $path);
?A maneira mais rápida: não faça isso. Olhe no cabeçalho x-sendfile para nginx , há coisas semelhantes para outros servidores da web também. Isso significa que você ainda pode fazer o controle de acesso etc em php, mas delegar o envio real do arquivo a um servidor web projetado para isso.
PS: Eu fico arrepiado só de pensar em como é mais eficiente usar isso com o nginx, em comparação com ler e enviar o arquivo em php. Basta pensar se 100 pessoas estão baixando um arquivo: Com php + apache, sendo generoso, isso é provavelmente 100 * 15mb = 1,5 GB (aprox, atire em mim), de RAM bem ali. O Nginx apenas transfere o envio do arquivo para o kernel e, em seguida, é carregado diretamente do disco para os buffers de rede. Veloz!
PPS: E, com este método, você ainda pode fazer todo o controle de acesso, coisas de banco de dados que quiser.
fonte
readfile()
Aqui vai uma solução PHP pura. Eu adaptei a seguinte função da minha estrutura pessoal :
O código é tão eficiente quanto pode ser, ele fecha o manipulador de sessão para que outros scripts PHP possam ser executados simultaneamente para o mesmo usuário / sessão. Ele também oferece suporte a downloads em intervalos (que também é o que o Apache faz por padrão, eu suspeito), para que as pessoas possam pausar / retomar downloads e também se beneficiar de velocidades de download mais altas com aceleradores de download. Ele também permite que você especifique a velocidade máxima (em Kbps) na qual o download (parte) deve ser servido por meio do
$speed
argumento.fonte
eio
nem sempre está disponível. Ainda assim, +1, não sabia sobre essa extensão pecl. =)$size = sprintf('%u', filesize($path))
?Deixe o Apache fazer o trabalho por você.
fonte
Uma melhor implementação, com suporte de cache, cabeçalhos http personalizados.
fonte
se você tem a possibilidade de adicionar extensões PECL ao seu php, você pode simplesmente usar as funções do pacote Fileinfo para determinar o tipo de conteúdo e então enviar os cabeçalhos apropriados ...
fonte
A
Download
função PHP mencionada aqui estava causando algum atraso antes de o arquivo realmente começar a ser baixado. Não sei se isso foi causado pelo uso de cache de verniz ou o quê, mas para mim ajudou a removersleep(1);
completamente e definir$speed
como1024
. Agora funciona sem problemas e é rápido como o inferno. Talvez você possa modificar essa função também, pois vi que ela é usada em toda a internet.fonte
Codifiquei uma função muito simples para servir arquivos com PHP e detecção automática de tipo MIME:
Uso
fonte