Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 021 — Subida de archivos#

⬅️ 020 · 📚 Parte 1 · 🎓 Clases · 022 ➡️ Parte 1 — Responder · Nivel 🟡 intermedio · Pista backendClase construida — 4 implementaciones verificadas contra contrato.json.

🎯 Objetivo#

Recibir un archivo sin cargarlo entero en memoria y rechazar lo que pasa del límite mientras se recibe, no después.

📖 Por qué el orden importa tanto#

Un archivo subido llega por la red poco a poco. Hay dos formas de tratarlo:

Enfoque Qué pasa con 500 MB
Leer entero y luego medir 500 MB en memoria antes de poder rechazarlo
Medir mientras se lee y cortar Se corta al pasar del límite

La primera opción hace del límite una decoración: el daño ya está hecho cuando lo aplicas. Diez peticiones simultáneas de 500 MB tumban el proceso aunque tu límite sea de 1 MB.

Es un caso de manual de agotamiento de recursos, y de las defensas que Nygard agrupa bajo la idea de que todo recurso debe tener un tope explícito [nygard-release-it].

🧩 La situación#

POST /subir recibe un archivo en un campo llamado archivo y devuelve su nombre y su tamaño. Sin ese campo responde 422. Por encima de 1 KB —un límite pequeño a propósito, para que la prueba sea rápida— responde 413.

🧮 El contrato#

Petición Respuesta
archivo nota.txt de 4 bytes 201 · {"nombre":"nota.txt","bytes":4}
sin campo archivo 422 · {"error":"falta el archivo"}
archivo de 4096 bytes (límite 1024) 413

