Estoy tratando de armar una función que reciba una ruta de archivo, identifique qué es, establezca los encabezados apropiados y la sirva como lo haría Apache.
La razón por la que hago esto es porque necesito usar PHP para procesar cierta información sobre la solicitud antes de entregar el archivo.
La velocidad es fundamental
virtual () no es una opción
Debe funcionar en un entorno de alojamiento compartido donde el usuario no tiene control del servidor web (Apache / nginx, etc.)
Esto es lo que tengo hasta ahora:
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
fuente
fuente
$extension = end(explode(".", $pathToFile))
, o puede hacerlo con substr y strrpos:$extension = substr($pathToFile, strrpos($pathToFile, '.'))
. Además, como alternativamime_content_type()
, puede probar una llamada al sistema:$mimetype = exec("file -bi '$pathToFile'", $output);
Respuestas:
Mi respuesta anterior fue parcial y no está bien documentada, aquí hay una actualización con un resumen de las soluciones de ella y de otros en la discusión.
Las soluciones están ordenadas de la mejor a la peor, pero también de la solución que necesita más control sobre el servidor web a la que necesita menos. No parece haber una manera fácil de tener una solución que sea rápida y funcione en todas partes.
Usando el encabezado X-SendFile
Según lo documentado por otros, en realidad es la mejor manera. La base es que usted hace su control de acceso en php y luego, en lugar de enviar el archivo usted mismo, le dice al servidor web que lo haga.
El código php básico es:
¿Dónde
$file_name
está la ruta completa en el sistema de archivos?El principal problema con esta solución es que debe ser permitido por el servidor web y no está instalado por defecto (apache), no está activo por defecto (lighttpd) o necesita una configuración específica (nginx).
apache
Bajo apache, si usa mod_php, debe instalar un módulo llamado mod_xsendfile y luego configurarlo (ya sea en la configuración de apache o .htaccess si lo permite)
Con este módulo, la ruta del archivo puede ser absoluta o relativa a la especificada
XSendFilePath
.Lighttpd
El mod_fastcgi admite esto cuando se configura con
La documentación de la función está en el wiki de lighttpd , documentan el
X-LIGHTTPD-send-file
encabezado pero elX-Sendfile
nombre también funcionaNginx
En Nginx no puede usar el
X-Sendfile
encabezado, debe usar su propio encabezado con el nombreX-Accel-Redirect
. Está habilitado de forma predeterminada y la única diferencia real es que su argumento debe ser un URI, no un sistema de archivos. La consecuencia es que debe definir una ubicación marcada como interna en su configuración para evitar que los clientes encuentren la URL del archivo real y vayan directamente a ella, su wiki contiene una buena explicación de esto.Encabezado de Symlinks y Location
Puede usar enlaces simbólicos y redirigirlos, simplemente cree enlaces simbólicos a su archivo con nombres aleatorios cuando un usuario esté autorizado a acceder a un archivo y redirigir al usuario a él usando:
Obviamente, necesitará una forma de podarlos cuando se llame al script para crearlos o mediante cron (en la máquina si tiene acceso o mediante algún servicio webcron de lo contrario)
Bajo apache, debe poder habilitar
FollowSymLinks
en un.htaccess
o en la configuración de apache.Control de acceso por IP y encabezado de ubicación
Otro truco es generar archivos de acceso apache desde php que permitan la IP explícita del usuario. Bajo apache significa usar
mod_authz_host
(mod_access
)Allow from
comandos .El problema es que bloquear el acceso al archivo (ya que varios usuarios pueden querer hacer esto al mismo tiempo) no es trivial y podría hacer que algunos usuarios esperen mucho tiempo. Y aún necesita podar el archivo de todos modos.
Obviamente, otro problema sería que varias personas detrás de la misma IP podrían acceder al archivo.
Cuando todo lo demás falla
Si realmente no tiene ninguna forma de que su servidor web lo ayude, la única solución que queda es readfile, que está disponible en todas las versiones de php actualmente en uso y funciona bastante bien (pero no es realmente eficiente).
Combinando soluciones
En fin, la mejor manera de enviar un archivo realmente rápido si desea que su código php se pueda usar en todas partes es tener una opción configurable en algún lugar, con instrucciones sobre cómo activarlo dependiendo del servidor web y tal vez una detección automática en su instalación. guión.
Es bastante similar a lo que se hace en muchos software para
mod_rewrite
en apache)mcrypt
módulo php)mbstring
módulo php)fuente
header("Location: " . $path);
?La forma más rápida: no lo hagas. Mire en el encabezado x-sendfile para nginx , también hay cosas similares para otros servidores web. Esto significa que aún puede hacer control de acceso, etc. en php, pero delegar el envío real del archivo a un servidor web diseñado para eso.
PD: Me da escalofríos solo de pensar en lo mucho más eficiente que es usar esto con nginx, en comparación con leer y enviar el archivo en php. Solo piense si 100 personas están descargando un archivo: con php + apache, siendo generoso, eso es probablemente 100 * 15mb = 1.5GB (aproximadamente, dispárenme), de RAM allí mismo. Nginx simplemente enviará el archivo al kernel y luego se cargará directamente desde el disco en los búferes de la red. ¡Rápido!
PPS: Y, con este método, aún puede hacer todo el control de acceso, las cosas de base de datos que desee.
fuente
readfile()
Aquí va una solución PHP pura. He adaptado la siguiente función de mi marco personal :
El código es tan eficiente como puede ser, cierra el controlador de sesión para que otros scripts PHP puedan ejecutarse simultáneamente para el mismo usuario / sesión. También admite la entrega de descargas en rangos (que también es lo que hace Apache de forma predeterminada, sospecho), para que las personas puedan pausar / reanudar las descargas y también beneficiarse de velocidades de descarga más altas con aceleradores de descarga. También le permite especificar la velocidad máxima (en Kbps) a la que la descarga (parte) debe ser servida a través del
$speed
argumento.fuente
eio
tampoco siempre está disponible. Aún así, +1, no sabía sobre esa extensión pecl. =)$size = sprintf('%u', filesize($path))
?Deje que Apache haga el trabajo por usted.
fuente
Una mejor implementación, con soporte de caché, encabezados http personalizados.
fuente
Si tiene la posibilidad de agregar extensiones PECL a su php, simplemente puede usar las funciones del paquete Fileinfo para determinar el tipo de contenido y luego enviar los encabezados adecuados ...
fuente
La
Download
función PHP mencionada aquí estaba causando cierto retraso antes de que el archivo comenzara a descargarse. No sé si esto fue causado por el uso de caché de barniz o qué, pero me ayudó a eliminar elsleep(1);
completo y conjunto$speed
a1024
. Ahora funciona sin ningún problema ya que es increíblemente rápido. Tal vez puedas modificar esa función también, porque vi que se usaba en todo Internet.fuente
Codifiqué una función muy simple para servir archivos con PHP y detección automática de tipo MIME:
Uso
fuente