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

s3 putObject jpg uploads base64 data to s3 when ContentEncoding is set to base64 in React Native #1712

Closed
hggz opened this issue Sep 9, 2017 · 13 comments
Labels
guidance Question that needs advice or information. third-party This issue is related to third-party libraries or applications.

Comments

@hggz
Copy link

hggz commented Sep 9, 2017

Whenever I attempt to download an image on s3 that I uploaded using s3.putObject, the file is corrupt. This is what I'm doing.

  1. retrieve photo from device using ImagePicker for react native
  2. uploading image base64 data string with the following parameters:
var req = AWSHelper.s3.putObject({
            Key: photoKey,
            Body: data,
            ContentEncoding: 'base64',
            ContentType: 'image/jpeg',
            ACL: 'public-read',
        });
        req.on('httpUploadProgress', function (evt) {
            Debug.Log("Uploading file... Status:", evt);
        });
        req.send(function (err, data) {
            Debug.Log("S3 sent response:", data);
            if (err) {
                Debug.Log("Error uploading to S3", err);
            } else {
                alert('photo uploaded chill');
            }
        });
  1. image gets uploaded to s3, with proper size but when I try to download it, its corrupt.

Is there anything I'm doing wrong from that snippet?

NOTE
I tried using a filestream to transmit the data that way too, and the same behavior occurs.

RN: 0.36.0
AWS-SDK-JS: 2.111.0

@syberkitten
Copy link

Having a similar yet different issue

When using putObject, I don't receive back the Location property,
only Etag.

When using upload instead, I'm getting back Location property
but the object itself has 0 bytes.

It does not matter which size I upload, it becomes 0 Bytes.

@hggz
Copy link
Author

hggz commented Sep 10, 2017