<!-- generado: fichas -->

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Express framework web de Node.js (JavaScript) 2010 MIT OpenJS Foundation
FastAPI framework web de Python (Python) 2018 MIT proyecto independiente
Spring Boot framework de aplicación de JVM (Java) 2014 Apache-2.0 Broadcom/VMware y colaboradores
ASP.NET Core framework web de .NET (C#) 2016 MIT Microsoft y .NET Foundation

🔧 Express#

Definió el modelo de middleware encadenado que copiaron casi todos los frameworks de Node.js. Minimalista no significa biblioteca: posee el bucle de peticiones.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts

Arrancarla suelta, sin el verificador:

PORT=3000 node server.mjs

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
server.mjs código JavaScript (módulo ES)

🔧 FastAPI#

Deriva validación, serialización y documentación OpenAPI de las anotaciones de tipo. Demostró que el tipado opcional de Python podía ser infraestructura, no adorno.

Arrancarla suelta, sin el verificador:

PORT=3000 python -m uvicorn main:app --host 127.0.0.1 --port 3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
main.py código Python
requirements.txt dependencias de Python, una por línea, con versión fijada

🔧 Spring Boot#

Autoconfiguración y servidor incrustado sobre Spring. Convirtió un framework famoso por su configuración XML en uno de arranque inmediato.

Preparar sus dependencias, dentro de su directorio:

mvn -q -B package -DskipTests

Arrancarla suelta, sin el verificador:

PORT=3000 java -jar target/clase-021-1.0.0.jar --server.port=3000

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
pom.xml manifiesto de Maven: el proyecto, su Java, sus dependencias y cómo se empaqueta
src/main/java/labs/Aplicacion.java código Java
src/main/resources/application.properties configuración de Spring Boot: lo que se ajusta sin tocar el código

🔧 ASP.NET Core#

Reescritura multiplataforma y de código abierto de la pila web de Microsoft. Sus API mínimas trajeron el estilo de los microframeworks al ecosistema .NET.

Preparar sus dependencias, dentro de su directorio:

dotnet build -c Release --nologo -v quiet

Arrancarla suelta, sin el verificador:

PORT=3000 dotnet run -c Release --no-build --urls http://127.0.0.1:3000

Qué hay dentro de su directorio:

Archivo Qué es
Clase021.csproj proyecto de .NET: el marco de destino y las dependencias
Program.cs código C#
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

Los cuatro aceptan un archivo y rechazan uno grande. Lo que hay que mirar es dónde se aplica el límite, porque de eso depende si el archivo enorme llegó a ocupar memoria antes de ser rechazado.

Express · express/server.mjs — con biblioteca externa#

const subida = multer({ storage: multer.memoryStorage(), limits: { fileSize: LIMITE } });
app.post("/subir", subida.single("archivo"), (peticion, respuesta) => {
app.use((error, peticion, respuesta, siguiente) => {
  if (error?.code === "LIMIT_FILE_SIZE") {
    return respuesta.status(413).json({ error: "archivo demasiado grande" });
  }
  siguiente(error);
});

Express no analiza multipart: hace falta una biblioteca. Es coherente con lo que es —un enrutador con middleware— y es una dependencia más que elegir y mantener.

Lo importante es que limits.fileSize se comprueba mientras se recibe y aborta al superarse. Y el manejador de errores no es opcional: sin él, el fallo sale con el formato por omisión de Express y no con un 413.

FastAPI · fastapi/main.py — el bucle a la vista#

    total = 0
    while trozo := await archivo.read(256):
        total += len(trozo)
        if total > LIMITE:
            return JSONResponse({"error": "archivo demasiado grande"}, status_code=413)

La implementación donde el mecanismo se ve mejor, porque el bucle está escrito. Se lee a trozos y se corta en cuanto se pasa: leer entero y medir después ya habría gastado la memoria que se quería proteger.

UploadFile es además un envoltorio sobre un archivo temporal — Starlette vuelca a disco los cuerpos grandes en lugar de mantenerlos en memoria. Esa parte viene resuelta; el límite, no.

Spring Boot · spring-boot/…/application.properties — el límite en configuración#

spring.servlet.multipart.max-file-size=1KB
spring.servlet.multipart.max-request-size=2KB

Y en Aplicacion.java, lo único que queda por escribir:

    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<Map<String, String>> demasiadoGrande() {
        return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
                .body(Map.of("error", "archivo demasiado grande"));
    }

El límite no está en el código: lo aplica el contenedor de servlets antes de que tu método exista. Es el enfoque más robusto de los cuatro —la defensa está antes que tu código— y el menos visible: quien lea el controlador no ve que hay un límite.

Fíjate en los dos ajustes, porque el segundo es el que se olvida: max-file-size limita cada archivo y max-request-size la petición completa. Sin el segundo, cien archivos de 1 KB pasan el primero.

ASP.NET Core · aspnet-core/Program.cs — comprobación tras leer#

    var formulario = await peticion.ReadFormAsync();
    var archivo = formulario.Files["archivo"];
    if (archivo.Length > limite)
    {
        return Results.Json(new { error = "archivo demasiado grande" }, statusCode: 413);
    }

Aquí el tamaño se comprueba después de leer el formulario, así que esta comprobación no protege la memoria: para cuando archivo.Length existe, el cuerpo ya se recibió.

La protección real la da el límite del servidor subyacente —Kestrel tiene su MaxRequestBodySize y el servidor de entrada el suyo—, y se configura ahí, no en el manejador. Queda declarado porque es la diferencia con las otras tres: lo que este código hace es decir el error correcto, no evitar el gasto.

🔬 Comparación#

Framework ¿Analiza multipart? Dónde vive el límite ¿Corta durante la recepción?
Spring Boot configuración del contenedor , antes de tu código
Express con biblioteca opción de la biblioteca
FastAPI tu bucle de lectura , si lo escribes
ASP.NET Core servidor subyacente depende de la configuración

🔒 Lo que esta clase no cubre y hay que saber#

Recibir el archivo es la parte fácil. Lo que hace peligrosa una subida es lo que viene después:

Las cuatro están en las guías de OWASP sobre subida de archivos [owasp-cheatsheets], y las cuatro son responsabilidad de la aplicación: ningún framework de esta tabla las resuelve por ti.

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 021

🧪 Reto de transferencia#

Añade una comprobación que rechace con 415 cualquier archivo cuyo contenido real no empiece por los bytes de una imagen PNG, ignorando el content-type declarado. Es la diferencia entre confiar en el cliente y comprobar.

🔗 Enlaces#

Fuentes#