Limitar la velocidad de descarga de archivos en PHP

En más de una ocasión queremos facilitar un archivo a nuestros usuarios por motivos varios. Por lo general lo alojaremos en algún hosting gratuíto para no incidir demasiado en el rendimiento de nuestro servidor: si no sabemos la aceptación que va a tener el archivo en cuestión podemos encontrarnos con la desagradable sorpresa de que nuestro servidor pase más tiempo caído que de pie, debido a cuellos de botella generados en horarios punta de descarga.

Tengo en mente hace algún tiempo el desarrollo de una e-biblioteca, en la cual es mi intención que se descarguen ebooks en formato pdf, y ante el problema del ancho de banda para alojar, pongamos por ejemplo, un par de gigas de ebooks especializados, he montado una función en php que te permite limitar la velocidad de descarga de los archivos sin retocar nada del lado del servidor.

PHP:
  1. <?php
  2. function enviar_archivo($archivo, $speed = 100) {
  3. // Veamos.. existe el fichero ?
  4.     if (!is_file($archivo)) {
  5.         die("<b>404 Archivo no encontrado");
  6.     } 
  7. // Recolectamos información sobre el archivo
  8.     $archivo_nombre = basename($archivo);
  9.     $archivo_extension = strtolower(substr(strrchr($archivo_nombre,"."),1));
  10. // Asignamos el Content-Type adecuado a cada tipo de archivo, esto se
  11. // puede configurar al gusto, yo he incluído los más comunes para los
  12. // ebooks: exe, zip, lit, pdf, mp3, html, html
  13.     switch($archivo_extension) {
  14.         case "exe":
  15.             $ctype="application/octet-stream";
  16.             break;
  17.         case "zip":
  18.             $ctype="application/zip";
  19.             break;
  20.         case "lit":
  21.             $ctype="application/lit";
  22.             break;
  23.         case "pdf":
  24.             $ctype="application/pdf";
  25.             break;
  26.         case "mp3":
  27.             $ctype="audio/mpeg3";
  28.             break;
  29.         case "htm":
  30.         case "html":
  31.             $ctype="text/html";
  32.             break;
  33.         case "txt":
  34.             $ctype="text/plain";
  35.             break;
  36. // Debemos tener cuidado con no dejar descargar por este
  37. // método ficheros sensibles tales como ficheros .php, .inc, etc...
  38.         case "php":
  39.         case "inc":
  40.             die("<b>No te dejo bajar ficheros ". $archivo_extension ."</b>");
  41.             break;
  42.         default:
  43.             $ctype="application/force-download";
  44.     }
  45. // Empezamos con los headers
  46.     header("Cache-Control:");
  47.     header("Cache-Control: public");
  48.     header("Content-Type: $ctype");
  49. // Meramente estético, sustituímos los posibles guiones bajos ( _ )
  50. // por espacios en el nombre del fichero para bajar archivos más
  51. // legibles. Ej: "fichero_a_bajar.pdf" se convierte en "fichero a bajar.pdf"
  52.     $archivo_nombre_limpio = str_replace("_", " ", $archivo_nombre);
  53. // Forzamos la descarga en lugar de la apertura
  54.     $header='Content-Disposition: attachment; filename='.$archivo_nombre_limpio;
  55.     header($header);
  56.     header("Accept-Ranges: bytes");
  57.     $tamano = filesize($archivo)
  58.  
  59.  
  60.  
  61. // Si el navegador nos solicita el http_range...
  62.     if(isset($_SERVER['HTTP_RANGE'])) {
  63. // entonces le enviamos sólo el trozo que falta.
  64. // (Mundialmente conocido como "resume")
  65.     list($a, $rango)=explode("=",$_SERVER['HTTP_RANGE']);
  66.     str_replace($rango, "-", $rango);
  67.     $tamano2=$tamano-1;
  68.     $nueva_longitud=$tamano2-$rango;
  69.     header("HTTP/1.1 206 Partial Content");
  70.     header("Content-Length: $nueva_longitud");
  71.     header("Content-Range: bytes $rango$tamano2/$tamano");
  72.     } else {
  73. // Si no le enviamos todo el pack
  74.     $tamano2=$tamano-1;
  75.     header("Content-Range: bytes 0-$tamano2/$tamano");
  76.     header("Content-Length: ".$tamano2);
  77.     } 
  78. // Una vez tomadas estas decisiones, sólo queda abrir el archivo...
  79.     $puntero_al_fichero = fopen("$archivo","rb");
  80. // localizar el inicio de la parte que queremos enviar...
  81.     fseek($puntero_al_fichero,$posicion_inicio);
  82. // y empezar a enviar. Aqui es donde se produce la magia... en base
  83. // a la variable de velocidad, leemos ese número de Ks del archivo,
  84. // lo enviamos... y nos "dormimos" un segundo. Esta forzada chapuza
  85. // produce exactamente el efecto deseado... ralentizar la descarga a
  86. // los X k/s que deseemos por cada envío.
  87.     while(!feof($puntero_al_fichero)) {     
  88. // Nos aseguramos de no producir un timeout por sobrepasar el
  89. // tiempo máximo de ejecución de PHP en caso de archivos
  90. // demasiado grandes.
  91.         set_time_limit(0);     
  92. // Ponemos los datos en el buffer...
  93.         print(fread($puntero_al_fichero,1024*$speed));
  94. // Lanzamos el contenido del buffer...
  95.         flush();
  96. // Y a dormir...
  97.         sleep(1);
  98.     }
  99. // Proceso completado, archivo descargado,
  100. // todo el "pescao vendío"
  101.     fclose($puntero_al_fichero);
  102.     exit;
  103. }
  104. // Y finalmente un ejemplo tonto de uso de la función
  105. enviar_archivo("fichero.pdf",100);
  106. ?>

