11

I need to PUT an image file from the react native app to an pre-configured s3 upload URL. I saw an example with fetch that uploads the image as a binary via path, but it does so as a multi-part form upload. Because the s3 upload url can only take it as a raw binary in the body - and not as a content-type of multi-part, what's the syntax to PUT the raw binary image as the body using fetch or any other library in react native?

The following code uploads it as form data - which is not what I want to do.

          var photo = {
            uri: response.uri,
            name: fileName,
          };
          const body = new FormData();  // how can I do this not as a form?
          body.append('photo', photo);
          const results = await fetch('https://somes3uploadurl.com, {
            method: 'PUT',
            body,
          });
MonkeyBonkey
  • 46,433
  • 78
  • 254
  • 460

4 Answers4

17

It turns out you can send the file in multiple ways, including base64 and as a Buffer.

Using react-native-fs and buffer:

Uploading as base64 worked, but the image was damaged somehow. So i uploaded using a buffer:

export const uploadToAws = async (signedRequest, file) => {
  const base64 = await fs.readFile(file.uri, 'base64')
  const buffer = Buffer.from(base64, 'base64')
  return fetch(signedRequest, {
    method: 'PUT',
    headers: {
    'Content-Type': 'image/jpeg; charset=utf-8',
    'x-amz-acl': 'public-read',
   },
    body: buffer,
  })
}

Note that on the server, you need to make sure you are setting the correct Content-Type: { ContentType: "image/jpeg; charset=utf-8", 'x-amz-acl': 'public-read' } as it seems fetch adds the charset to Content-Type.

5

You also may use next solution:

  /**
   * @param {{contentType: string, uploadUrl: string}} resourceData for upload your image
   * @param {string} file path to file in filesystem 
   * @returns {boolean} true if data uploaded
   */
  async uploadImage(resourceData, file) {
    return new Promise((resolver, rejecter) => {
      const xhr = new XMLHttpRequest();

      xhr.onload = () => {
        if (xhr.status < 400) {
          resolver(true)
        } else {
          const error = new Error(xhr.response);
          rejecter(error)
        }
      };
      xhr.onerror = (error) => {
        rejecter(error)
      };

      xhr.open('PUT', resourceData.uploadUrl);
      xhr.setRequestHeader('Content-Type', resourceData.contentType);
      xhr.send({ uri: file });
    })
  }

And call this function from your code like:

  let isSuccess = await uploadImage({
    contentType: "image/jpeg",
    uploadUrl: "http://my.super.web.amazon.service..."
  }, "file:///path-to-file-in-filesystem.jpeg")

Source: https://github.com/react-native-community/react-native-image-picker/issues/61#issuecomment-297865475

whalemare
  • 1,107
  • 1
  • 13
  • 30
  • 2
    This definitely needs to be the new accepted answer. Reading the file into base64 and posting can be extremely heavy on memory usage. This method does not cause such an issue as it is reading from the filesystem directly. – Daniel Abdelsamed Jul 20 '19 at 06:06
0

You don't need to use react-native-fs or that buffer library. Instead just read the file in using https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsArrayBuffer as pass the result to fetch's body parameter. readAsBinaryString and readAsDataUrl gave me weird results.

Note: I didn't have to append "; charset=utf-8" either to my content-type header.

Example:

const fileReader = new FileReader();
fileReader.addEventListener('load', (event: any) => {
  const buffer = event.target.result;
  return fetch(link.url, {
    method: 'PUT',
    body: buffer,
  })
    .then((response) => {
      if (!response.ok) {
        throw new Error(
          `${response.status}: ${response.statusText}`
        );
      }
      return response;
    })
    .then(resolve, reject);
});
fileReader.addEventListener('error', reject);
fileReader.addEventListener('abort', reject);
fileReader.readAsArrayBuffer(file);
Per Christian Henden
  • 1,514
  • 1
  • 16
  • 26
Glitches
  • 742
  • 2
  • 8
  • 18
  • 1
    What parameter should one pecify to readAsArrayBuffer? I have an image selected with react-native-image-picker and all I have is a file uri (starting with content://) and a file path – nicecatch Apr 05 '18 at 08:47
  • 1
    Care to show an example? The issue here suggests this API isn't in place yet though; https://github.com/facebook/react-native/issues/21209 – febeling Apr 03 '19 at 13:13
0

This worked for me without any external dependencies.

If the fetch method is provided a URI that is on the device it will return the contents of that file, so just pass in the local file path.

export const getBlob = async (fileUri) => {
  const resp = await fetch(fileUri)
  const imageBody = await resp.blob()
  return imageBody
}

export const uploadImage = async (uploadUrl, fileUri) => {
  const imageBody = await getBlob(fileUri)

  return fetch(uploadUrl, {
    method: 'PUT',
    body: imageBody,
  })
}
sensi
  • 36
  • 3