Resumen
La carga de archivos grandes en aplicaciones web presenta varios desafíos, tales como la posibilidad de fallas durante la carga, la necesidad de reanudar cargas interrumpidas y la gestión eficiente de la transferencia de datos. Este documento describe un enfoque robusto para manejar la carga de archivos grandes, utilizando técnicas de particionamiento en “chunks”, verificación mediante hashes, y la implementación de endpoints para gestionar la reanudación de cargas. Se incluye un ejemplo práctico en JavaScript para ilustrar la implementación.
Introducción
La transferencia de archivos grandes a través de la web puede ser problemática debido a la inestabilidad de las conexiones y limitaciones de tiempo de espera. Dividir archivos en partes más pequeñas, conocidas como “chunks”, y verificar la integridad de cada una de ellas con un hash, permite una carga más controlada y recuperable. Este enfoque es útil tanto para garantizar que no se vuelvan a subir partes ya transferidas, como para manejar cargas interrumpidas.
División de Archivos en “Chunks”
Concepto de “Chunks”
La división de un archivo grande en “chunks” permite manejar la carga de manera más controlada y eficiente. Cada “chunk” es una porción del archivo que se sube individualmente.
Implementación en JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chunked File Upload Example</title>
</head>
<body>
<!-- Input para seleccionar el archivo -->
<input type="file" id="fileInput" />
<!-- Botón para iniciar la subida -->
<button id="uploadButton">Subir archivo</button>
<script>
const fileInput = document.getElementById('fileInput');
const uploadButton = document.getElementById('uploadButton');
// Tamaño de chunk en bytes (100MB)
const chunkSize = 100 * 1024 * 1024;
uploadButton.addEventListener('click', () => {
const file = fileInput.files[0];
if (!file) {
alert('Por favor, selecciona un archivo primero.');
return;
}
// Calcular cuántos chunks se necesitan
const totalChunks = Math.ceil(file.size / chunkSize);
// Calcular hash del archivo para identificarlo de manera única
const fileHash = generateHash(file);
// Simulación de verificación de qué chunks ya están subidos
const uploadedChunks = checkUploadedChunks(fileHash);
for (let i = 0; i < totalChunks; i++) {
if (!uploadedChunks.includes(i)) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
uploadChunk(chunk, i, totalChunks, file.name, fileHash);
} else {
console.log(`Chunk ${i + 1} ya ha sido subido anteriormente.`);
}
}
});
function uploadChunk(chunk, chunkIndex, totalChunks, fileName, fileHash) {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', chunkIndex);
formData.append('totalChunks', totalChunks);
formData.append('fileName', fileName);
formData.append('fileHash', fileHash);
fetch('/upload', {
method: 'POST',
body: formData,
})
.then(response => {
if (response.ok) {
console.log(`Chunk ${chunkIndex + 1} de ${totalChunks} subido con éxito.`);
} else {
console.error(`Error al subir el chunk ${chunkIndex + 1}.`);
}
})
.catch(error => {
console.error('Error de red:', error);
});
}
function generateHash(file) {
// Implementar la generación de un hash único basado en el contenido del archivo
// Puede utilizarse la API SubtleCrypto o una librería externa como SparkMD5
return 'filehash-placeholder'; // Retorno de ejemplo
}
function checkUploadedChunks(fileHash) {
// Implementar una solicitud al servidor para verificar qué chunks ya han sido subidos
// Retorna un array de índices de chunks ya subidos
return []; // Ejemplo: retorno vacío, implica que nada ha sido subido
}
</script>
</body>
</html>
Verificación y Control de Cargas Parciales
Implementación del Endpoint en el Servidor
En el servidor, un endpoint puede verificar qué partes de un archivo ya han sido subidas mediante el uso del hash del archivo. Esto permite que el cliente solo suba los “chunks” faltantes.
Ejemplo de Endpoint
// Ejemplo en Node.js (Express)
app.post('/upload', (req, res) => {
const { chunkIndex, totalChunks, fileHash } = req.body;
// Verificar si el chunk ya fue subido
if (isChunkUploaded(fileHash, chunkIndex)) {
return res.status(200).send('Chunk ya subido');
}
// Guardar el chunk recibido
const chunk = req.files.chunk; // Suponiendo uso de middleware como multer
saveChunk(chunk, fileHash, chunkIndex);
// Si es el último chunk, ensamblar todo el archivo
if (chunkIndex === totalChunks - 1) {
assembleFile(fileHash, totalChunks);
}
res.status(200).send('Chunk subido');
});
function isChunkUploaded(fileHash, chunkIndex) {
// Implementar lógica para verificar si el chunk ya fue subido
return false; // Ejemplo: retornar false para siempre subir
}
function saveChunk(chunk, fileHash, chunkIndex) {
// Guardar el chunk en almacenamiento temporal
}
function assembleFile(fileHash, totalChunks) {
// Combinar todos los chunks en el archivo final
}
Gestión de Hashes
Uso de Hashes para Identificar Archivos
Un hash único se genera a partir del contenido del archivo. Este hash permite identificar de manera única el archivo sin depender de su nombre, asegurando que la verificación de la carga se base en el contenido real y no en metadatos externos.
Implementación de Hash en JavaScript
Existen varias maneras de generar un hash en JavaScript, incluyendo el uso de la API SubtleCrypto o librerías como SparkMD5.
Conclusiones
La gestión de la carga de archivos grandes puede ser optimizada dividiendo el archivo en “chunks”, verificando su integridad mediante hashes, y controlando la reanudación de la carga mediante la verificación en el servidor. Esta metodología asegura una transferencia de archivos más robusta y recuperable, que es capaz de manejar interrupciones y fallas en la red, mejorando la experiencia del usuario y la fiabilidad del sistema.
Palabras clave: Carga de archivos, “Chunks”, Hashing, Resiliencia de red, JavaScript, API Rest