Términos relacionados:

4 comentarios a esta entrada

  • Sebita dijo
    el # Miércoles, 6 de Diciembre del 2006 a las 23:33

    1

    muy bueno el script, pero tengo una duda..

    en la linea que dice:
    fseek($puntero_al_fichero, $posicion_inicio);

    la variable “$posicion_inicio” de donde sale??

    porque: Notice: Undefined variable: posicion_inicio…

    gracias.

  • Marcos B.L. dijo
    el # Jueves, 7 de Diciembre del 2006 a las 11:09

    2

    El código es tan sólo una “prueba de concepto”, como ves hay código que aún no sirve, ya que en el momento de escribir esto me peleaba con la función “resume”.

    Para usarlo como está creo que bastará con que sustituyas la variable $posicion_inicio por un simple 0.

    fseek($puntero_al_fichero,0);

  • Pepe dijo
    el # Lunes, 20 de Agosto del 2007 a las 20:13

    3

    No se que pasa pero los archivos ZIP se bajan corruptos….
    He probado con los RAR y con las imagenes y se bajan bien, pero no con los ZIP

  • Pepito dijo
    el # Martes, 21 de Agosto del 2007 a las 02:18

    4

    le he quitado lo del http_range y ahora los zips se bajan bien.

    Muchas gracias por la funcion.

Deja tu opinión

Sólo se permiten las etiquetas XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Post anteriores/siguientes:

Post (quizás) relacionados:

  • Limitar Descarga o Apertura de ficheros en Internet Explorer

  • Sólo válido para IE 6 SP2 y posteriores, (aunque hay formas de lograr lo mismo del lado del servidor utilizando, por ejemplo un header content...
  • Servicios de replicación de archivos

  • UN POCO DE HISTORIA PARA SITUARNOS EN CONTEXTO Si pasas de rollos... puedes ir directamente a la "chicha" pulsando aqui. En Internet hay muchas formas por las...
  • CodePress, editor de código fuente online

  • CodePress es un editor de código fuente derivado del proyecto ECCO (Un IDE de programación basado en web), con coloreado de sintaxis escrito en puro...
  • De re varia

  • Interesante proyecto de páginas de inicio utilizando AJAX Servicio de publicación de libros. También puedes crear tus propios sellos en este otro sitio web. Servicio similar a...
  • CoralCDN, tu Akamai distribuido gratuito

  • UPDATE: Aunque el contenido y fin de este post sigue siendo perfectamente válido, el ejemplo incluído al final no funcionará, ya que se trabata de...