【问题标题】:React Native upload to S3 with presigned URL使用预签名 URL 将本机上传到 S3
【发布时间】:2016-10-12 03:16:58
【问题描述】:

一直在尝试使用预签名的 url 从 React Native 将图像上传到 S3,但没有成功。这是我的代码:

在节点中生成预签名的url:

const s3 = new aws.S3();

const s3Params = {
  Bucket: bucket,
  Key: fileName,
  Expires: 60,
  ContentType: 'image/jpeg',  
  ACL: 'public-read'
};

return s3.getSignedUrl('putObject', s3Params);

这是对 S3 的 RN 请求:

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

错误信息:

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

我可以使用生成的 url 成功地 curl 和图像到 S3,并且我似乎能够从 RN 成功发布到 requestb.in(但是我只能看到 requestb.in 上的原始数据,所以不能 100% 确定图像正确存在)。

基于这一切,我已将问题缩小到 1) 我的图像上传时间不正确,或 2) S3 希望我的请求的方式与它的传入方式不同。

任何帮助将不胜感激!

更新

如果正文只是文本({'data': 'foo'}),可以成功地从 RN 发布到 S3。也许 AWS 不喜欢多种形式的数据?如何在 RN 中仅作为文件发送???

【问题讨论】:

  • 不确定您的签名为何无效。我有几乎相同的签名代码,它工作正常。您的空成功上传是由于您将路径作为 URI 传递。 file:///var/.../4BBAE22E-DADC-4240-A266-8E469C0636B8.jpg 应该可以工作。
  • 您的 AWS 密钥是否有任何尾随转发“/”?
  • @DanielBasedow 我不认为签名无效。我可以使用它 curl 上传图片。我认为我的 RN 请求的形成方式有问题?
  • @Cole 只是想知道这是否是最佳实践?当客户端上的用户想要上传图片时,我应该将文件名发送到 Node.js 服务器以生成预签名 URL -> 将预签名 URL 发送回客户端应用程序 -> 然后客户端应用程序上传到预签名 URL?
  • @kayla 我的理解是从客户端直接上传到 S3 是首选方法是的。通过后端发送文件会给服务器带来不必要的负载。以下是 heroku 文档:devcenter.heroku.com/articles/s3-upload-node

标签: amazon-web-services amazon-s3 xmlhttprequest react-native pre-signed-url


【解决方案1】:

FormData 将创建一个multipart/form-data 请求。 S3 PUT 对象需要它的请求体是一个文件。

您只需在请求正文中发送您的文件,而无需将其包装到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);
};

例如在浏览器中查看https://devcenter.heroku.com/articles/s3-upload-node。还请确保您的 Content-Type 标头与签名的 URL 请求匹配。

【讨论】:

  • 您能否详细说明确保 Content-Type 标头与签名的 URL 请求匹配?我不太明白你的意思。
  • 你只需要设置xhr.setRequestHeader('Content-Type', fileType)
  • 我认为 S3 PUT 需要 Content-Length see docs,而 S3 POST 需要 file 对象 see docs
  • @edward-samuel,我想知道如何使用 Fetch 而不是 XMLHttpRequest 实现同样的效果
  • 此代码在 iOS 上适用于我,但在 Android 上无效。我得到 xhr.status = 0 但没有上传到 S3。
【解决方案2】:

我在上传到 iOS 和 Android 上的预签名 S3 URL 上浪费了太多时间。 对我有用的是rn-fetch-bloblib

代码sn-p:

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))

【讨论】:

  • 你是我的救星!
  • @Peter Machowski 对大文件也能正常工作吗?
  • @UmangLoriya 我没有用
  • 你能解释一下你的 headers 对象是什么样子的吗?我正在传递一个 FormData 对象,其中包含带有预签名 URL(存储桶、区域、密钥……)的所有所需信息,但是在尝试上传时出现黑屏……
  • @Mayoul 不需要特定的标题。黑屏提示其他代码有错误。
【解决方案3】:
"rn-fetch-blob": 0.12.0,
"react-native": 0.61.5

此代码适用于 Android 和 iOS

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

注意 {'Content-Type': undefined} 对于 iOS 是必需的

【讨论】:

    【解决方案4】:

    很抱歉,如果没有一个适用于任何机构。我花了 5 天时间才让这个工作。 5天没有结果,直到我睡眼惺忪的小睡后变成绿色。猜猜我做了一个甜蜜的梦,带来了这个想法。这么快说你在你的服务器上有一个端点,可以为来自反应本机端或反应端或任何网络前沿的请求生成签名 URL。我会为 react native 和 react 做这个(可以为 html 页面和 angular 页面服务)。

    网络方法

    将图像上传到 S3 存储桶预签名 URI

    /*
          Function to carry out the actual PUT request to S3 using the signed request from the app.
        */
        function uploadFile(file, signedRequest, url){
         // document.getElementById('preview').src = url; // THE PREVIEW PORTION
            //    document.getElementById('avatar-url').value = url; //
          const xhr = new XMLHttpRequest();
          xhr.open('PUT', signedRequest);
          xhr.onreadystatechange = () => {
            if(xhr.readyState === 4){
              if(xhr.status === 200){
                document.getElementById('preview').src = url;
               // document.getElementById('avatar-url').value = url;
              }
              else{
                alert('Could not upload file.');
              }
            }
          };
          xhr.send(file);
        }
    
        /*
          Function to get the temporary signed request from the app.
          If request successful, continue to upload the file using this signed
          request.
        */
        function getSignedRequest(file){
          const xhr = new XMLHttpRequest();
    
          xhr.open('GET', 'http://localhost:1234'+`/sign-s3?file-name=${file.name}&file-type=${file.type}`);
            xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
        xhr.setRequestHeader('Content-type', 'application/json');
        xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
          xhr.onreadystatechange = () => {
            if(xhr.readyState === 4){
              if(xhr.status === 200){
                const response = JSON.parse(xhr.responseText);
                uploadFile(file, response.signedRequest, response.url);
              }
              else{
                alert('Could not get signed URL.');
              }
            }
          };
          xhr.send();
        }
    
        /*
         Function called when file input updated. If there is a file selected, then
         start upload procedure by asking for a signed request from the app.
        */
        function initUpload(){
          const files = document.getElementById('file-input').files;
          const file = files[0];
          if(file == null){
            return alert('No file selected.');
          }
          getSignedRequest(file);
        }
    
        /*
         Bind listeners when the page loads.
        */
    
    
       //check if user is actually on the profile page
    //just ensure that the id profile page exist  on your html
      if (document.getElementById('profile-page')) {
        document.addEventListener('DOMContentLoaded',() => {
    
          ///here is ur upload trigger bttn effect
    
            document.getElementById('file-input').onchange = initUpload;
        });
    
      }
    
    
    
    

    【讨论】:

      【解决方案5】:

      对于 React Native,我不会使用任何 3RD 方库。

      我有我的选择图像功能,可以选择图像并使用 xhr 上传

      const pickImage = async () => {
          let result = await ImagePicker.launchImageLibraryAsync({
           // mediaTypes: ImagePicker.MediaTypeOptions.All,
            allowsEditing: true,
            aspect: [4, 3],
            quality: 1,
            base64:true
          });
      
          console.log(result);
      
      
      
      
      
      
          if (!result.cancelled) {
           // setImage(result.uri);
            let base64Img = `data:image/jpg;base64,${result.uri}`;
      
      
      
      
             // ImagePicker saves the taken photo to disk and returns a local URI to it
        let localUri = result.uri;
        let filename = localUri.split('/').pop();
      
        // Infer the type of the image
        let match = /\.(\w+)$/.exec(filename);
        let type = match ? `image/${match[1]}` : `image`;
      
        // Upload the image using the fetch and FormData APIs
        let formData = new FormData();
        // Assume "photo" is the name of the form field the server expects
        formData.append('file', { uri: base64Img, name: filename, type });
      
        const xhr = new XMLHttpRequest();
      
      
        xhr.open('GET', ENVIRONMENTS.CLIENT_API+`/sign-s3?file-name=${filename}&file-type=${type}`);
        xhr.setRequestHeader('Access-Control-Allow-Headers', '*');
      xhr.setRequestHeader('Content-type', 'application/json');
      // xhr.setRequestHeader('Content-type', 'multipart/form-data');
      xhr.setRequestHeader('Access-Control-Allow-Origin', '*');
       xhr.setRequestHeader('X-Amz-ACL', 'public-read') //added
      xhr.setRequestHeader('Content-Type', type) //added
      xhr.onreadystatechange = () => {
        if(xhr.readyState === 4){
          if(xhr.status === 200){
            const response = JSON.parse(xhr.responseText);
            alert(JSON.stringify( response.signedRequest, response.url))
            // uploadFile(file, response.signedRequest, response.url);
            // this.setState({imagename:file.name})
            const xhr2 = new XMLHttpRequest();
      
                  xhr2.open('PUT', response.signedRequest);
                  xhr2.setRequestHeader('Access-Control-Allow-Headers', '*');
                  xhr2.setRequestHeader('Content-type', 'application/json');
                  // xhr2.setRequestHeader('Content-type', 'multipart/form-data');
                  xhr2.setRequestHeader('Access-Control-Allow-Origin', '*');
                  //  xhr2.setRequestHeader('X-Amz-ACL', 'public-read') //added
                  xhr2.setRequestHeader('Content-Type', type) //added
                  xhr2.onreadystatechange = () => {
                    if(xhr2.readyState === 4){
                      if(xhr2.status === 200){
      
                        alert("successful upload ")
                      }
                      else{
                        // alert('Could not upload file.');
                        var error = new Error(xhr.responseText)
                        error.code = xhr.status;
                        for (var key in response) error[key] = response[key]
                        alert(error)
                      }
                    }
                  };
                  xhr2.send( result.base64)
          }
          else{
            alert('Could not get signed URL.');
          }
        }
      };
      xhr.send();
      
      
      
      
      
      
      
      
          }
      
      
        };
      
      
      
      
      
      
      then some where in the render method
      
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Button title="Pick an image from camera roll" onPress={pickImage} />
            {image && <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />}
          </View>
      
      
      hope it helps any one who doesnt want sleepless nights like me.
      

      【讨论】:

        猜你喜欢
        • 2020-09-30
        • 1970-01-01
        • 2021-02-15
        • 1970-01-01
        • 2021-08-26
        • 2020-10-23
        • 2020-04-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多