Usando o progresso do manipulador ao fazer upload de arquivos para o AWS S3 com a Reagir

0

Pergunta

Eu sou apenas recentemente lidar com o AWS SDK e, portanto, por favor, desculpa se a minha abordagem é um absurdo completo.

Eu quero fazer o upload de um simples arquivo de mídia para o meu S3. Eu estava seguindo este tutorial e até agora eu sou capaz de fazer upload de arquivos sem problema. Para userbility uma barra de progresso seria um bom extra e, portanto, eu estava pesquisando como fazer isso. Rapidamente descobri que o atual AWS SDK v3 não suporta httpUploadProgress mais , mas devemos usar @aws-sdk/lib-storage em vez disso. Usando esta biblioteca, eu ainda sou capaz de fazer upload de arquivos para o S3, mas eu não posso obter o controlador do progresso para o trabalho! Suponho que isso tenha algo a ver comigo, não entender como lidar com async dentro de um Reagir componente.

Então aqui está a minha minified componente de exemplo (estou usando o Chakra UI aqui)

const TestAWS: React.FC = () => {
  const inputRef = useRef<HTMLInputElement | null>(null);
  const [progr, setProgr] = useState<number>();

  const region = "eu-west-1";
  const bucketname = "upload-test";

  const handleClick = async () => {
    inputRef.current?.click();
  };

  const handleChange = (e: any) => {

    console.log('Start file upload');

    const file = e.target.files[0];
    const target = {
      Bucket: bucketname,
      Key: `jobs/${file.name}`,
      Body: file,
    };

    const s3 = new S3Client({
      region: region,
      credentials: fromCognitoIdentityPool({
        client: new CognitoIdentityClient({ region: region }),
        identityPoolId: "---MY ID---",
      }),
    });

    const upload = new Upload({
      client: s3,
      params: target,
    });

    const t = upload.on("httpUploadProgress", progress => {
      console.log("Progress", progress);

      if (progress.loaded && progress.total) {
        console.log("loaded/total", progress.loaded, progress.total);
        setProgr(Math.round((progress.loaded / progress.total) * 100)); // I was expecting this line to be sufficient for updating my component
      }
    });
    await upload.done().then(r => console.log(r));
  };

console.log('Progress', progr);

return (
    <InputGroup onClick={handleClick}>
      <input ref={inputRef} type={"file"} multiple={false} hidden accept='video/*' onChange={e => handleChange(e)} />
      <Flex layerStyle='uploadField'>
        <Center w='100%'>
          <VStack>
            <PlusIcon />
            <Text>Choose Video File</Text>
          </VStack>
        </Center>
      </Flex>
      {progr && <Progress value={progr} />}
    </InputGroup>
  );
};

export default TestAWS;

Então, basicamente eu ver o evento de ser demitido (start upload de arquivo). Em seguida, ele leva um tempo e eu vejo a Promessa de resultado e o Progress, 100 no meu console. Isso significa para mim que a variável de estado é atualizado (pelo menos uma vez), mas o componente não re-renderizar?

O que é que eu estou fazendo de errado aqui? Qualquer ajuda apreciada!

amazon-s3 aws-sdk reactjs
2021-11-22 15:34:31
2

Melhor resposta

1

Tudo bem, eu encontrei a solução. A chamada de retorno na variável de estado funciona muito bem e faz o que deve. Mas a configuração do Upload objeto estava desligado. Depois de cavar a fonte que eu descobri que o ouvinte de evento só é acionado se o autor enviou mais dados. Porque Uploader pedaços uploads você tem duas separadas parâmetros de configuração que permitem que você divida a sua transferência para separar os pedaços. Então,

const upload = new Upload({
  client: s3,
  params: target,
  queueSize: 4,          // 4 is minimum
  partSize: 5*1024*1024  // 5MB is minimum
});

basicamente faz o trabalho quando o arquivo de upload é maior que 5MB! Só então, o evento é acionado novamente e atualiza a variável de estado.

Uma vez que este autor é feito para lidar com grandes carregamentos de ficheiros, esta totalmente faz sentido, e nós poderíamos simplesmente ajustar queueSize e partSize de acordo com o arquivo que deseja carregar. Algo como

let queueSize = 10;
const file = event.target.files[0];

let partSize = file.size / (10 * 1024 * 1024);    // 1/10th of the file size in MB

const upload = new Upload({
  client: s3,
  params: target,
  queueSize: partSize > 5 queueSize : undefined,
  partSize: partSize > 5 ? partsize : undefined
});

Obviamente, isso pode ser feito de forma muito mais sofisticada, mas eu não queria gastar muito tempo com isso, pois não é parte da pergunta original.

Conclusão

Se o arquivo for grande o suficiente (>5 MB), você vai ver o progresso de atualização, dependendo do número de blocos (de 5 mb ou mais) que você escolheu para dividir o seu arquivo.

Uma vez que este afeta somente o handleChange método do exemplo original, eu posto isto para a integralidade

const handleChange = async ( event ) => {
  const file = event.target.files[0]

  const target = {
    Bucket: 'some-S3-bucket',
    Key: `jobs/${file.name}`,
    Body: file,
  };

  const s3 = new S3Client({
    region: 'your-region',
    credentials: fromCognitoIdentityPool({
      client: new CognitoIdentityClient({ region: 'your-region' }),
      identityPoolId: "your-id",
    }),
  });

  // this will default to queueSize=4 and partSize=5MB
  const upload = new Upload({
    client: s3,
    params: target
  });

  upload.on("httpUploadProgress", progress => {
    console.log('Current Progress', progress);
    setProgr(progress);
  });

  await upload.done().then(r => console.log(r));
} 

Talvez isso ajude alguém que tem o mesmo problema.

2021-11-22 18:06:15
1

Me deparei com a sua resposta depois de ter exatamente o mesmo problema (com Vue) hoje!

De fato você está certo: o AWS SDK JS v3 evento só é acionado por uma parte que não é de todo claro e eu desperdício de tempo de depuração que demais. Como para um de 4 mb do arquivo, ele sempre fogo em 100%.

Como você diz, você pode experimentar com a parte de tamanho , mas o mínimo é de 5MB e então em uma conexão lenta achei ele pode parecer que um carregamento é preso como você tem que esperar para 5MB para obter quaisquer dados. Hmm. Então o que eu fiz foi olhar para o tamanho do arquivo a ser carregado. E se ele está em um limiar (diga-se de 25MB, ou o que for aplicável), bem, é provavelmente seguro para fazer o upload de uma vez como você realmente não precisa de multipart para upload. E então, eu também fiz um presigned URL (https://aws.amazon.com/blogs/developer/generate-presigned-url-modular-aws-sdk-javascript/) que pode ser usado para COLOCAR utilizando axios (desde fetch não suporta eventos de progresso ainda).

Então, dessa forma você pode usar upload para arquivos grandes (onde você realmente precisa multipart upload e onde 5MB como uma porcentagem do tamanho do arquivo é pequeno), e usar um presigned URL para arquivos pequenos e assim ficar muito mais freqüentes atualizações.

O mesmo progresso manipulador de eventos pode ser usado por ambos.

this.$axios
  .request({
     method: "PUT",
     url: SIGNED-URL-HERE,
     data: file,
     timeout: 3600 * 1000,
     onUploadProgress: this.uploadProgress,
  })
  .then((data) => {
     console.log("Success", data);
  })
  .catch((error) => {
     console.log("Error", error.code, error.message);
  });

Não é o ideal, mas ajuda.

2021-11-24 00:54:55

Eu tive a mesma idéia, mas para ser justo, eu acho que lib-storage nunca foi concebido para ser usado para pequenos carregamentos de arquivo. Infelizmente, parece que não há atualmente nenhuma solução satisfatória quando usando v3 (desde que usando a busca api sob o capô) e upload de arquivos pequenos. Assim, a sua abordagem é, definitivamente, uma boa solução, mas espero que eles vão implementar algo no SDK muito em breve.
Flo Ragossnig

Estou de acordo. É chato ter que usar uma assinado URL como uma solução alternativa, mas, a menos que/até que o SDK alterações (talvez quando a buscar API adiciona progresso do upload) para agora parece que você tem para escolher, dependendo se em várias partes ou regular as atualizações de progresso é o mais importante para o seu uso
coder_uk

Em outros idiomas

Esta página está em outros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................