React Native upload to S3 with presigned URL

13,672

Solution 1

FormData will create a multipart/form-data request. S3 PUT object needs its request body to be a file.

You just need to send your file in the request body without wrapping it into FormData:

function uploadFile(file, signedRequest, url) {
  const xhr = new XMLHttpRequest();
  xhr.open('PUT', signedRequest);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      if(xhr.status === 200) {
        alert(url);
      } else {
        alert('Could not upload file.');
      }
    }
  };
  xhr.send(file);
};

See https://devcenter.heroku.com/articles/s3-upload-node for example in a browser. Please also ensure your Content-Type header is matched with the signed URL request.

Solution 2

I've wasted way too much time on uploading to pre-signed S3 URL on both iOS and Android. What worked for me was rn-fetch-blob lib

Code snippet:

import RNFetchBlob from 'rn-fetch-blob'

const preSignedURL = 'pre-signed url'
const pathToImage = '/path/to/image.jpg' // without file:// scheme at the beginning
const headers = {}

RNFetchBlob.fetch('PUT', preSignedURL, headers, RNFetchBlob.wrap(pathToImage))

Solution 3

"rn-fetch-blob": 0.12.0,
"react-native": 0.61.5

This code works for both Android & iOS

const response = await RNFetchBlob.fetch(
  'PUT',
  presignedUrl,
  {
    'Content-Type': undefined
  },
  RNFetchBlob.wrap(file.path.replace('file://', '')),
)

Note {'Content-Type': undefined} is needed for iOS

Share:
13,672
Cole
Author by

Cole

Updated on July 08, 2022

Comments

  • Cole
    Cole almost 2 years

    Been trying with no luck to upload an image to S3 from React Native using pre-signed url. Here is my code:

    generate pre-signed url in node:

    const s3 = new aws.S3();
    
    const s3Params = {
      Bucket: bucket,
      Key: fileName,
      Expires: 60,
      ContentType: 'image/jpeg',  
      ACL: 'public-read'
    };
    
    return s3.getSignedUrl('putObject', s3Params);
    

    here is RN request to S3:

    var file = {
      uri: game.pictureToSubmitUri,
      type: 'image/jpeg',
      name: 'image.jpg',
    };
    
    const xhr = new XMLHttpRequest();
    var body = new FormData();
    body.append('file', file);
    xhr.open('PUT', signedRequest);
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4){
        if(xhr.status === 200){
          alert('Posted!');
        }
        else{
          alert('Could not upload file.');
       }
     }
    };
    xhr.send(body);
    

    game.pictureToSubmitUri = assets-library://asset/asset.JPG?id=A282A2C5-31C8-489F-9652-7D3BD5A1FAA4&ext=JPG

    signedRequest = https://my-bucket.s3-us-west-1.amazonaws.com/8bd2d4b9-3206-4bff-944d-e06f872d8be3?AWSAccessKeyId=AKIAIOLHQY4GAXN26FOQ&Content-Type=image%2Fjpeg&Expires=1465671117&Signature=bkQIp5lgzuYrt2vyl7rqpCXPcps%3D&x-amz-acl=public-read

    Error message:

    <Code>SignatureDoesNotMatch</Code>
    <Message>
    The request signature we calculated does not match the signature you provided. Check your key and signing method.
    </Message>
    

    I can successfully curl and image to S3 using the generated url, and I seem to be able to successfully post to requestb.in from RN (however I can only see the raw data on requestb.in so not 100% sure the image is properly there).

    Based on all this, I've narrowed my issue down to 1) my image is not correctly uploading period, or 2) somehow the way S3 wants my request is different then how it is coming in.

    Any help would be muuuuuucchhhh appreciated!

    UPDATE

    Can successfully post from RN to S3 if body is just text ({'data': 'foo'}). Perhaps AWS does not like mutliform data? How can I send as just a file in RN???

  • Inchoon Park
    Inchoon Park over 7 years
    Can you elaborate on ensuring that the Content-Type header is matched with the signed URL request? I do not quite understand what you mean by this.
  • Abner Terribili
    Abner Terribili almost 7 years
    You just need to set xhr.setRequestHeader('Content-Type', fileType)
  • Marklar
    Marklar almost 7 years
    I think S3 PUT requires Content-Length see docs, whereas S3 POST requires a file object see docs.
  • leomayleomay
    leomayleomay almost 6 years
    @edward-samuel, I am wondering how to achieve the same with Fetch instead of XMLHttpRequest
  • Roy Lee
    Roy Lee over 5 years
    you're my life saver!
  • Xavi A.
    Xavi A. almost 5 years
    This code worked for me on iOS, but not on Android. I get xhr.status = 0 but nothing is uploaded to S3.
  • Umang Loriya
    Umang Loriya about 4 years
    @Peter Machowski Is it working normally for large files too?
  • Peter Machowski
    Peter Machowski about 4 years
    @UmangLoriya I haven't tested it with files other than <10Mb images but I don't see a reason for not working with larger files.
  • Mayoul
    Mayoul almost 4 years
    Can you explain how's your headers object looks like ? I'm passing a FormData object containing all the needed infos with the presigned URL (bucket, region, key, ...), but then I'm getting a black screen when attempting to upload...
  • Peter Machowski
    Peter Machowski over 3 years
    @Mayoul there are no specific headers needed. Black screen suggest error in some other code.
  • highjump
    highjump about 3 years
    I tried this, but it crashes on iOS, have you experienced this?
  • sj_959
    sj_959 almost 3 years
    @PeterMachowski Thanks a lot! The file gets uploaded but when I try to download it, it says No Such Key. What could cause this ?