Por qué querrías subir archivos a un S3 privado directamente desde el navegador?
Bueno, si tu aplicación está subiendo un archivo a tu servidor, y luego tu servidor lo sube a un S3 Bucket de AWS, tienes un cuello de botella y problemas de rendimiento.
Mis clientes subíanarchivos de videos grandes, 100mb promedio, desde varios lugares: Asia, Europa y Norteamérica, mi servidor está alojado en Heroku y localizado en Virginia del Norte pero mi principal S3 Bucket está en Irlanda!
Será más fácil y eficiente si el cliente de la web tiene la posibilidad de subir directamente a ese S3 bucket.
Parece trivial, pero puede que se enfrente a varios problemas y la documentación oficial de la AWS no le dice mucho.
El procedimiento
Necesitarás generar URLs AWS S3 "pre-firmadas", para que un usuario pueda escribir un objeto directamente con una llamada POST o PUT.
URL pre-firmada (Método HTTP PUT)
Una URL "pre-firmada" es una URL que se genera con sus credenciales de AWS y que provee a los usuarios el poder de dar acceso temporal a un objeto de AWS S3 específico.
Las URLs pre-firmadas son útiles si quieres que tu usuario/cliente pueda subir un objeto específico a su bucket, pero no requiere que tengan credenciales o permisos de seguridad de AWS.
Cuando crea una URL pre-firmada, debe proporcionar sus credenciales de seguridad y luego especificar un nombre para elbucket, una clave de objeto, un método HTTP (PUT para subir objetos) y una fecha y hora de caducidad.
Las URL pre-firmadas son válidas sólo durante el tiempo especificado.
Alternativa (Método de formulario HTTP POST)
AWS S3 soporta POST, lo que permite a sus usuarios subir contenidos directamente a AWS S3.
POST está diseñado para simplificar las subidas, reducir la latencia de las subidas y ahorrarle dinero en las aplicaciones en las que los usuarios suben datos para almacenarlos en AWS S3.
Generar credenciales
- Abre la consola AWS y navega a IAM
- Crear un usuario con acceso programado
- Hacer click en agregar políticas existentes
- Hacer click en crear su propia política y copie lo siguiente
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:Put*"
],
"Resource": [
"arn:aws:s3:::your-bucket-name/*",
]
}
]
}
-
Hacer click en Revisar política e introduzca un nombre para la política.
-
Guarda la política.
-
Añádela a tu nuevo usuario.
Configurando la política de S3 CORS
La política de "mismo origen" es un importante concepto de seguridad implementado por los navegadores web para evitar que el código Javascript haga peticiones contra un dominio diferente al que se sirvió.
Cross-Origin Resource Sharing (CORS) es una técnica para relajar la política de "mismo origen", permitiendo que Javascript en una página web haga llamadas HTTP a un origen diferente.
CORS facilita a los proveedores de servicios la distribución de contenidos a los usuarios, al tiempo que añade interoperabilidad a los servicios en línea.
-
Ve a tu bucket
-
Ve a la pestaña de permisos
-
Hacer click en la configuración de CORS, copie y pegue lo siguiente
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
El CORS facilita que los servicios web se integren rápida y fácilmente sin exponer a sus usuarios.
Activando el Endpoint de aceleración de transferencia
La aceleración de transferencia de AWS S3 es una característica de nivel de bucket que permite una transferencia de datos más rápida hacia y desde AWS S3.
-
Vaya a su bucket
-
Elija las propiedades
- Hacer click en los permisos
- Desplácese a aceleración y actívela
Código de servidor - PUT a un endpoint de aceleración de transferencia
Tienes dos opciones para generar la URL pre-firmada, dependiendo de cómo tu código de cliente suba el archivo.
Con este enfoque se crea un endpoint PUT pero no se puede usar Multi-Part Form Data, aunque se beneficia de usar AWS Transfer acceleration Dependemos en el método "GetSignedUrl de AWS-SDK".
Lea más sobre ello en el AWS S3 SDK
const AWS = require('aws-sdk');
const express = require('express');
const route = express.Router();
route.get('/signed-url-put-object', async (req, res) => {
AWS.config.update({
accessKeyId: 'AAAAAAAAAAAAAAAA', // Generado en el paso 1
secretAccessKey: 'J21//xxxxxxxxxxx', // Generado en el paso 1
region: 'eu-west-1', // Debe ser el mismo que tu bucket
signatureVersion: 'v4',
});
const params = {
Bucket: 'your-bucket-name',
Key: 'my-awesome-object.webm',
Expires: 30 * 60, // 30 minutos
ContentType: 'video/webm'
};
const options = {
signatureVersion: 'v4',
region: 'eu-west-1', // igual que tu cubo
// resaltar-siguiente-línea
endpoint: new AWS.Endpoint('your-bucket-name.s3-accelerate.amazonaws.com'),
// resaltar-siguiente-línea
useAccelerateEndpoint: true,
}
const client = new AWS.S3(options);
const signedURL = await (new Promise((resolve, reject) => {
// resaltar-siguiente-línea
client.getSignedUrl('putObject', params, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
});
}));
return res.json({
signedURL,
})
}
Código de servidor - POST Multi-Parts FormData
Obtener una política POST pre-firmada para apoyar la subida a S3 directamente desde un formulario HTML del navegador.
Con esto, generarás un FORM y debes enviar todos los campos en un objeto FormData en una petición POST al bucket de AWS S3.
No puedes usar el endpoint de aceleración de transferencia porque es un endpoint de CloudFront que no está configurado con las opciones CORS necesarias y tristemente no puedes cambiarlo.
Pero esto es útil si se está desarrollando una aplicación nativa de react y se tiene la necesidad de usar un FormData o cualquier otro escenario en el que se debe usar subidas de multi-parts.
Para este método dependemos del método "CreatePresignedPost" de AWS-SDK, por favor note la diferencia con el método anterior.
Lea más sobre ello en el AWS S3 SDK
No puedes usar la aceleración de transferencia con este método*
const AWS = require('aws-sdk');
const express = require('express');
const route = express.Router();
route.get('/signed-form-upload', async (req, res) => {
AWS.config.update({
accessKeyId: 'AAAAAAAAAAAAAAAA', // Generado en el paso 1
secretAccessKey: 'J21//xxxxxxxxxxx', // Generado en el paso 1
region: 'eu-west-1', // Debe ser el mismo que tu bucket
signatureVersion: 'v4',
});
const params = {
Bucket: 'your-bucket-name',
Key: 'my-awesome-object.webm',
Fields: {
Key: 'my-awesome-object.webm',
},
};
const options = {
signatureVersion: 'v4',
region: 'eu-west-1', // igual que tu bucket
// resaltar-siguiente-línea
endpoint = new AWS.Endpoint('https://your-bucket-name.s3.amazonaws.com'),
// resaltar-siguiente-línea
useAccelerateEndpoint = false,
// resaltar-siguiente-línea
s3ForcePathStyle = true,
}
const client = new AWS.S3(options);
const form = await (new Promise((resolve, reject) => {
// resaltar-siguiente-línea
client.createPresignedPost(params, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data)
}
});
}));
return res.json({
form: { ...form, url: config.aws.s3.AWS_S3_ENDPOINT }
})
}
Problemas comunes
"La firma no coincide "
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<StringToSignBytes>90 81 89 12 ...</StringToSignBytes>
<RequestId>G7AAF1689RC5909C</RequestId>
<HostId>q+r+2T5K6mMKLKTWw0R9/jm33LyIfZFACY8GEDznfmMrRxvaVJwPiu/hlofuJWbW</HostId>
<StringToSign>PUT
video/webm
456789067
x-amz-acl:authenticated-read
/your-bucket-name/</StringToSign>
<AWSAccessKeyId>youraccesskey</AWSAccessKeyId>
</Error>
S3 crea una firma combinando el tipo de archivo, la clave del archivo, el tipo de contenido, etc.
Si tienes este problema, comprueba:
-
Asegúrate de que estás pasando el encabezado de tipo de contenido correcto.
-
Comprueba que estás usando el método de carga de formularios con POST, o PUT con el endpoint de aceleración de transferencia.
-
El tipo de archivo y la clave de archivo Debe coincidir exactamente con la que se proporcionó cuando se creó la URL pre-firmada.
-
Cuando utilice el método POST FormData, compruebe que está enviando todos los campos del formulario que fueron generados por AWS S3 SDK.
Conclusión
Hay varias maneras de subir archivos a un bucket de AWS S3 directamente desde el navegador, y puede ser desafiante y confuso, pero con un poco de esfuerzo, tendrás una gran mejora en tu rendimiento.
En mi caso la mejora de rendimiento fue de alrededor del 200% gracias al endpoint de aceleración de transferencia de la AWS S3.
Puedes probar esta impresionante herramienta de estimación de rendimiento aquí mismo
Recursos
-
https://www.digitalocean.com/community/questions/signed-put-url-for-nodejs
-
https://sanderknape.com/2017/08/using-pre-signed-urls-upload-file-private-s3-bucket/
-
https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html
-
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html
-
https://docs.aws.amazon.com/AmazonS3/latest/user-guide/enable-transfer-acceleration.html