Subidas directas a AWS S3 desde el navegador (Increíble mejora de rendimiento)

Santiago Quinteros - CEO & CTO - Software on the road
By:
Santiago Quinteros

Subida directa a la infraestructura del s3

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.

Problema de la replicación de carga de objetos

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

Consola AWS buscando el servicio IAM

  • Crear un usuario con acceso programado

Creando un usuario de AWS con acceso programado

  • Hacer click en agregar políticas existentes

Elegir adjuntar la política existente

  • 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/*",
            ]
        }
    ]
}

Elegir adjuntar la política existente

  • 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

Pestaña de propiedades

  • Hacer click en los permisos

Aceleración de transferencia

  • Desplácese a aceleración y actívela

Aceleración de transferencia

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.

Rendimiento de la aceleración de transferencia de AWS S3

Puedes probar esta impresionante herramienta de estimación de rendimiento aquí mismo

Recursos

Get the latest articles in your inbox.

Join the other 2000+ savvy node.js developers who get article updates. You will receive only high-quality articles about Node.js, Cloud Computing and Javascript front-end frameworks.


santypk4

CEO at Softwareontheroad.com