Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uploading blob to S3 signed url, results in empty file #5915

Open
vanenshi opened this issue Sep 24, 2023 · 11 comments
Open

Uploading blob to S3 signed url, results in empty file #5915

vanenshi opened this issue Sep 24, 2023 · 11 comments

Comments

@vanenshi
Copy link

Describe the bug

Related to #3442

Sending a blob using

  1. axios.put
  2. to the a signed url received from our backend

results in an empty file on the S3 bucket. The request has a Content-Length of a few bytes, so s3 is not the origin of the problem

To Reproduce

export async function uploadImage(
  opt: UploadImageVariables,
): Promise<UploadImageResponse> {
  const response = await instance.post<UploadImageResponse>(
    uploadImageURL,
    opt.uploadImageInput,
  );

  var uploadUrl = response.data.signedUrl;

  const imageFilePath = opt.path;
  const ImageResponse = await fetch(imageFilePath);
  const imageBlob = await ImageResponse.blob();

  await axios.put(uploadUrl, imageBlob, {
    headers: {
      ...response.data.requestHeaders,
      'Content-Type': opt.uploadImageInput.contentType,
      'Content-Length': imageBlob.size.toString(),
    },
  });

  return response.data;
}

Code snippet

No response

Expected behavior

using the exact function with same headers and blob, fetch and XMLHttpRequest works fine

fetch

export async function uploadImage(
  opt: UploadImageVariables,
): Promise<UploadImageResponse> {
  const response = await instance.post<UploadImageResponse>(
    uploadImageURL,
    opt.uploadImageInput,
  );

  var uploadUrl = response.data.signedUrl;

  const imageFilePath = opt.path;
  const ImageResponse = await fetch(imageFilePath);
  const imageBlob = await ImageResponse.blob();

  await fetch(uploadUrl, {
    method: 'PUT',
    headers: {
      ...response.data.requestHeaders,
      'Content-Type': opt.uploadImageInput.contentType,
      'Content-Length': imageBlob.size.toString(),
    },
    body: imageBlob,
  });

  return response.data;
}

XMLHttpRequest

export async function uploadImage(
  opt: UploadImageVariables,
): Promise<UploadImageResponse> {
  const response = await instance.post<UploadImageResponse>(
    uploadImageURL,
    opt.uploadImageInput,
  );

  var uploadUrl = response.data.signedUrl;

  const imageFilePath = opt.path;
  const ImageResponse = await fetch(imageFilePath);
  const imageBlob = await ImageResponse.blob();

  await uploadObjectToS3(uploadUrl, imageBlob, {
    ...response.data.requestHeaders,
    'Content-Type': opt.uploadImageInput.contentType,
    'Content-Length': imageBlob.size.toString(),
  });

  return response.data;
}

// axios don't upload the blob, so we are using the XMLHttpRequest
// https://github.com/axios/axios/issues/3442
const uploadObjectToS3 = (
  uploadUrl: string,
  blob: Blob,
  headers?: Record<string, string>,
  onProgress?: (event: ProgressEvent) => void,
) => {
  return new Promise(function (resolve, reject) {
    const xhr = new XMLHttpRequest();

    xhr.open('PUT', uploadUrl);

    xhr.onerror = (err): void => {
      reject({
        status: xhr.status,
        statusText: xhr.statusText,
        ...err,
      });
    };

    xhr.upload.onerror = (arg): void => {
      console.error('ERR upload error', arg);
    };

    xhr.upload.onprogress = (evt): void => {
      console.log(Math.round((evt.loaded / evt.total) * 100) + '%');

      onProgress?.(evt);
    };

    xhr.onload = (ev): void => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve(xhr.response);
      } else {
        reject({
          status: xhr.status,
          statusText: xhr.statusText,
        });
      }
    };

    if (headers) {
      Object.entries(headers).forEach(([key, value]) => {
        xhr.setRequestHeader(key, value);
      });
    }

    xhr.send(blob);
  });
};

Axios Version

1.5.0

Adapter Version

No response

Browser

No response

Browser Version

No response

Node.js Version

No response

OS

No response

Additional Library Versions

react-native `0.72.4`

Additional context/Screenshots

No response

@puneethkumarvh
Copy link

Is this issue open or closed ?

@vanenshi
Copy link
Author

Is this issue open or closed ?

@puneethkumarvh Hi Puneeth, It's open and relevant, we had to use the alternative methods to upload blob in our project

@hunkydoryrepair
Copy link

Do you know what the Content-Type is that is being used?
Axios will encode to the given Content-Type in the header, which could potentially change the Content-Length, leaving them incorrect. Fetch and XHR will just pass the blob as is.

@vanenshi
Copy link
Author

Axios will encode to the given Content-Type in the header, which could potentially change the Content-Length, leaving them incorrect. Fetch and XHR will just pass the blob as is.

I tested using the image/png and image/jpg, I also manually provided the Content-Length as you see. no luck

@ianitow
Copy link

ianitow commented Sep 30, 2023

Could you give more details of the environment?

I tried to reproduce the following steps:

Create an account in Amazon

  • Create a bucket in s3
  • Create a user and give full access to s3's resources
  • Generate a signed a url using the package aws-sdk
const url = await s3.getSignedUrlPromise('putObject',  {
        Bucket: 'ianitobucket',
        Key: 'arquivo_para_teste',
        Expires: 60*60*24*7,
        ContentType: 'image/png',
      });

The result is a URL pre-signed, with it I used Axios to make a request using the code:

import axios from 'axios';
const uploadUrl = 'https://ianitobucket.s3.amazonaws.com/...';

const imageFilePath = 'https://files.tecnoblog.net/wp-content/uploads/2021/10/logotipo-da-empresa-amazon.png'; // URL EXAMPLE

const ImageResponse = await fetch(imageFilePath);
const imageBlob = await ImageResponse.blob();


await axios.put(uploadUrl, imageBlob, {
    headers: {
        'Content-Type': 'image/png',
        'Content-Length': imageBlob.size.toString(),
    },
});
console.log('uploaded');

Everything seems okay, the image was uploaded with content.

Environment:

Node: v18.17.1
Axios: 1.5.0 and 1.5.1

@vanenshi
Copy link
Author

@ianitow I am running the code in react-native 0.72.4, its using facebook/hermes (https://github.com/facebook/hermes) as js engine.
i guess, as your tests indicate, problem might be caused by hermes

@znareak
Copy link

znareak commented Dec 27, 2023

Same here, i get the same error :T

@ManucherKM
Copy link

ManucherKM commented Jan 8, 2024

Maybe this will help someone. The problem was on the surface. As it turned out, FileList is not an array, although it has some array properties. Here is a simplified code that didn't work fine:
image
Here is the code that worked fine. As you can see, it was necessary to convert the FileList to an array.
image

@aaronabf
Copy link

Our team is encountering this as well.

@duongquang1611
Copy link

Any have solution, i am using react native 0.74, so tired

@jurisjansons
Copy link

How is this still an issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants