简体   繁体   中英

Pre-signed Post Uploads to S3 fails intermittently in Safari when uploading file via multipart/form-data content type

I've been experiencing pre-signed post upload failures to S3 that I cannot reproduce. The failure happens mostly via Mobile Safari browsers, but my error log has shown that the error occurs on Desktop Safari as well.

When the error occurs, I get the following error back from S3:

<Error>
    <Code>EntityTooSmall</Code>
    <Message>Your proposed upload is smaller than the minimum allowed size</Message> 
    <ProposedSize>0</ProposedSize>
    <MinSizeAllowed>26254270</MinSizeAllowed>
    <...></...>
</error>

The code I'm using to presign the S3 Url and subsequently upload the file works fine in other browsers, eg Chrome, Firefox, Brave.

Once I have the pre-signed URL, I'm creating a new FormData object and using the pre-signed fields to construct an object which I then send to S3 from the browser via Axios.

My guess is the error has something to do with Safari incorrectly appending the file data to the FormData object, but since it happens randomly, I can't figure out why.

Safari correctly reads the size of the file, as this value is used to create the presigned url, but when it comes time to attach it to the form data object and upload, something must be going wrong.

The following code is what I use with success on other browsers, and what works most of the time on safari:


<script>

import axios from 'axios'
import getPresignUrl from './presign'

// Vue Component

export default {
  name: 'FinickyOnSafariUploader'
  data: () => ({
    // ...
  }),
  methods: {
    presignAndUpload (file) {
      const fileData = {
        fileName: file.name,
        fileType: file.type,
        fileSize: file.size
      }

      getPresignUrl(fileData)
        .then((resp) => {
          const presignFields = resp.data.presignFields  // Object
          const presignUrl = resp.data.presignUrl  // String
          return this.uploadToS3(file, presignUrl, presignUrl, presignFields)
        })
    },
    uploadToS3 (file, presignUrl, presignFields) {
      formPostData = new FormData()

      // Add Presigned Post Fields for Auth
      for (const field in presignFields) {
        formPostData.append(field, presignFields[field])
      }

      // Add Content-Type & File Data
      formPostData.append('Content-Type', file.type)
      formPostData.append('file', file)

      const req = {
        url: presignUrl,
        method: 'POST',
        headers: { 'Content-Type': 'multipart/form-data' },
        data: formPostData
      }

      return axios.request(req)
    }
  }
}


</script>


Does anyone have any guidance/insight as to why this happens sporadically and only on Safari?

It looks like you need to wait for the methods that take a long time to finish, to finish doing what they need to do. You can do this with promises or using async / await. I suspect you would be able to reproduce this error on any browser if you upload a big enough file, that would make your functions return nothing instead of something when they are done.

You can use async / await on object methods like:

//First lets reproduce suspected error
const axios = require("axios");
const objError = {
  somefunction() {
    return axios.get("https://source.unsplash.com/random");
  },
};
console.log(objError.somefunction());

//the fix
const objFix = {
  async somefunction() {
    return await axios.get("https://source.unsplash.com/random");
  },
};

fixed = async () => {
  console.log(await objFix.somefunction());
};

fixed();

To convert your methods to do their job asynchronously and then wait for them you need to do something like:

export default {
//...
  methods: {
    async presignAndUpload(file) {
      await getPresignUrl(fileData).then((resp) => {
        //...
        return await this.uploadToS3(file, presignUrl, presignUrl, presignFields);
      });
    },
    async uploadToS3(file, presignUrl, presignFields) {
      //...
      return await axios.request(req);
    },
  },
};

Good evening) I think you should use async/await. I suppose your functions execute without waiting for finish. Write like

async function doUpload() {
  await operation1();
  await operation2();
  await operation3();
}

it can guarantee what all your functions executed correctly

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM