Bienvenido a los foros de Perl en español, tinchosan.
La solución no es obvia porque el problema, aunque lo parezca, tampoco lo es.
Primero, unos conceptos básicos.
Un CGI es un programa que el servidor web ejecuta, y del que espera unos contenidos, que serán transmitidos al navegador del usuario. Esos contenidos pueden ser una página HTML completa, o solo una parte (un módulo) dentro de una página HTML. Casi siempre son contenidos dinámicos, es decir, que depende de ciertas condiciones o parámetros, dará resultados distintos.
El proceso es: el usuario pulsa "algo" en la página que está viendo (una imagen, un enlace, un botón...). Eso se convierte en una petición al servidor web. Éste, mirando los mapas de traslación, se da cuenta de que corresponde a la ejecución de un proceso externo (el programa cgi). Arranca el proceso, pasándole por la entrada estándar los argumentos que el usuario ha enviado junto con la petición (el nombre del botón que ha pulsado, las coordenadas dentro de la imagen, o algún parámetro pasado por la URL en el caso del enlace).
El programa cgi se ejecuta, leyendo los argumentos, sacará el resultado por la salida estándar, con una cabecera HTTP. El servidor web encamina todo eso hacia el navegador del usuario, quien, por medio de la cabecera, sabrá cómo representar el resto del contenido. El programa cgi termina, y el servidor web cierra la conexión con el navegador del usuario. Y... ya empiezas a ver los primeros problemas: el protocolo
CGI depende del protocolo
HTTP, que
no está orientado a conexión, sino solo a petición y respuesta. Una vez servido el contenido, se corta la conexión (bueno, no es del todo así, pero sí lo es de forma estricta).
Cuando un usuario hace una subido de un fichero, ese fichero se convierte en un flujo, que el servidor web reenvía hacia el proceso cgi, también a través de la entrada estándar.
El problema es que hasta que no se cierra el proceso cgi, la página sigue apareciendo como que sigue cargando, desde el lado del usuario. Y hay algunos navegadores que no presentan la página hasta que ésta ha llegado completamente. Y eso solo sucede si el proceso cgi ha terminado.
Dicho de otra manera: si el proceso cgi termina, no podemos ir mostrando la barra de progreso. Y si el proceso cgi no termina, el navegador del usuario no termina de pintar la página.
En tu programa, estás metiéndote en un bucle, enviando capas (<div>), con la esperanza de que el navegador web los represente, pero, para el navegador web se trata de capas individuales, con lo que saldrán todas las capas, una detrás de otra. Y lo dicho: no hay garantía de que salgan hasta que termine el cgi.
La solución clásica es usar el protocolo NPH (Non-Parsed-Header), y así el navegador va pintando la página a medida de que recibe contenidos, pero el resultado es el mismo que el de tu programa: los contenidos se van colocando unos detrás de otros.
Una solución intermedia es la siguiente: el proceso cgi envía una redirección (
Location: ) a una página, cuyo único propósito es:
* recargarse cada x segundos (gracias a los <meta> de su cabecera)
* en cada recarga, ejecuta otro cgi, cuya única misión es de consultar el estado de descarga del fichero que se sigue bajando el primer cgi. En el caso de que ya esté del todo bajado, entonces, devuelve otra redirección (otro Location) pero esta vez para otra página que indique que el fichero ha subido completamente.
La solución moderna es usando Ajax: la página lanza el formulario con el fichero que sube el usuario, y queda a la espera de las respuestas que reciba desde el servidor. Desde el lado del servidor, el cgi sigue a lo suyo, recibiendo el fichero. El Ajax va ejecutando peticiones a otro cgi, que mira el progreso de la subida, y eso lo traduce el JavaScript en la animación de la barra de progreso.
No es una solución sencilla, claro. Por fortuna, algunos entornos de trabajo en JavaScript pueden ayudar a simplificar el trabajo de codificación.
En Perl, existe el módulo
CGI::ProgressBar, que usa una mezcla de Perl y JavaScript, para presentar una barra de progreso, para procesos largos. Usa el truco de enviar un trozo de código HTML que llama a un código JavaScript que se envió en primer lugar.