¿Qué es multipart/form-data Content-Type? Subida de archivos, ejemplos
multipart/form-data es el Content-Type HTTP para formularios con archivos. Formato del boundary, envío con fetch, curl y k6, y errores comunes.
¿Qué es multipart/form-data?
multipart/form-data es el Content-Type de HTTP para enviar un formulario que contiene archivos o datos binarios. El cuerpo de la solicitud se divide en partes, una por campo, separadas por una cadena llamada boundary, y cada parte lleva sus propias cabeceras. Los navegadores lo usan cuando un formulario tiene enctype="multipart/form-data", y está definido en la RFC 7578.
Existe porque los bytes de un archivo no caben de forma segura en el texto key=value&key=value de application/x-www-form-urlencoded. Con multipart, los campos de texto y los archivos binarios viajan en una sola solicitud.
Cómo funciona multipart/form-data
La cabecera Content-Type indica un boundary, una cadena que no puede aparecer en ningún punto del contenido. Cada parte empieza con -- más el boundary, y el cuerpo termina con --, el boundary y otra vez --.
POST /api/upload HTTP/1.1
Host: api.example.com
Content-Type: multipart/form-data; boundary=----example7MA4YW
------example7MA4YW
Content-Disposition: form-data; name="username"
alice
------example7MA4YW
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg
(binary JPEG bytes)
------example7MA4YW--- Cada parte necesita una cabecera
Content-Disposition: form-datacon elnamedel campo. Las partes de archivo añaden además unfilename. - El
Content-Typepropio de cada parte es opcional. Si falta, la parte se lee comotext/plain. Los archivos deben indicar su tipo de medio, oapplication/octet-streamsi se desconoce. - No añadas
Content-Transfer-Encoding. La RFC 7578 indica que los emisores no deben usarlo en HTTP.
multipart/form-data frente a urlencoded y JSON
- multipart/form-data admite archivos y campos binarios, a cambio de la sobrecarga del boundary y de las cabeceras de cada parte.
- application/x-www-form-urlencoded es el valor por defecto de los formularios solo de texto. Es compacto, pero no sirve para archivos.
- application/json es la elección habitual para solicitudes de API solo de texto. No admite archivos directamente: las subidas necesitan multipart, un paso de subida aparte o base64 dentro del JSON (lo que aumenta los datos en aproximadamente un tercio).
Para ver mejor qué puede contener el cuerpo de una solicitud, consulta payloads de API y JSON.
Enviar multipart/form-data desde código
Navegador (fetch y FormData)
const fd = new FormData();
fd.append('username', 'alice');
fd.append('avatar', fileInput.files[0]);
await fetch('/api/upload', { method: 'POST', body: fd });Aquí no establezcas tú la cabecera Content-Type. MDN advierte de que, si lo haces, el navegador no puede añadir el boundary que generó y el servidor ya no puede dividir el cuerpo.
curl
curl -X POST https://api.example.com/upload \
-F "username=alice" \
-F "avatar=@photo.jpg"-F hace que curl envíe multipart/form-data, y el prefijo @ lee el valor de un archivo. Hay más opciones en el comando curl, y puedes probar una solicitud sin instalar nada con la herramienta gratuita curl online.
Python (requests)
import requests
requests.post(
'https://api.example.com/upload',
data={'username': 'alice'},
files={'avatar': open('photo.jpg', 'rb')},
)k6
import http from 'k6/http';
const img = open('./photo.jpg', 'b');
export default function () {
http.post('https://api.example.com/upload', {
username: 'alice',
avatar: http.file(img, 'photo.jpg', 'image/jpeg'),
});
}Pasar http.file() dentro de un objeto de cuerpo es la forma en que k6 sube un archivo. Carga el archivo una sola vez en la fase de inicio con open(path, 'b'), no dentro de la función por defecto.
Cómo se lee en el servidor
El servidor necesita un parser multipart: Multer en Express, request.FILES en Django, request.files en Flask, UploadFile en FastAPI, MultipartFile en Spring. Los parsers difieren en si guardan los archivos en memoria o los envían a disco por streaming, así que compruébalo antes de aceptar subidas grandes.
Trata todo lo que llega en una parte como no fiable. La RFC 7578 indica a los receptores que no usen un nombre de archivo tal cual y que descarten cualquier ruta de directorio que contenga. El Content-Type de la parte también es solo una afirmación del cliente, así que determina el tipo real del archivo a partir de su contenido.
Errores habituales
- 413 Content Too Large. El cuerpo supera un límite del servidor o del proxy. nginx fija
client_max_body_sizeen 1m por defecto, así que una foto normal falla hasta que subas ese valor. - 400 por boundary ausente. La cabecera Content-Type no incluía el parámetro
boundary, o no coincidía con el cuerpo. Suele deberse a haber fijado la cabecera a mano. - El campo de archivo llega vacío. El nombre del campo en el formulario o en el código no coincide con el que espera el servidor, o el cliente sobrescribió el boundary.
- Nombres de archivo ilegibles. Los nombres no ASCII necesitan UTF-8 de forma coherente. La RFC 7578 permite la codificación porcentual en los nombres y desaconseja aquí la forma
filename*de la RFC 5987. - Subidas que se atascan en conexiones lentas. Los archivos muy grandes conviene enviarlos directamente a un almacenamiento de objetos con una URL firmada, o con un protocolo reanudable, en lugar de pasar por tu servidor de API.
Probar subidas multipart bajo carga
- Usa tamaños y contenidos de archivo realistas. Los marcadores diminutos ocultan el coste de ancho de banda y de procesamiento, y los bytes aleatorios no se comprimen como las imágenes y los PDF.
- Prueba justo por debajo y por encima del límite de tamaño y comprueba que se devuelve un 413 limpio, no un 500.
- Lanza las subidas de forma concurrente para ver si el servidor guarda los archivos en memoria y se queda sin ella.
- Prueba desde las regiones de tus usuarios, porque la velocidad de subida depende mucho de la distancia.
LoadFocus ejecuta scripts de JMeter y k6 desde 25 regiones en la nube, de modo que un script de subida como el de k6 de arriba se puede lanzar desde varias ubicaciones a la vez. Para partir de un comando cURL con opciones -F, consulta convertir cURL, HAR, Postman u OpenAPI en una prueba de carga (en inglés). Para ver cómo cambian los parámetros según el tipo de cuerpo, lee How are Parameters Sent in an HTTP POST Request? (en inglés). Lo dicho sobre las pruebas de API también vale para los endpoints de subida.
Preguntas frecuentes sobre multipart/form-data
¿Por qué el boundary va en la cabecera Content-Type?
El parser necesita el boundary antes de poder leer el cuerpo, y no puede buscarlo dentro de un cuerpo que aún no ha dividido. En la cabecera está disponible desde el principio.
¿Puedo usar multipart/form-data sin archivos?
Sí, funciona también con campos de solo texto. Para solicitudes solo de texto, urlencoded o JSON son más ligeros porque no hay sobrecarga por parte.
¿Qué diferencia hay entre multipart/form-data y multipart/mixed?
multipart/form-data es para envíos de formularios, y cada parte lleva un Content-Disposition con el nombre del campo. multipart/mixed es el contenedor genérico que se usa en el correo electrónico y otros mensajes MIME.
¿Cómo envío JSON y un archivo a la vez?
Envía dos partes en una misma solicitud multipart: una parte de texto con el JSON (con su propio Content-Type application/json) y una parte de archivo. Incrustar el archivo en base64 dentro del JSON también funciona, pero aumenta los datos en aproximadamente un tercio.
¿Por qué falla mi subida si fijo el Content-Type a mano?
Porque al valor le falta el boundary, o este no coincide con el cuerpo. Deja la cabecera sin definir y que la genere tu cliente HTTP o el navegador.
¿Cuál es el tamaño máximo de subida?
HTTP no fija ninguno. El límite lo pone tu servidor, proxy o gateway: nginx usa 1m por defecto y se puede subir con client_max_body_size. Para archivos muy grandes, usa URL firmadas hacia un almacenamiento de objetos o una subida reanudable.
Términos relacionados
- ¿Qué es Swagger? Swagger y OpenAPI explicados
- Operaciones CRUD: Create, Read, Update, Delete explicado
- ¿Qué son los ataques a APIs?
- ¿Qué son las cookies de API? Definición y seguridad
- Métricas de rendimiento de API: latencia, throughput y tasa de errores
- ¿Qué son las API internas?
- ¿Qué es un payload en una API? JSON, XML, ejemplos
- ¿Qué es un endpoint de API? Definición, ejemplos y buenas prácticas
Herramientas LoadFocus relacionadas
Lleva este concepto a la práctica con LoadFocus, la misma plataforma que potencia todo lo que acabas de leer.