Yeah so after spending way too much time on this, (it hasn't been resolved) I'll share my findings.

  1. I am able to send the base64 encoded data to aws s3 with putobject but don't receive the location property too, only the Etag.
  2. the item I put up on s3 is corrupted because the data uploaded wasnt its binary-string form.
  3. if I DO attempt to upload the data using its binary blob form I get an error with XMLHTTPRequest.js saying that 'the request has already been sent' midway through the upload.
  4. the main reason I tried using putObject instead of the recommended upload
    method was because I also receive the same 'XMLHTTPRequest.js' error mid upload. Looking at the XMLHTTPRequest error, its thrown when the 'readyState' property is set to true while its trying to perform a send operation.
  5. I was getting this behavior while utilizing the Cognito credentials.

:( my heart

@syberkitten
Copy link

syberkitten commented Sep 11, 2017

@hgonzalez94 It's quite mind bogging to me that Amazon
does not have such basic things figured out after so many years.

Sounds like It better to look for other solutions.
right now when i upload a file using putObject, it mostly times out
... and bunch of tickets on this:

#1235
#281

@chrisradek
Copy link
Contributor

@hgonzalez94
How is the data corrupted? Is the base64 string you download different from the base64 string you upload to S3? I don't believe setting ContentEncoding to base64 actually does anything from S3's side (i.e., they won't decode the string on upload), so your data should still be stored as a base64 encoded string representation of your image.

Are you trying to display your image in React Native? I did a quick test using the latest version of the SDK where I uploaded a base64 image to S3, then downloaded it and displayed it in an Image tag. If you're displaying the image in an Image tag, you do need to prepend the base64 image with something like data:image/jpg;base64, before you use it as your image source.

Here's my rough example (using TypeScript but should be easy to convert to JS):

/** App.js */
import React from 'react';
import { StyleSheet, Text, View, Button, Image } from 'react-native';
import {putObject} from './src/putObject';
import {getImage} from './src/getImage';

export default class App extends React.Component {
  render() {
    const state = this.state;  
    return (
      <View style={styles.container}>
        <Text>Open up App.js to start working on your app!</Text>
        <Text>Changes you make will automatically reload.</Text>
        <Text>Shake your phone to open the developer menu.</Text>
        <Button 
          title="Press Me"
          onPress={async () => {
            await putObject();
            this.setState({
              hasImage: true,
              image: await getImage()
            });

          }}
        />
        {state && state.hasImage && <Image
          style={{width: 100, height: 200, borderColor: 'red', borderWidth: 1}}
          source={{uri: state.image}}
         />}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
/** putObject.ts */
import * as S3 from 'aws-sdk/clients/s3';
import {image} from './image'; //base64 encoded image for testing

const s3 = new S3({
    region: 'us-west-2',
    credentials: {
        /* From somewhere */
    }
});

export async function putObject() {
    const data = await s3.putObject({
        Bucket: 'BUCKET',
        Key: 'test-image.jpg',
        ContentType: 'image/jpeg',
        Body: image
    }).promise();
}
/** getImage.ts */
import * as S3 from 'aws-sdk/clients/s3';

const s3 = new S3({
    region: 'us-west-2',
    credentials: {
        /* From somewhere */
    }
});

export async function getImage() {
    const data = await s3.getObject({
        Bucket: 'BUCKET',
        Key: 'test-image.jpg'
    }).promise();
    return 'data:image/jpg;base64,' + (data.Body as Buffer);
}

@hggz
Copy link
Author

hggz commented Sep 14, 2017

Hey chris thanks for your reply.

So by 'corrupted' (my apologies, it was a poor choice of words) I mean I'm unable to view the images on s3 from the aws side. I do upload the base 64 string fine but Ideally I would be able to upload the binary string using the sdk so I can view it from s3, or in any other medium I download.

I hope you can see why I would prefer to have it in its default image representable format thats generally easy to migrate among different platforms, instead of forcing any other application of mine to adhere to this standard Im setting solely from the react native.

Because of this, If I wanted to access my images, not only would I have to set up proper aws access credentials but I would also have to translate it from its base 64 string to its binary string, and thats just another step I don't want to have to take.

The method you uploaded above doesn't work for me. It times out and throws an XMLHTTPRequest error saying that 'The request has already been sent' mid upload. The putObjectRequest DOES work if I provide it the base64 data string itself. What you're doing above is taking an image object which, when it gets uploaded, has its binary string representation uploaded.

To anyone else interested in my work around, I chose to just post the binary string manually through a signed request I made, conforming to another web posting option aws offers.

@chrisradek
Copy link
Contributor

@hgonzalez94
Which platform are you testing on, Android or iOS?

I ran the same code as above, except I decoded the base64 string into a binary format before calling putObject. When I do that, the image is saved to S3 and does not need any transformations performed to view it. I have only run this test on iOS so far, and have react-native 0.47.0 installed. I've noticed differences in XMLHttpRequest behavior depending on which platform you're targeting so there may be some issue that has either been fixed, or I'm not seeing yet.

To decode the base64 image, I'm using AWS.util.base64.decode(image). It's not documented but unlikely to change.

@hggz
Copy link
Author

hggz commented Sep 14, 2017

I'm on iOS, v 0.36.0 like I posted above. When I try submitting the binary format XMLHttpRequest gives me an error mid upload saying "The request has already been sent". I can follow that error in the source for the request but I don't know why it happens mid way. Same behavior on android

@chrisradek
Copy link
Contributor

@hgonzalez94
Are you able to update your version of react native to see if that fixes your issue? On both iOS and android, using version 0.47.0, I'm not encountering these issues. I can set up a project using v0.36.0, but I suspect that something with the XMLHttpRequest implementation react-native shims changed to better support binary payloads since your version.

@hggz
Copy link
Author

hggz commented Sep 17, 2017

Hey @chrisradek I'll shelve this for now, but I am unable to upgrade to that version of react native at this time. I have a work around for now, but I'll have to see about upgrading at a later time.

@jeskew jeskew changed the title s3 putObject jpg uploads corrupted data to s3 react native s3 putObject jpg uploads base64 data to s3 when ContentEncoding is set to base64 in React Native Sep 20, 2017
@jeskew
Copy link
Contributor

jeskew commented Sep 20, 2017

@hgonzalez94 If you can post the workaround as a gist, that would be awesome. I know sending HTTP messages with binary payloads in earlier versions of React Native could be tricky, so I'm sure others who are unable to upgrade would love to see a stable way to do so.

Please feel free to reopen if you have any questions or concerns.

@jeskew jeskew closed this as completed Sep 20, 2017
@shrinandhini2801
Copy link

Hi Am trying to upload an image and download that and render in tag of react native. I have tried with the solution which @chrisradek have suggested. But it says S3 is not a constructor so i cant create an object and assign the configurations to it . Please advice. Also i am trying with aws amplify library and below is the code ,

storeImageInS3 = () => {
      console.log("inside store image in s3" + JSON.stringify(this.state.ImageSource))
      Storage.put("test-image", this.state.ImageSource.uri)
          .then(result => console.log(result))
          .catch(err => console.log(err));
  }

  getImageFromS3 = () => {
      Storage.get("test-image")
          .then(result => {
              this.setState({ ImageFroms3: { uri: result } });
              console.log("ImageFroms3 state==" + JSON.stringify(this.state.ImageFroms3))
          })
          .catch(err => console.log(err));


  }

I am able to upload , but when i download its not n a proper format . I have tried with base 64 as well as image uri . Its not working. can someone help in resolving this???

@lawrencegrey
Copy link

Hi Am trying to upload an image and download that and render in tag of react native. I have tried with the solution which @chrisradek have suggested. But it says S3 is not a constructor so i cant create an object and assign the configurations to it . Please advice. Also i am trying with aws amplify library and below is the code ,

storeImageInS3 = () => {
console.log("inside store image in s3" + JSON.stringify(this.state.ImageSource))
Storage.put("test-image", this.state.ImageSource.uri)
.then(result => console.log(result))
.catch(err => console.log(err));
}

getImageFromS3 = () => {
Storage.get("test-image")
.then(result => {
this.setState({ ImageFroms3: { uri: result } });
console.log("ImageFroms3 state==" + JSON.stringify(this.state.ImageFroms3))
})
.catch(err => console.log(err));

}
I am able to upload , but when i download its not n a proper format . I have tried with base 64 as well as image uri . Its not working. can someone help in resolving this???

Just display the link to the image like this
<img [src]="imageFileName" width="300px" height="200px" />

var imageFileName = "https://s3.us-east-2.amazonaws.com/nameOfFolder/nameOfFile.ext";

@srchase srchase added third-party This issue is related to third-party libraries or applications. guidance Question that needs advice or information. and removed Question labels Jan 4, 2019
@lock
Copy link

lock bot commented Sep 28, 2019

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.

@lock lock bot locked as resolved and limited conversation to collaborators Sep 28, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
guidance Question that needs advice or information. third-party This issue is related to third-party libraries or applications.
Projects
None yet
Development

No branches or pull requests

7 participants